DEV: Introduce Chat Notices with publishing method (#22369)

This commit is contained in:
Mark VanLandingham 2023-07-06 08:26:25 -05:00 committed by GitHub
parent c6cd3af5b5
commit 3171fd1a0a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 193 additions and 37 deletions

View File

@ -475,6 +475,12 @@ module Chat
)
end
def self.publish_notice(user_id:, channel_id:, text_content:)
payload = { type: "notice", text_content: text_content, channel_id: channel_id }
MessageBus.publish("/chat/#{channel_id}", payload, user_ids: [user_id])
end
private
def self.permissions(chat_channel)

View File

@ -21,7 +21,7 @@
@displayed={{this.includeHeader}}
/>
<ChatRetentionReminder @channel={{@channel}} />
<Chat::Notices @channel={{@channel}} />
<ChatMentionWarnings />

View File

@ -0,0 +1,17 @@
<div class="chat-notices">
<ChatRetentionReminder @channel={{@channel}} />
{{#each this.noticesForChannel as |notice|}}
<div class="chat-notices__notice">
<p class="chat-notices__notice__content">
{{notice.textContent}}
</p>
<DButton
@icon="times"
@class="btn-flat chat-notices__notice__clear"
@action={{fn this.clearNotice notice}}
/>
</div>
{{/each}}
</div>

View File

@ -0,0 +1,18 @@
import Component from "@glimmer/component";
import { inject as service } from "@ember/service";
import { action } from "@ember/object";
export default class ChatNotices extends Component {
@service("chat-channel-pane-subscriptions-manager") subscriptionsManager;
get noticesForChannel() {
return this.subscriptionsManager.notices.filter(
(notice) => notice.channelId === this.args.channel.id
);
}
@action
clearNotice(notice) {
this.subscriptionsManager.clearNotice(notice);
}
}

View File

@ -0,0 +1,15 @@
import { tracked } from "@glimmer/tracking";
export default class ChatNotice {
static create(args = {}) {
return new ChatNotice(args);
}
@tracked channelId;
@tracked textContent;
constructor(args = {}) {
this.channelId = args.channel_id;
this.textContent = args.text_content;
}
}

View File

@ -1,11 +1,16 @@
import { inject as service } from "@ember/service";
import { tracked } from "@glimmer/tracking";
import { TrackedArray } from "@ember-compat/tracked-built-ins";
import ChatPaneBaseSubscriptionsManager from "./chat-pane-base-subscriptions-manager";
import ChatThreadPreview from "../models/chat-thread-preview";
import ChatNotice from "../models/chat-notice";
export default class ChatChannelPaneSubscriptionsManager extends ChatPaneBaseSubscriptionsManager {
@service chat;
@service currentUser;
@tracked notices = new TrackedArray();
get messageBusChannel() {
return `/chat/${this.model.id}`;
}
@ -18,6 +23,17 @@ export default class ChatChannelPaneSubscriptionsManager extends ChatPaneBaseSub
return;
}
handleNotice(data) {
this.notices.push(ChatNotice.create(data));
}
clearNotice(notice) {
const index = this.notices.indexOf(notice);
if (index > -1) {
this.notices.splice(index, 1);
}
}
handleThreadOriginalMessageUpdate(data) {
const message = this.messagesManager.findMessage(data.original_message_id);
if (message) {

View File

@ -116,6 +116,9 @@ export default class ChatPaneBaseSubscriptionsManager extends Service {
case "update_thread_original_message":
this.handleThreadOriginalMessageUpdate(busData);
break;
case "notice":
this.handleNotice(busData);
break;
}
}
@ -248,6 +251,10 @@ export default class ChatPaneBaseSubscriptionsManager extends Service {
throw "not implemented";
}
handleNotice() {
throw "not implemented";
}
_afterDeleteMessage() {
throw "not implemented";
}

View File

@ -32,6 +32,11 @@ export default class ChatThreadPaneSubscriptionsManager extends ChatPaneBaseSubs
return;
}
// NOTE: We don't yet handle notices inside of threads so do nothing.
handleNotice() {
return;
}
_afterDeleteMessage(targetMsg, data) {
if (this.model.currentUserMembership?.lastReadMessageId === targetMsg.id) {
this.model.currentUserMembership.lastReadMessageId =

View File

@ -0,0 +1,42 @@
.chat-notices {
display: flex;
flex-direction: column;
gap: 0.5em;
position: absolute;
top: 0;
left: 50%;
transform: translateX(-50%);
z-index: 10;
min-width: 280px;
&__notice,
.chat-retention-reminder {
display: flex;
align-items: center;
justify-content: space-between;
background: var(--tertiary-low);
padding: 0.5em 0 0.5em 1em;
color: var(--primary);
padding: 0.5em 0 0.5em 1em;
}
.btn-flat {
margin: 0 0.25em;
color: var(--primary-medium);
&:hover,
&:focus {
background-color: transparent;
.d-icon {
color: var(--primary);
}
}
.d-icon {
color: var(--primary-medium);
}
}
}
.full-page-chat .chat-notices {
top: 4rem;
}

View File

@ -1,35 +0,0 @@
.chat-retention-reminder {
display: flex;
position: absolute;
top: 0;
left: 50%;
transform: translateX(-50%);
align-items: center;
justify-content: space-between;
background: var(--tertiary-low);
padding: 0.5em 0 0.5em 1em;
font-size: var(--font-down-1);
color: var(--primary);
z-index: 10;
min-width: 280px;
.btn-flat.dismiss-btn {
margin-left: 0.25em;
color: var(--primary-medium);
&:hover,
&:focus {
background-color: transparent;
.d-icon {
color: var(--primary);
}
}
.d-icon {
color: var(--primary-medium);
}
}
}
.full-page-chat .chat-retention-reminder {
top: 4rem;
}

View File

@ -28,10 +28,10 @@
@import "chat-message-separator";
@import "chat-message-thread-indicator";
@import "chat-message";
@import "chat-notices";
@import "chat-onebox";
@import "chat-reply";
@import "chat-replying-indicator";
@import "chat-retention-reminder";
@import "chat-selection-manager";
@import "chat-side-panel";
@import "chat-skeleton";

View File

@ -0,0 +1,65 @@
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
import hbs from "htmlbars-inline-precompile";
import fabricators from "discourse/plugins/chat/discourse/lib/fabricators";
import { query, queryAll } from "discourse/tests/helpers/qunit-helpers";
import { module, test } from "qunit";
import { click, render } from "@ember/test-helpers";
module("Discourse Chat | Component | chat-notice", function (hooks) {
setupRenderingTest(hooks);
test("displays all notices for a channel", async function (assert) {
this.channel = fabricators.channel();
this.manager = this.container.lookup(
"service:chatChannelPaneSubscriptionsManager"
);
this.manager.handleNotice({
channel_id: this.channel.id,
text_content: "hello",
});
this.manager.handleNotice({
channel_id: this.channel.id,
text_content: "goodbye",
});
this.manager.handleNotice({
channel_id: this.channel.id + 1,
text_content: "N/A",
});
await render(hbs`<ChatNotices @channel={{this.channel}} />`);
const notices = queryAll(".chat-notices .chat-notices__notice");
assert.strictEqual(notices.length, 2, "Two notices are rendered");
assert.true(notices[0].innerText.includes("hello"));
assert.true(notices[1].innerText.includes("goodbye"));
});
test("Notices can be cleared", async function (assert) {
this.channel = fabricators.channel();
this.manager = this.container.lookup(
"service:chatChannelPaneSubscriptionsManager"
);
this.manager.handleNotice({
channel_id: this.channel.id,
text_content: "hello",
});
await render(hbs`<ChatNotices @channel={{this.channel}} />`);
assert.strictEqual(
queryAll(".chat-notices .chat-notices__notice").length,
1,
"Notice is present"
);
await click(query(".chat-notices__notice__clear"), "Clear the notice");
assert.strictEqual(
queryAll(".chat-notices .chat-notices__notice").length,
0,
"Notice was cleared"
);
});
});