diff --git a/plugins/chat/app/controllers/chat/chat_controller.rb b/plugins/chat/app/controllers/chat/chat_controller.rb index fcb918f1e43..0be60a23526 100644 --- a/plugins/chat/app/controllers/chat/chat_controller.rb +++ b/plugins/chat/app/controllers/chat/chat_controller.rb @@ -107,6 +107,7 @@ module Chat content: content, staged_id: params[:staged_id], upload_ids: params[:upload_ids], + thread_id: params[:thread_id], ) return render_json_error(chat_message_creator.error) if chat_message_creator.failed? @@ -215,6 +216,7 @@ module Chat messages = preloaded_chat_message_query.where(chat_channel: @chat_channel) messages = messages.with_deleted if guardian.can_moderate_chat?(@chatable) + messages = messages.where(thread_id: params[:thread_id]) if params[:thread_id] if message_id.present? condition = direction == PAST ? "<" : ">" @@ -305,6 +307,7 @@ module Chat messages = preloaded_chat_message_query.where(chat_channel: @chat_channel) messages = messages.with_deleted if guardian.can_moderate_chat?(@chatable) + messages = messages.where(thread_id: params[:thread_id]) if params[:thread_id] past_messages = messages diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-live-pane.hbs b/plugins/chat/assets/javascripts/discourse/components/chat-live-pane.hbs index 9c5d9bf1305..15cd7bdf434 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-live-pane.hbs +++ b/plugins/chat/assets/javascripts/discourse/components/chat-live-pane.hbs @@ -75,7 +75,7 @@ {{/if}} - {{#if (and this.loadedOnce (not @channel.canLoadMorePast))}} + {{#if (and this.loadedOnce (not @channel.messagesManager.canLoadMorePast))}}
{{i18n "chat.all_loaded"}}
@@ -85,7 +85,7 @@ diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-live-pane.js b/plugins/chat/assets/javascripts/discourse/components/chat-live-pane.js index fa69ec1b956..67c3c09757a 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-live-pane.js +++ b/plugins/chat/assets/javascripts/discourse/components/chat-live-pane.js @@ -95,7 +95,7 @@ export default class ChatLivePane extends Component { // Technically we could keep messages to avoid re-fetching them, but // it's not worth the complexity for now - this.args.channel?.clearMessages(); + this.args.channel?.messagesManager?.clearMessages(); if (this._loadedChannelId !== this.args.channel?.id) { this._unsubscribeToUpdates(this._loadedChannelId); @@ -221,8 +221,8 @@ export default class ChatLivePane extends Component { const loadingMoreKey = `loadingMore${capitalize(direction)}`; const canLoadMore = loadingPast - ? this.args.channel.canLoadMorePast - : this.args.channel.canLoadMoreFuture; + ? this.args.channel.messagesManager.canLoadMorePast + : this.args.channel.messagesManager.canLoadMoreFuture; if ( !canLoadMore || @@ -272,7 +272,7 @@ export default class ChatLivePane extends Component { } this.args.channel.details = meta; - this.args.channel.addMessages(messages); + this.args.channel.messagesManager.addMessages(messages); // Edge case for IOS to avoid blank screens // and/or scrolling to bottom losing track of scroll position @@ -301,7 +301,7 @@ export default class ChatLivePane extends Component { return; } - if (!this.args.channel?.canLoadMorePast) { + if (!this.args.channel?.messagesManager?.canLoadMorePast) { return; } @@ -358,7 +358,7 @@ export default class ChatLivePane extends Component { @debounce(100) highlightOrFetchMessage(messageId) { - const message = this.args.channel.findMessage(messageId); + const message = this.#messagesManager?.findMessage(messageId); if (message) { this.scrollToMessage(message.id, { highlight: true, @@ -379,7 +379,7 @@ export default class ChatLivePane extends Component { return; } - const message = this.args.channel.findMessage(messageId); + const message = this.#messagesManager?.findMessage(messageId); if (message?.deletedAt && opts.autoExpand) { message.expanded = true; } @@ -486,7 +486,7 @@ export default class ChatLivePane extends Component { return; } - if (this.args.channel.canLoadMoreFuture) { + if (this.#messagesManager?.canLoadMoreFuture) { this._fetchAndScrollToLatest(); } else if (this.args.channel.messages?.length > 0) { this.scrollToMessage( @@ -525,9 +525,9 @@ export default class ChatLivePane extends Component { } removeMessage(msgData) { - const message = this.args.channel.findMessage(msgData.id); + const message = this.args.channel.messagesManager.findMessage(msgData.id); if (message) { - this.args.channel.removeMessage(message); + this.args.channel.messagesManager.removeMessage(message); } } @@ -579,7 +579,7 @@ export default class ChatLivePane extends Component { stagedMessage.channelId = data.chat_message.chat_channel_id; stagedMessage.createdAt = data.chat_message.created_at; - const inReplyToMsg = this.args.channel.findMessage( + const inReplyToMsg = this.args.channel.messagesManager.findMessage( data.chat_message.in_reply_to?.id ); if (inReplyToMsg && !inReplyToMsg.threadId) { @@ -599,31 +599,36 @@ export default class ChatLivePane extends Component { } if (data.chat_message.user.id === this.currentUser.id && data.staged_id) { - const stagedMessage = this.args.channel.findStagedMessage(data.staged_id); + const stagedMessage = this.args.channel.messagesManager.findStagedMessage( + data.staged_id + ); if (stagedMessage) { return this._handleStagedMessage(stagedMessage, data); } } - if (this.args.channel.canLoadMoreFuture) { + if (this.args.channel.messagesManager.canLoadMoreFuture) { // If we can load more messages, we just notice the user of new messages this.hasNewMessages = true; } else if (this.#isTowardsBottom()) { // If we are at the bottom, we append the message and scroll to it const message = ChatMessage.create(this.args.channel, data.chat_message); - this.args.channel.addMessages([message]); + + this.args.channel.messagesManager.addMessages([message]); this.scrollToLatestMessage(); this.updateLastReadMessage(); } else { // If we are almost at the bottom, we append the message and notice the user const message = ChatMessage.create(this.args.channel, data.chat_message); - this.args.channel.addMessages([message]); + this.args.channel.messagesManager.addMessages([message]); this.hasNewMessages = true; } } handleProcessedMessage(data) { - const message = this.args.channel.findMessage(data.chat_message.id); + const message = this.args.channel.messagesManager.findMessage( + data.chat_message.id + ); if (message) { message.cooked = data.chat_message.cooked; this.scrollToLatestMessage(); @@ -631,14 +636,18 @@ export default class ChatLivePane extends Component { } handleRefreshMessage(data) { - const message = this.args.channel.findMessage(data.chat_message.id); + const message = this.args.channel.messagesManager.findMessage( + data.chat_message.id + ); if (message) { message.incrementVersion(); } } handleEditMessage(data) { - const message = this.args.channel.findMessage(data.chat_message.id); + const message = this.args.channel.messagesManager.findMessage( + data.chat_message.id + ); if (message) { message.message = data.chat_message.message; message.cooked = data.chat_message.cooked; @@ -660,7 +669,7 @@ export default class ChatLivePane extends Component { handleDeleteMessage(data) { const deletedId = data.deleted_id; - const targetMsg = this.args.channel.findMessage(deletedId); + const targetMsg = this.args.channel.messagesManager.findMessage(deletedId); if (!targetMsg) { return; @@ -670,13 +679,15 @@ export default class ChatLivePane extends Component { targetMsg.deletedAt = data.deleted_at; targetMsg.expanded = false; } else { - this.args.channel.removeMessage(targetMsg); + this.args.channel.messagesManager.removeMessage(targetMsg); } } handleReactionMessage(data) { if (data.user.id !== this.currentUser.id) { - const message = this.args.channel.findMessage(data.chat_message_id); + const message = this.args.channel.messagesManager.findMessage( + data.chat_message_id + ); if (message) { message.react(data.emoji, data.action, data.user, this.currentUser.id); } @@ -684,32 +695,40 @@ export default class ChatLivePane extends Component { } handleRestoreMessage(data) { - const message = this.args.channel.findMessage(data.chat_message.id); + const message = this.args.channel.messagesManager.findMessage( + data.chat_message.id + ); if (message) { message.deletedAt = null; } else { - this.args.channel.addMessages([ + this.args.channel.messagesManager.addMessages([ ChatMessage.create(this.args.channel, data.chat_message), ]); } } handleMentionWarning(data) { - const message = this.args.channel.findMessage(data.chat_message_id); + const message = this.args.channel.messagesManager.findMessage( + data.chat_message_id + ); if (message) { message.mentionWarning = EmberObject.create(data); } } handleSelfFlaggedMessage(data) { - const message = this.args.channel.findMessage(data.chat_message_id); + const message = this.args.channel.messagesManager.findMessage( + data.chat_message_id + ); if (message) { message.userFlagStatus = data.user_flag_status; } } handleFlaggedMessage(data) { - const message = this.args.channel.findMessage(data.chat_message_id); + const message = this.args.channel.messagesManager.findMessage( + data.chat_message_id + ); if (message) { message.reviewableId = data.reviewable_id; } @@ -719,6 +738,10 @@ export default class ChatLivePane extends Component { return this.isDestroying || this.isDestroyed; } + get #messagesManager() { + return this.args.channel?.messagesManager; + } + @action sendMessage(message, uploads = []) { resetIdle(); @@ -767,8 +790,8 @@ export default class ChatLivePane extends Component { stagedMessage.inReplyTo = this.replyToMsg; } - this.args.channel.addMessages([stagedMessage]); - if (!this.args.channel.canLoadMoreFuture) { + this.args.channel.messagesManager.addMessages([stagedMessage]); + if (!this.args.channel.messagesManager.canLoadMoreFuture) { this.scrollToLatestMessage(); } @@ -817,7 +840,8 @@ export default class ChatLivePane extends Component { } _onSendError(id, error) { - const stagedMessage = this.args.channel.findStagedMessage(id); + const stagedMessage = + this.args.channel.messagesManager.findStagedMessage(id); if (stagedMessage) { if (error.jqXHR?.responseJSON?.errors?.length) { stagedMessage.error = error.jqXHR.responseJSON.errors[0]; @@ -916,7 +940,7 @@ export default class ChatLivePane extends Component { if (messageId) { this.cancelEditing(); - const message = this.args.channel.findMessage(messageId); + const message = this.args.channel.messagesManager.findMessage(messageId); this.replyToMsg = message; this.appEvents.trigger("chat-composer:reply-to-set", message); this._focusComposer(); @@ -928,7 +952,8 @@ export default class ChatLivePane extends Component { @action replyMessageClicked(message) { - const replyMessageFromLookup = this.args.channel.findMessage(message.id); + const replyMessageFromLookup = + this.args.channel.messagesManager.findMessage(message.id); if (replyMessageFromLookup) { this.scrollToMessage(replyMessageFromLookup.id, { highlight: true, @@ -944,7 +969,7 @@ export default class ChatLivePane extends Component { @action editButtonClicked(messageId) { - const message = this.args.channel.findMessage(messageId); + const message = this.args.channel.messagesManager.findMessage(messageId); this.editingMessage = message; this.scrollToLatestMessage(); this._focusComposer(); diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-thread.hbs b/plugins/chat/assets/javascripts/discourse/components/chat-thread.hbs index 3238d28a611..0813dffb22e 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-thread.hbs +++ b/plugins/chat/assets/javascripts/discourse/components/chat-thread.hbs @@ -1,4 +1,8 @@ -
+
@@ -14,7 +18,7 @@

- {{replace-emoji this.thread.original_message.excerpt}} + {{replace-emoji this.thread.originalMessage.excerpt}}

@@ -23,14 +27,36 @@ }} {{this.thread.original_message_user.username}} + >{{this.thread.originalMessageUser.username}}
+
    + {{#each this.thread.messages as |message|}} +
  • {{message.user.username}}: {{message.message}}
  • + {{/each}} +
+ {{#if (or this.loading this.loadingMoreFuture)}} + + {{/if}}
+ +
\ No newline at end of file diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-thread.js b/plugins/chat/assets/javascripts/discourse/components/chat-thread.js index 1c07f0c538b..94cbc482651 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-thread.js +++ b/plugins/chat/assets/javascripts/discourse/components/chat-thread.js @@ -1,15 +1,34 @@ import Component from "@glimmer/component"; +import { cloneJSON } from "discourse-common/lib/object"; +import ChatMessageDraft from "discourse/plugins/chat/discourse/models/chat-message-draft"; +import { tracked } from "@glimmer/tracking"; +import { action } from "@ember/object"; +import ChatMessage from "discourse/plugins/chat/discourse/models/chat-message"; +import { popupAjaxError } from "discourse/lib/ajax-error"; +import { bind, debounce } from "discourse-common/utils/decorators"; import I18n from "I18n"; import { inject as service } from "@ember/service"; +const PAGE_SIZE = 50; + export default class ChatThreadPanel extends Component { @service siteSettings; @service currentUser; @service chat; @service router; + @service chatApi; + @service chatComposerPresenceManager; + @service appEvents; + + @tracked loading; + @tracked loadingMorePast; get thread() { - return this.chat.activeChannel.activeThread; + return this.channel.activeThread; + } + + get channel() { + return this.chat.activeChannel; } get title() { @@ -19,4 +38,241 @@ export default class ChatThreadPanel extends Component { return I18n.t("chat.threads.op_said"); } + + @action + loadMessages() { + if (this.args.targetMessageId) { + this.requestedTargetMessageId = parseInt(this.args.targetMessageId, 10); + } + + // TODO (martin) Loading/scrolling to selected messagew + // this.highlightOrFetchMessage(this.requestedTargetMessageId); + // if (this.requestedTargetMessageId) { + // } else { + this.fetchMessages(); + // } + } + + get _selfDeleted() { + return this.isDestroying || this.isDestroyed; + } + + @debounce(100) + fetchMessages() { + if (this._selfDeleted) { + return; + } + + this.loadingMorePast = true; + this.loading = true; + this.thread.messagesManager.clearMessages(); + + const findArgs = { pageSize: PAGE_SIZE }; + + // TODO (martin) Find arguments for last read etc. + // const fetchingFromLastRead = !options.fetchFromLastMessage; + // if (this.requestedTargetMessageId) { + // findArgs["targetMessageId"] = this.requestedTargetMessageId; + // } else if (fetchingFromLastRead) { + // findArgs["targetMessageId"] = this._getLastReadId(); + // } + // + findArgs.threadId = this.thread.id; + + return this.chatApi + .messages(this.channel.id, findArgs) + .then((results) => { + if (this._selfDeleted || this.channel.id !== results.meta.channel_id) { + this.router.transitionTo( + "chat.channel", + "-", + results.meta.channel_id + ); + } + + const [messages, meta] = this.afterFetchCallback(this.channel, results); + this.thread.messagesManager.addMessages(messages); + + // TODO (martin) ECHO MODE + this.channel.messagesManager.addMessages(messages); + + // TODO (martin) details needed for thread?? + this.thread.details = meta; + + // TODO (martin) Scrolling to particular messages + // if (this.requestedTargetMessageId) { + // this.scrollToMessage(findArgs["targetMessageId"], { + // highlight: true, + // }); + // } else if (fetchingFromLastRead) { + // this.scrollToMessage(findArgs["targetMessageId"]); + // } else if (messages.length) { + // this.scrollToMessage(messages.lastObject.id); + // } + }) + .catch(this.#handleErrors) + .finally(() => { + if (this._selfDeleted) { + return; + } + + this.requestedTargetMessageId = null; + this.loading = false; + this.loadingMorePast = false; + + // this.fillPaneAttempt(); + }); + } + + @bind + afterFetchCallback(channel, results) { + const messages = []; + let foundFirstNew = false; + + results.chat_messages.forEach((messageData) => { + // If a message has been hidden it is because the current user is ignoring + // the user who sent it, so we want to unconditionally hide it, even if + // we are going directly to the target + if (this.currentUser.ignored_users) { + messageData.hidden = this.currentUser.ignored_users.includes( + messageData.user.username + ); + } + + if (this.requestedTargetMessageId === messageData.id) { + messageData.expanded = !messageData.hidden; + } else { + messageData.expanded = !(messageData.hidden || messageData.deleted_at); + } + + // newest has to be in after fetcg callback as we don't want to make it + // dynamic or it will make the pane jump around, it will disappear on reload + if ( + !foundFirstNew && + messageData.id > channel.currentUserMembership.last_read_message_id + ) { + foundFirstNew = true; + messageData.newest = true; + } + + messages.push(ChatMessage.create(channel, messageData)); + }); + + return [messages, results.meta]; + } + + @action + sendMessage(message, uploads = []) { + // TODO (martin) For desktop notifications + // resetIdle() + if (this.sendingLoading) { + return; + } + + this.sendingLoading = true; + this.channel.draft = ChatMessageDraft.create(); + + // TODO (martin) Handling case when channel is not followed???? IDK if we + // even let people send messages in threads without this, seems weird. + + const stagedMessage = ChatMessage.createStagedMessage(this.channel, { + message, + created_at: new Date(), + uploads: cloneJSON(uploads), + user: this.currentUser, + thread_id: this.thread.id, + }); + + this.thread.messagesManager.addMessages([stagedMessage]); + + // TODO (martin) Scrolling!! + // if (!this.channel.canLoadMoreFuture) { + // this.scrollToBottom(); + // } + + return this.chatApi + .sendMessage(this.channel.id, { + message: stagedMessage.message, + in_reply_to_id: stagedMessage.inReplyTo?.id, + staged_id: stagedMessage.stagedId, + upload_ids: stagedMessage.uploads.map((upload) => upload.id), + thread_id: stagedMessage.threadId, + }) + .then(() => { + // TODO (martin) Scrolling!! + // this.scrollToBottom(); + }) + .catch((error) => { + this.#onSendError(stagedMessage.stagedId, error); + }) + .finally(() => { + if (this._selfDeleted) { + return; + } + this.sendingLoading = false; + this.#resetAfterSend(); + }); + } + + @action + editMessage() {} + // editMessage(chatMessage, newContent, uploads) {} + + @action + setReplyTo() {} + // setReplyTo(messageId) {} + + @action + setInReplyToMsg(inReplyMsg) { + this.replyToMsg = inReplyMsg; + } + + @action + cancelEditing() { + this.editingMessage = null; + } + + @action + editLastMessageRequested() {} + + @action + composerValueChanged() {} + // composerValueChanged(value, uploads, replyToMsg) {} + + #handleErrors(error) { + switch (error?.jqXHR?.status) { + case 429: + case 404: + popupAjaxError(error); + break; + default: + throw error; + } + } + + #onSendError(stagedId, error) { + const stagedMessage = + this.thread.messagesManager.findStagedMessage(stagedId); + if (stagedMessage) { + if (error.jqXHR?.responseJSON?.errors?.length) { + stagedMessage.error = error.jqXHR.responseJSON.errors[0]; + } else { + this.chat.markNetworkAsUnreliable(); + stagedMessage.error = "network_error"; + } + } + + this.#resetAfterSend(); + } + + #resetAfterSend() { + if (this._selfDeleted) { + return; + } + + this.replyToMsg = null; + this.editingMessage = null; + this.chatComposerPresenceManager.notifyState(this.channel.id, false); + this.appEvents.trigger("chat-composer:reply-to-set", null); + } } diff --git a/plugins/chat/assets/javascripts/discourse/lib/chat-messages-manager.js b/plugins/chat/assets/javascripts/discourse/lib/chat-messages-manager.js new file mode 100644 index 00000000000..0158698f339 --- /dev/null +++ b/plugins/chat/assets/javascripts/discourse/lib/chat-messages-manager.js @@ -0,0 +1,43 @@ +import { tracked } from "@glimmer/tracking"; +import { TrackedArray } from "@ember-compat/tracked-built-ins"; +import { setOwner } from "@ember/application"; + +export default class ChatMessagesManager { + @tracked messages = new TrackedArray(); + @tracked canLoadMoreFuture; + @tracked canLoadMorePast; + + constructor(owner) { + setOwner(this, owner); + } + + clearMessages() { + this.messages.clear(); + + this.canLoadMoreFuture = null; + this.canLoadMorePast = null; + } + + addMessages(messages = []) { + this.messages = this.messages + .concat(messages) + .uniqBy("id") + .sortBy("createdAt"); + } + + findMessage(messageId) { + return this.messages.find( + (message) => message.id === parseInt(messageId, 10) + ); + } + + removeMessage(message) { + return this.messages.removeObject(message); + } + + findStagedMessage(stagedMessageId) { + return this.messages.find( + (message) => message.staged && message.id === stagedMessageId + ); + } +} diff --git a/plugins/chat/assets/javascripts/discourse/lib/chat-threads-manager.js b/plugins/chat/assets/javascripts/discourse/lib/chat-threads-manager.js index a19aa7492e5..0fdd45d1bcf 100644 --- a/plugins/chat/assets/javascripts/discourse/lib/chat-threads-manager.js +++ b/plugins/chat/assets/javascripts/discourse/lib/chat-threads-manager.js @@ -43,7 +43,7 @@ export default class ChatThreadsManager { let model = this.#findStale(threadObject.id); if (!model) { - model = ChatThread.create(threadObject); + model = new ChatThread(threadObject); this.#cache(model); } @@ -55,7 +55,6 @@ export default class ChatThreadsManager { .thread(channelId, threadId) .catch(popupAjaxError) .then((thread) => { - this.#cache(thread); return thread; }); } diff --git a/plugins/chat/assets/javascripts/discourse/models/chat-channel.js b/plugins/chat/assets/javascripts/discourse/models/chat-channel.js index e1eb2b58405..ec45c76b929 100644 --- a/plugins/chat/assets/javascripts/discourse/models/chat-channel.js +++ b/plugins/chat/assets/javascripts/discourse/models/chat-channel.js @@ -6,8 +6,8 @@ import { escapeExpression } from "discourse/lib/utilities"; import { tracked } from "@glimmer/tracking"; import slugifyChannel from "discourse/plugins/chat/discourse/lib/slugify-channel"; import ChatThreadsManager from "discourse/plugins/chat/discourse/lib/chat-threads-manager"; +import ChatMessagesManager from "discourse/plugins/chat/discourse/lib/chat-messages-manager"; import { getOwner } from "discourse-common/lib/get-owner"; -import { TrackedArray } from "@ember-compat/tracked-built-ins"; export const CHATABLE_TYPES = { directMessageChannel: "DirectMessage", @@ -55,18 +55,24 @@ export default class ChatChannel extends RestModel { @tracked chatableType; @tracked status; @tracked activeThread; - @tracked messages = new TrackedArray(); @tracked lastMessageSentAt; @tracked canDeleteOthers; @tracked canDeleteSelf; @tracked canFlag; - @tracked canLoadMoreFuture; - @tracked canLoadMorePast; @tracked canModerate; @tracked userSilenced; @tracked draft; threadsManager = new ChatThreadsManager(getOwner(this)); + messagesManager = new ChatMessagesManager(getOwner(this)); + + get messages() { + return this.messagesManager.messages; + } + + set messages(messages) { + this.messagesManager.messages = messages; + } get escapedTitle() { return escapeExpression(this.title); @@ -126,46 +132,16 @@ export default class ChatChannel extends RestModel { this.canFlag = details.can_flag ?? false; this.canModerate = details.can_moderate ?? false; if (details.can_load_more_future !== undefined) { - this.canLoadMoreFuture = details.can_load_more_future; + this.messagesManager.canLoadMoreFuture = details.can_load_more_future; } if (details.can_load_more_past !== undefined) { - this.canLoadMorePast = details.can_load_more_past; + this.messagesManager.canLoadMorePast = details.can_load_more_past; } this.userSilenced = details.user_silenced ?? false; this.status = details.channel_status; this.channelMessageBusLastId = details.channel_message_bus_last_id; } - clearMessages() { - this.messages.clear(); - - this.canLoadMoreFuture = null; - this.canLoadMorePast = null; - } - - addMessages(messages = []) { - this.messages = this.messages - .concat(messages) - .uniqBy("id") - .sortBy("createdAt"); - } - - findMessage(messageId) { - return this.messages.find( - (message) => message.id === parseInt(messageId, 10) - ); - } - - removeMessage(message) { - return this.messages.removeObject(message); - } - - findStagedMessage(stagedMessageId) { - return this.messages.find( - (message) => message.staged && message.id === stagedMessageId - ); - } - canModifyMessages(user) { if (user.staff) { return !STAFF_READONLY_STATUSES.includes(this.status); diff --git a/plugins/chat/assets/javascripts/discourse/models/chat-thread.js b/plugins/chat/assets/javascripts/discourse/models/chat-thread.js index abe42551d0b..599bacccdfb 100644 --- a/plugins/chat/assets/javascripts/discourse/models/chat-thread.js +++ b/plugins/chat/assets/javascripts/discourse/models/chat-thread.js @@ -1,4 +1,5 @@ -import RestModel from "discourse/models/rest"; +import { getOwner } from "discourse-common/lib/get-owner"; +import ChatMessagesManager from "discourse/plugins/chat/discourse/lib/chat-messages-manager"; import User from "discourse/models/user"; import { escapeExpression } from "discourse/lib/utilities"; import { tracked } from "@glimmer/tracking"; @@ -10,22 +11,42 @@ export const THREAD_STATUSES = { archived: "archived", }; -export default class ChatThread extends RestModel { +export default class ChatThread { @tracked title; @tracked status; + messagesManager = new ChatMessagesManager(getOwner(this)); + + constructor(args = {}) { + this.title = args.title; + this.id = args.id; + this.status = args.status; + + this.originalMessageUser = this.#initUserModel(args.original_message_user); + + // TODO (martin) Not sure if ChatMessage is needed here, original_message + // only has a small subset of message stuff. + this.originalMessage = args.original_message; + this.originalMessage.user = this.originalMessageUser; + } + + get messages() { + return this.messagesManager.messages; + } + + set messages(messages) { + this.messagesManager.messages = messages; + } + get escapedTitle() { return escapeExpression(this.title); } -} -ChatThread.reopenClass({ - create(args) { - args = args || {}; - if (!args.original_message_user instanceof User) { - args.original_message_user = User.create(args.original_message_user); + #initUserModel(user) { + if (!user || user instanceof User) { + return user; } - args.original_message.user = args.original_message_user; - return this._super(args); - }, -}); + + return User.create(user); + } +} diff --git a/plugins/chat/assets/javascripts/discourse/routes/chat-channel.js b/plugins/chat/assets/javascripts/discourse/routes/chat-channel.js index 743c28c3440..f610f301c08 100644 --- a/plugins/chat/assets/javascripts/discourse/routes/chat-channel.js +++ b/plugins/chat/assets/javascripts/discourse/routes/chat-channel.js @@ -10,8 +10,7 @@ export default class ChatChannelRoute extends DiscourseRoute { @action willTransition(transition) { - this.chat.activeChannel.activeThread = null; - this.chatStateManager.closeSidePanel(); + this.#closeThread(); if (transition?.to?.name === "chat.channel.index") { const targetChannelId = transition?.to?.parent?.params?.channelId; @@ -19,7 +18,7 @@ export default class ChatChannelRoute extends DiscourseRoute { targetChannelId && parseInt(targetChannelId, 10) !== this.chat.activeChannel.id ) { - this.chat.activeChannel.clearMessages(); + this.chat.activeChannel.messagesManager.clearMessages(); } } @@ -29,4 +28,10 @@ export default class ChatChannelRoute extends DiscourseRoute { this.chat.updatePresence(); } } + + #closeThread() { + this.chat.activeChannel.activeThread?.messagesManager?.clearMessages(); + this.chat.activeChannel.activeThread = null; + this.chatStateManager.closeSidePanel(); + } } diff --git a/plugins/chat/assets/javascripts/discourse/services/chat-api.js b/plugins/chat/assets/javascripts/discourse/services/chat-api.js index 44ad75dc5c4..2f7f41f4a22 100644 --- a/plugins/chat/assets/javascripts/discourse/services/chat-api.js +++ b/plugins/chat/assets/javascripts/discourse/services/chat-api.js @@ -261,6 +261,10 @@ export default class ChatApi extends Service { if (data.direction) { args.direction = data.direction; } + + if (data.threadId) { + args.thread_id = data.threadId; + } } return ajax(path, { data: args });