FIX: correctly handle subscriptions (#24270)
Subscriptions manager have been a pain since the beginning, one of the problem is that thread and channels behave mostly the same but with various small difference which I expect to increase over time. Trying to use subclasses for this case has proven to be a mistake, this commit now uses a class for each case (channel, thread) which for now contains a lot of duplication, which might be reduced in the future but has the merit to make reasoning about each case very simple. This refactor is fixing a bug introduced in90efdd7f9d
which was causing the wrong channel to be unsubscribed, this shouldn't be possible anymore. We had tests for this which were disabled due to flakeyness, I will consider re-enabling them in the future. Other notes: - notices had been added to the subscriptions manager service, they have been moved into their own dedicated service: `ChatChannelNoticesManager` - the `(each model)` trick used in `<ChatChannel />` since90efdd7f9d
to ensure atomicity has been applied to `<ChatThread />` too
This commit is contained in:
parent
1d68ff430b
commit
dcaa719363
|
@ -13,6 +13,7 @@ import {
|
|||
} from "discourse/lib/user-presence";
|
||||
import discourseDebounce from "discourse-common/lib/debounce";
|
||||
import { bind } from "discourse-common/utils/decorators";
|
||||
import ChatChannelSubscriptionManager from "discourse/plugins/chat/discourse/lib/chat-channel-subscription-manager";
|
||||
import {
|
||||
FUTURE,
|
||||
PAST,
|
||||
|
@ -29,9 +30,6 @@ import {
|
|||
scrollListToMessage,
|
||||
} from "discourse/plugins/chat/discourse/lib/scroll-helpers";
|
||||
import ChatMessage from "discourse/plugins/chat/discourse/models/chat-message";
|
||||
// TODO (martin) Remove this when the handleSentMessage logic inside chatChannelPaneSubscriptionsManager
|
||||
// is moved over from this file completely.
|
||||
import { handleStagedMessage } from "discourse/plugins/chat/discourse/services/chat-pane-base-subscriptions-manager";
|
||||
import { stackingContextFix } from "../lib/chat-ios-hacks";
|
||||
|
||||
export default class ChatChannel extends Component {
|
||||
|
@ -40,7 +38,6 @@ export default class ChatChannel extends Component {
|
|||
@service chat;
|
||||
@service chatApi;
|
||||
@service chatChannelsManager;
|
||||
@service chatChannelPaneSubscriptionsManager;
|
||||
@service chatComposerPresenceManager;
|
||||
@service chatDraftsManager;
|
||||
@service chatEmojiPickerManager;
|
||||
|
@ -97,7 +94,7 @@ export default class ChatChannel extends Component {
|
|||
teardownListeners() {
|
||||
this.#cancelHandlers();
|
||||
removeOnPresenceChange(this.onPresenceChangeCallback);
|
||||
this.unsubscribeToUpdates(this.args.channel.id);
|
||||
this.subscriptionManager.teardown();
|
||||
}
|
||||
|
||||
@action
|
||||
|
@ -140,7 +137,11 @@ export default class ChatChannel extends Component {
|
|||
return;
|
||||
}
|
||||
|
||||
this.subscribeToUpdates(this.args.channel);
|
||||
this.subscriptionManager = new ChatChannelSubscriptionManager(
|
||||
this,
|
||||
this.args.channel,
|
||||
{ onNewMessage: this.onNewMessage }
|
||||
);
|
||||
|
||||
if (this.args.targetMessageId) {
|
||||
this.debounceHighlightOrFetchMessage(this.args.targetMessageId);
|
||||
|
@ -149,6 +150,14 @@ export default class ChatChannel extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
@bind
|
||||
onNewMessage(message) {
|
||||
stackingContextFix(this.scrollable, () => {
|
||||
this.messagesManager.addMessages([message]);
|
||||
});
|
||||
this.debouncedUpdateLastReadMessage();
|
||||
}
|
||||
|
||||
@bind
|
||||
onPresenceChangeCallback(present) {
|
||||
if (present) {
|
||||
|
@ -464,36 +473,6 @@ export default class ChatChannel extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
@bind
|
||||
onMessage(data) {
|
||||
switch (data.type) {
|
||||
case "sent":
|
||||
this.handleSentMessage(data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
handleSentMessage(data) {
|
||||
if (data.chat_message.user.id === this.currentUser.id && data.staged_id) {
|
||||
const stagedMessage = handleStagedMessage(
|
||||
this.args.channel,
|
||||
this.messagesManager,
|
||||
data
|
||||
);
|
||||
if (stagedMessage) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const message = ChatMessage.create(this.args.channel, data.chat_message);
|
||||
message.manager = this.args.channel.messagesManager;
|
||||
stackingContextFix(this.scrollable, () => {
|
||||
this.messagesManager.addMessages([message]);
|
||||
});
|
||||
this.debouncedUpdateLastReadMessage();
|
||||
this.args.channel.lastMessage = message;
|
||||
}
|
||||
|
||||
@action
|
||||
async onSendMessage(message) {
|
||||
await message.cook();
|
||||
|
@ -620,28 +599,6 @@ export default class ChatChannel extends Component {
|
|||
});
|
||||
}
|
||||
|
||||
unsubscribeToUpdates(channelId) {
|
||||
if (!channelId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.chatChannelPaneSubscriptionsManager.unsubscribe();
|
||||
this.messageBus.unsubscribe(`/chat/${channelId}`, this.onMessage);
|
||||
}
|
||||
|
||||
subscribeToUpdates(channel) {
|
||||
if (!channel) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.messageBus.subscribe(
|
||||
`/chat/${channel.id}`,
|
||||
this.onMessage,
|
||||
channel.channelMessageBusLastId
|
||||
);
|
||||
this.chatChannelPaneSubscriptionsManager.subscribe(channel);
|
||||
}
|
||||
|
||||
@action
|
||||
addAutoFocusEventListener() {
|
||||
document.addEventListener("keydown", this._autoFocus);
|
||||
|
|
|
@ -17,10 +17,12 @@
|
|||
>
|
||||
{{#if this.chat.activeChannel}}
|
||||
{{#each (array this.chat.activeChannel) as |channel|}}
|
||||
<ChatChannel
|
||||
@targetMessageId={{readonly @params.messageId}}
|
||||
@channel={{channel}}
|
||||
/>
|
||||
{{#if channel}}
|
||||
<ChatChannel
|
||||
@targetMessageId={{readonly @params.messageId}}
|
||||
@channel={{channel}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { array } from "@ember/helper";
|
||||
import { action } from "@ember/object";
|
||||
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
|
||||
import didUpdate from "@ember/render-modifiers/modifiers/did-update";
|
||||
|
@ -88,12 +89,14 @@ export default class ChatDrawerThread extends Component {
|
|||
{{didUpdate this.fetchChannelAndThread @params.channelId}}
|
||||
{{didUpdate this.fetchChannelAndThread @params.threadId}}
|
||||
>
|
||||
{{#if this.chat.activeChannel.activeThread}}
|
||||
<ChatThread
|
||||
@thread={{this.chat.activeChannel.activeThread}}
|
||||
@targetMessageId={{@params.messageId}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{#each (array this.chat.activeChannel.activeThread) as |thread|}}
|
||||
{{#if thread}}
|
||||
<ChatThread
|
||||
@thread={{thread}}
|
||||
@targetMessageId={{@params.messageId}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</template>
|
||||
|
|
|
@ -8,11 +8,11 @@ const COMPONENT_DICT = {
|
|||
};
|
||||
|
||||
export default class ChatNotices extends Component {
|
||||
@service("chat-channel-pane-subscriptions-manager") subscriptionsManager;
|
||||
@service("chat-channel-notices-manager") noticesManager;
|
||||
|
||||
@action
|
||||
clearNotice() {
|
||||
this.subscriptionsManager.clearNotice(this.args.notice);
|
||||
this.noticesManager.clearNotice(this.args.notice);
|
||||
}
|
||||
|
||||
get component() {
|
||||
|
|
|
@ -2,10 +2,10 @@ import Component from "@glimmer/component";
|
|||
import { inject as service } from "@ember/service";
|
||||
|
||||
export default class ChatNotices extends Component {
|
||||
@service("chat-channel-pane-subscriptions-manager") subscriptionsManager;
|
||||
@service("chat-channel-notices-manager") noticesManager;
|
||||
|
||||
get noticesForChannel() {
|
||||
return this.subscriptionsManager.notices.filter(
|
||||
return this.noticesManager.notices.filter(
|
||||
(notice) => notice.channelId === this.args.channel.id
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
data-id={{@thread.id}}
|
||||
{{did-insert this.setUploadDropZone}}
|
||||
{{did-insert this.didUpdateThread}}
|
||||
{{did-update this.didUpdateThread @thread.id}}
|
||||
{{will-destroy this.teardown}}
|
||||
>
|
||||
{{#if @includeHeader}}
|
||||
|
|
|
@ -9,6 +9,7 @@ import { resetIdle } from "discourse/lib/desktop-notifications";
|
|||
import { NotificationLevels } from "discourse/lib/notification-levels";
|
||||
import discourseDebounce from "discourse-common/lib/debounce";
|
||||
import { bind } from "discourse-common/utils/decorators";
|
||||
import ChatChannelThreadSubscriptionManager from "discourse/plugins/chat/discourse/lib/chat-channel-thread-subscription-manager";
|
||||
import {
|
||||
FUTURE,
|
||||
PAST,
|
||||
|
@ -36,7 +37,6 @@ export default class ChatThread extends Component {
|
|||
@service chatHistory;
|
||||
@service chatThreadComposer;
|
||||
@service chatThreadPane;
|
||||
@service chatThreadPaneSubscriptionsManager;
|
||||
@service currentUser;
|
||||
@service router;
|
||||
@service siteSettings;
|
||||
|
@ -85,14 +85,9 @@ export default class ChatThread extends Component {
|
|||
this.uploadDropZone = element;
|
||||
}
|
||||
|
||||
@action
|
||||
subscribeToUpdates() {
|
||||
this.chatThreadPaneSubscriptionsManager.subscribe(this.args.thread);
|
||||
}
|
||||
|
||||
@action
|
||||
teardown() {
|
||||
this.chatThreadPaneSubscriptionsManager.unsubscribe();
|
||||
this.subscriptionManager.teardown();
|
||||
cancel(this._debouncedFillPaneAttemptHandler);
|
||||
cancel(this._debounceUpdateLastReadMessageHandler);
|
||||
}
|
||||
|
@ -166,7 +161,11 @@ export default class ChatThread extends Component {
|
|||
@action
|
||||
loadMessages() {
|
||||
this.fetchMessages();
|
||||
this.subscribeToUpdates();
|
||||
this.subscriptionManager = new ChatChannelThreadSubscriptionManager(
|
||||
this,
|
||||
this.args.thread,
|
||||
{ onNewMessage: this.onNewMessage }
|
||||
);
|
||||
}
|
||||
|
||||
@action
|
||||
|
@ -296,6 +295,11 @@ export default class ChatThread extends Component {
|
|||
scrollListToMessage(this.scrollable, message, opts);
|
||||
}
|
||||
|
||||
@bind
|
||||
onNewMessage(message) {
|
||||
this.messagesManager.addMessages([message]);
|
||||
}
|
||||
|
||||
@bind
|
||||
processMessages(thread, result) {
|
||||
const messages = result.messages.map((messageData) => {
|
||||
|
|
|
@ -1,82 +1,46 @@
|
|||
import Service, { inject as service } from "@ember/service";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { getOwner, setOwner } from "@ember/application";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { cloneJSON } from "discourse-common/lib/object";
|
||||
import { bind } from "discourse-common/utils/decorators";
|
||||
import ChatMessage from "discourse/plugins/chat/discourse/models/chat-message";
|
||||
import ChatThreadPreview from "discourse/plugins/chat/discourse/models/chat-thread-preview";
|
||||
|
||||
export function handleStagedMessage(channel, messagesManager, data) {
|
||||
const stagedMessage = messagesManager.findStagedMessage(data.staged_id);
|
||||
|
||||
if (!stagedMessage) {
|
||||
return;
|
||||
}
|
||||
|
||||
stagedMessage.error = null;
|
||||
stagedMessage.id = data.chat_message.id;
|
||||
stagedMessage.staged = false;
|
||||
stagedMessage.excerpt = data.chat_message.excerpt;
|
||||
stagedMessage.channel = channel;
|
||||
stagedMessage.createdAt = new Date(data.chat_message.created_at);
|
||||
|
||||
return stagedMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles subscriptions for MessageBus messages sent from Chat::Publisher
|
||||
* to the channel and thread panes. There are individual services for
|
||||
* each (ChatChannelPaneSubscriptionsManager and ChatThreadPaneSubscriptionsManager)
|
||||
* that implement their own logic where necessary. Functions which will
|
||||
* always be different between the two raise a "not implemented" error in
|
||||
* the base class, and the child class must define the associated function,
|
||||
* even if it is a noop in that context.
|
||||
*
|
||||
* For example, in the thread context there is no need to handle the thread
|
||||
* creation event, because the panel will not be open in that case.
|
||||
*/
|
||||
export default class ChatPaneBaseSubscriptionsManager extends Service {
|
||||
@service chat;
|
||||
export default class ChatChannelSubscriptionManager {
|
||||
@service currentUser;
|
||||
@service chatChannelNoticesManager;
|
||||
@service messageBus;
|
||||
|
||||
messageBusChannel = null;
|
||||
messageBusLastId = null;
|
||||
@tracked channel;
|
||||
|
||||
get messagesManager() {
|
||||
return this.model.messagesManager;
|
||||
}
|
||||
constructor(context, channel, { onNewMessage } = {}) {
|
||||
setOwner(this, getOwner(context));
|
||||
|
||||
beforeSubscribe() {}
|
||||
afterMessage() {}
|
||||
|
||||
subscribe(model) {
|
||||
this.unsubscribe();
|
||||
this.beforeSubscribe(model);
|
||||
this.model = model;
|
||||
|
||||
if (!this.messageBusChannel) {
|
||||
return;
|
||||
}
|
||||
this.channel = channel;
|
||||
this.onNewMessage = onNewMessage;
|
||||
|
||||
this.messageBus.subscribe(
|
||||
this.messageBusChannel,
|
||||
this.onMessage,
|
||||
this.messageBusLastId
|
||||
this.channel.channelMessageBusLastId
|
||||
);
|
||||
}
|
||||
|
||||
unsubscribe() {
|
||||
if (!this.model) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.messageBus.unsubscribe(this.messageBusChannel, this.onMessage);
|
||||
this.model = null;
|
||||
get messagesManager() {
|
||||
return this.channel.messagesManager;
|
||||
}
|
||||
|
||||
handleStagedMessageInternal(channel, data) {
|
||||
return handleStagedMessage(channel, this.messagesManager, data);
|
||||
get messageBusChannel() {
|
||||
return `/chat/${this.channel.id}`;
|
||||
}
|
||||
|
||||
teardown() {
|
||||
this.messageBus.unsubscribe(this.messageBusChannel, this.onMessage);
|
||||
this.modelId = null;
|
||||
}
|
||||
|
||||
@bind
|
||||
onMessage(busData) {
|
||||
onMessage(busData, _, __, lastMessageBusId) {
|
||||
switch (busData.type) {
|
||||
case "sent":
|
||||
this.handleSentMessage(busData);
|
||||
|
@ -119,11 +83,42 @@ export default class ChatPaneBaseSubscriptionsManager extends Service {
|
|||
break;
|
||||
}
|
||||
|
||||
this.afterMessage(this.model, ...arguments);
|
||||
this.channel.channelMessageBusLastId = lastMessageBusId;
|
||||
}
|
||||
|
||||
handleSentMessage() {
|
||||
throw "not implemented";
|
||||
handleSentMessage(data) {
|
||||
if (data.chat_message.user.id === this.currentUser.id && data.staged_id) {
|
||||
const stagedMessage = this.handleStagedMessage(
|
||||
this.channel,
|
||||
this.messagesManager,
|
||||
data
|
||||
);
|
||||
if (stagedMessage) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const message = ChatMessage.create(this.channel, data.chat_message);
|
||||
message.manager = this.channel.messagesManager;
|
||||
this.onNewMessage?.(message);
|
||||
this.channel.lastMessage = message;
|
||||
}
|
||||
|
||||
handleStagedMessage(channel, messagesManager, data) {
|
||||
const stagedMessage = messagesManager.findStagedMessage(data.staged_id);
|
||||
|
||||
if (!stagedMessage) {
|
||||
return;
|
||||
}
|
||||
|
||||
stagedMessage.error = null;
|
||||
stagedMessage.id = data.chat_message.id;
|
||||
stagedMessage.staged = false;
|
||||
stagedMessage.excerpt = data.chat_message.excerpt;
|
||||
stagedMessage.channel = channel;
|
||||
stagedMessage.createdAt = new Date(data.chat_message.created_at);
|
||||
|
||||
return stagedMessage;
|
||||
}
|
||||
|
||||
handleProcessedMessage(data) {
|
||||
|
@ -182,7 +177,10 @@ export default class ChatPaneBaseSubscriptionsManager extends Service {
|
|||
this.messagesManager.removeMessage(targetMsg);
|
||||
}
|
||||
|
||||
this._afterDeleteMessage(targetMsg, data);
|
||||
if (this.channel.currentUserMembership.lastReadMessageId === targetMsg.id) {
|
||||
this.channel.currentUserMembership.lastReadMessageId =
|
||||
data.latest_not_deleted_message_id;
|
||||
}
|
||||
}
|
||||
|
||||
handleRestoreMessage(data) {
|
||||
|
@ -211,10 +209,10 @@ export default class ChatPaneBaseSubscriptionsManager extends Service {
|
|||
}
|
||||
|
||||
handleNewThreadCreated(data) {
|
||||
this.model.threadsManager
|
||||
.find(this.model.id, data.thread_id, { fetchIfNotFound: true })
|
||||
this.channel.threadsManager
|
||||
.find(this.channel.id, data.thread_id, { fetchIfNotFound: true })
|
||||
.then((thread) => {
|
||||
const channelOriginalMessage = this.model.messagesManager.findMessage(
|
||||
const channelOriginalMessage = this.channel.messagesManager.findMessage(
|
||||
thread.originalMessage.id
|
||||
);
|
||||
|
||||
|
@ -224,15 +222,14 @@ export default class ChatPaneBaseSubscriptionsManager extends Service {
|
|||
});
|
||||
}
|
||||
|
||||
handleThreadOriginalMessageUpdate() {
|
||||
throw "not implemented";
|
||||
handleNotice(data) {
|
||||
this.chatChannelNoticesManager.handleNotice(data);
|
||||
}
|
||||
|
||||
handleNotice() {
|
||||
throw "not implemented";
|
||||
}
|
||||
|
||||
_afterDeleteMessage() {
|
||||
throw "not implemented";
|
||||
handleThreadOriginalMessageUpdate(data) {
|
||||
const message = this.messagesManager.findMessage(data.original_message_id);
|
||||
if (message?.thread) {
|
||||
message.thread.preview = ChatThreadPreview.create(data.preview);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,215 @@
|
|||
import { tracked } from "@glimmer/tracking";
|
||||
import { getOwner, setOwner } from "@ember/application";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { cloneJSON } from "discourse-common/lib/object";
|
||||
import { bind } from "discourse-common/utils/decorators";
|
||||
import ChatMessage from "discourse/plugins/chat/discourse/models/chat-message";
|
||||
|
||||
export default class ChatChannelThreadSubscriptionManager {
|
||||
@service currentUser;
|
||||
@service messageBus;
|
||||
|
||||
@tracked channel;
|
||||
|
||||
constructor(context, thread, { onNewMessage } = {}) {
|
||||
setOwner(this, getOwner(context));
|
||||
|
||||
this.thread = thread;
|
||||
this.onNewMessage = onNewMessage;
|
||||
|
||||
this.messageBus.subscribe(
|
||||
this.messageBusChannel,
|
||||
this.onMessage,
|
||||
this.thread.channelMessageBusLastId
|
||||
);
|
||||
}
|
||||
|
||||
get messagesManager() {
|
||||
return this.thread.messagesManager;
|
||||
}
|
||||
|
||||
get messageBusChannel() {
|
||||
return `/chat/${this.thread.channel.id}/thread/${this.thread.id}`;
|
||||
}
|
||||
|
||||
teardown() {
|
||||
this.messageBus.unsubscribe(this.messageBusChannel, this.onMessage);
|
||||
}
|
||||
|
||||
@bind
|
||||
onMessage(busData, _, __, lastMessageBusId) {
|
||||
switch (busData.type) {
|
||||
case "sent":
|
||||
this.handleSentMessage(busData);
|
||||
break;
|
||||
case "reaction":
|
||||
this.handleReactionMessage(busData);
|
||||
break;
|
||||
case "processed":
|
||||
this.handleProcessedMessage(busData);
|
||||
break;
|
||||
case "edit":
|
||||
this.handleEditMessage(busData);
|
||||
break;
|
||||
case "refresh":
|
||||
this.handleRefreshMessage(busData);
|
||||
break;
|
||||
case "delete":
|
||||
this.handleDeleteMessage(busData);
|
||||
break;
|
||||
case "bulk_delete":
|
||||
this.handleBulkDeleteMessage(busData);
|
||||
break;
|
||||
case "restore":
|
||||
this.handleRestoreMessage(busData);
|
||||
break;
|
||||
case "self_flagged":
|
||||
this.handleSelfFlaggedMessage(busData);
|
||||
break;
|
||||
case "flag":
|
||||
this.handleFlaggedMessage(busData);
|
||||
break;
|
||||
case "thread_created":
|
||||
this.handleNewThreadCreated(busData);
|
||||
break;
|
||||
}
|
||||
|
||||
this.thread.threadMessageBusLastId = lastMessageBusId;
|
||||
}
|
||||
|
||||
handleSentMessage(data) {
|
||||
if (data.chat_message.user.id === this.currentUser.id && data.staged_id) {
|
||||
const stagedMessage = this.handleStagedMessage(
|
||||
this.thread.channel,
|
||||
this.messagesManager,
|
||||
data
|
||||
);
|
||||
if (stagedMessage) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const message = ChatMessage.create(this.thread.channel, data.chat_message);
|
||||
message.thread = this.thread;
|
||||
message.manager = this.messagesManager;
|
||||
this.onNewMessage?.(message);
|
||||
}
|
||||
|
||||
handleStagedMessage(channel, messagesManager, data) {
|
||||
const stagedMessage = messagesManager.findStagedMessage(data.staged_id);
|
||||
|
||||
if (!stagedMessage) {
|
||||
return;
|
||||
}
|
||||
|
||||
stagedMessage.error = null;
|
||||
stagedMessage.id = data.chat_message.id;
|
||||
stagedMessage.staged = false;
|
||||
stagedMessage.excerpt = data.chat_message.excerpt;
|
||||
stagedMessage.channel = channel;
|
||||
stagedMessage.createdAt = new Date(data.chat_message.created_at);
|
||||
|
||||
return stagedMessage;
|
||||
}
|
||||
|
||||
handleProcessedMessage(data) {
|
||||
const message = this.messagesManager.findMessage(data.chat_message.id);
|
||||
if (message) {
|
||||
message.cooked = data.chat_message.cooked;
|
||||
message.processed = true;
|
||||
}
|
||||
}
|
||||
|
||||
handleReactionMessage(data) {
|
||||
const message = this.messagesManager.findMessage(data.chat_message_id);
|
||||
if (message) {
|
||||
message.react(data.emoji, data.action, data.user, this.currentUser.id);
|
||||
}
|
||||
}
|
||||
|
||||
handleEditMessage(data) {
|
||||
const message = this.messagesManager.findMessage(data.chat_message.id);
|
||||
if (message) {
|
||||
message.excerpt = data.chat_message.excerpt;
|
||||
message.uploads = cloneJSON(data.chat_message.uploads || []);
|
||||
message.edited = data.chat_message.edited;
|
||||
}
|
||||
}
|
||||
|
||||
handleRefreshMessage(data) {
|
||||
const message = this.messagesManager.findMessage(data.chat_message.id);
|
||||
if (message) {
|
||||
message.incrementVersion();
|
||||
}
|
||||
}
|
||||
|
||||
handleBulkDeleteMessage(data) {
|
||||
data.deleted_ids.forEach((deletedId) => {
|
||||
this.handleDeleteMessage({
|
||||
deleted_id: deletedId,
|
||||
deleted_at: data.deleted_at,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
handleDeleteMessage(data) {
|
||||
const deletedId = data.deleted_id;
|
||||
const targetMsg = this.messagesManager.findMessage(deletedId);
|
||||
|
||||
if (!targetMsg) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.currentUser.staff || this.currentUser.id === targetMsg.user.id) {
|
||||
targetMsg.deletedAt = data.deleted_at;
|
||||
targetMsg.deletedById = data.deleted_by_id;
|
||||
targetMsg.expanded = false;
|
||||
} else {
|
||||
this.messagesManager.removeMessage(targetMsg);
|
||||
}
|
||||
|
||||
if (this.thread.currentUserMembership?.lastReadMessageId === targetMsg.id) {
|
||||
this.thread.currentUserMembership.lastReadMessageId =
|
||||
data.latest_not_deleted_message_id;
|
||||
}
|
||||
}
|
||||
|
||||
handleRestoreMessage(data) {
|
||||
const message = this.messagesManager.findMessage(data.chat_message.id);
|
||||
if (message) {
|
||||
message.deletedAt = null;
|
||||
} else {
|
||||
const newMessage = ChatMessage.create(this.model, data.chat_message);
|
||||
newMessage.manager = this.messagesManager;
|
||||
this.messagesManager.addMessages([newMessage]);
|
||||
}
|
||||
}
|
||||
|
||||
handleSelfFlaggedMessage(data) {
|
||||
const message = this.messagesManager.findMessage(data.chat_message_id);
|
||||
if (message) {
|
||||
message.userFlagStatus = data.user_flag_status;
|
||||
}
|
||||
}
|
||||
|
||||
handleFlaggedMessage(data) {
|
||||
const message = this.messagesManager.findMessage(data.chat_message_id);
|
||||
if (message) {
|
||||
message.reviewableId = data.reviewable_id;
|
||||
}
|
||||
}
|
||||
|
||||
handleNewThreadCreated(data) {
|
||||
this.thread.threadsManager
|
||||
.find(this.thread.id, data.thread_id, { fetchIfNotFound: true })
|
||||
.then((thread) => {
|
||||
const channelOriginalMessage = this.thread.messagesManager.findMessage(
|
||||
thread.originalMessage.id
|
||||
);
|
||||
|
||||
if (channelOriginalMessage) {
|
||||
channelOriginalMessage.thread = thread;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import { tracked } from "@glimmer/tracking";
|
||||
import Service from "@ember/service";
|
||||
import { TrackedArray } from "@ember-compat/tracked-built-ins";
|
||||
import ChatNotice from "../models/chat-notice";
|
||||
|
||||
export default class ChatChannelNoticesManager extends Service {
|
||||
@tracked notices = new TrackedArray();
|
||||
|
||||
handleNotice(data) {
|
||||
this.notices.pushObject(ChatNotice.create(data));
|
||||
}
|
||||
|
||||
clearNotice(notice) {
|
||||
this.notices.removeObject(notice);
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
import { tracked } from "@glimmer/tracking";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { TrackedArray } from "@ember-compat/tracked-built-ins";
|
||||
import ChatNotice from "../models/chat-notice";
|
||||
import ChatThreadPreview from "../models/chat-thread-preview";
|
||||
import ChatPaneBaseSubscriptionsManager from "./chat-pane-base-subscriptions-manager";
|
||||
|
||||
export default class ChatChannelPaneSubscriptionsManager extends ChatPaneBaseSubscriptionsManager {
|
||||
@service chat;
|
||||
@service currentUser;
|
||||
|
||||
@tracked notices = new TrackedArray();
|
||||
|
||||
beforeSubscribe(model) {
|
||||
this.messageBusChannel = `/chat/${model.id}`;
|
||||
this.messageBusLastId = model.channelMessageBusLastId;
|
||||
}
|
||||
|
||||
afterMessage(model, _, __, lastMessageBusId) {
|
||||
model.channelMessageBusLastId = lastMessageBusId;
|
||||
}
|
||||
|
||||
handleSentMessage() {
|
||||
return;
|
||||
}
|
||||
|
||||
handleNotice(data) {
|
||||
this.notices.pushObject(ChatNotice.create(data));
|
||||
}
|
||||
|
||||
clearNotice(notice) {
|
||||
this.notices.removeObject(notice);
|
||||
}
|
||||
|
||||
handleThreadOriginalMessageUpdate(data) {
|
||||
const message = this.messagesManager.findMessage(data.original_message_id);
|
||||
if (message?.thread) {
|
||||
message.thread.preview = ChatThreadPreview.create(data.preview);
|
||||
}
|
||||
}
|
||||
|
||||
_afterDeleteMessage(targetMsg, data) {
|
||||
if (this.model.currentUserMembership.lastReadMessageId === targetMsg.id) {
|
||||
this.model.currentUserMembership.lastReadMessageId =
|
||||
data.latest_not_deleted_message_id;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
import ChatMessage from "discourse/plugins/chat/discourse/models/chat-message";
|
||||
import ChatPaneBaseSubscriptionsManager from "./chat-pane-base-subscriptions-manager";
|
||||
|
||||
export default class ChatThreadPaneSubscriptionsManager extends ChatPaneBaseSubscriptionsManager {
|
||||
beforeSubscribe(model) {
|
||||
this.messageBusChannel = `/chat/${model.channel.id}/thread/${model.id}`;
|
||||
this.messageBusLastId = model.threadMessageBusLastId;
|
||||
}
|
||||
|
||||
afterMessage(model, _, __, lastMessageBusId) {
|
||||
model.threadMessageBusLastId = lastMessageBusId;
|
||||
}
|
||||
|
||||
handleSentMessage(data) {
|
||||
if (data.chat_message.user.id === this.currentUser.id && data.staged_id) {
|
||||
const stagedMessage = this.handleStagedMessageInternal(
|
||||
this.model.channel,
|
||||
data
|
||||
);
|
||||
if (stagedMessage) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const message = ChatMessage.create(this.model.channel, data.chat_message);
|
||||
message.thread = this.model;
|
||||
message.manager = this.messagesManager;
|
||||
this.messagesManager.addMessages([message]);
|
||||
}
|
||||
|
||||
// NOTE: noop, there is nothing to do when a thread original message
|
||||
// is updated inside the thread panel (for now).
|
||||
handleThreadOriginalMessageUpdate() {
|
||||
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 =
|
||||
data.latest_not_deleted_message_id;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
<ChatThread
|
||||
@thread={{this.model}}
|
||||
@targetMessageId={{this.targetMessageId}}
|
||||
@includeHeader={{true}}
|
||||
/>
|
||||
{{#each (array this.model) as |thread|}}
|
||||
<ChatThread
|
||||
@thread={{thread}}
|
||||
@targetMessageId={{this.targetMessageId}}
|
||||
@includeHeader={{true}}
|
||||
/>
|
||||
{{/each}}
|
|
@ -12,9 +12,7 @@ module("Discourse Chat | Component | chat-notice", function (hooks) {
|
|||
|
||||
test("displays all notices for a channel", async function (assert) {
|
||||
this.channel = fabricators.channel();
|
||||
this.manager = this.container.lookup(
|
||||
"service:chatChannelPaneSubscriptionsManager"
|
||||
);
|
||||
this.manager = this.container.lookup("service:chatChannelNoticesManager");
|
||||
this.manager.handleNotice({
|
||||
channel_id: this.channel.id,
|
||||
text_content: "hello",
|
||||
|
@ -40,9 +38,7 @@ module("Discourse Chat | Component | chat-notice", function (hooks) {
|
|||
|
||||
test("Notices can be cleared", async function (assert) {
|
||||
this.channel = fabricators.channel();
|
||||
this.manager = this.container.lookup(
|
||||
"service:chatChannelPaneSubscriptionsManager"
|
||||
);
|
||||
this.manager = this.container.lookup("service:chatChannelNoticesManager");
|
||||
this.manager.handleNotice({
|
||||
channel_id: this.channel.id,
|
||||
text_content: "hello",
|
||||
|
@ -66,9 +62,7 @@ module("Discourse Chat | Component | chat-notice", function (hooks) {
|
|||
});
|
||||
test("MentionWithoutMembership notice renders", async function (assert) {
|
||||
this.channel = fabricators.channel();
|
||||
this.manager = this.container.lookup(
|
||||
"service:chatChannelPaneSubscriptionsManager"
|
||||
);
|
||||
this.manager = this.container.lookup("service:chatChannelNoticesManager");
|
||||
const text = "Joffrey can't chat, hermano";
|
||||
this.manager.handleNotice({
|
||||
channel_id: this.channel.id,
|
||||
|
|
Loading…
Reference in New Issue