diff --git a/plugins/chat/app/controllers/chat/chat_controller.rb b/plugins/chat/app/controllers/chat/chat_controller.rb
index 0a5ec268eb8..df19c534fa3 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
staged_id: params[:staged_id],
upload_ids: params[:upload_ids],
thread_id: params[:thread_id],
+ staged_thread_id: params[:staged_thread_id],
)
return render_json_error(chat_message_creator.error) if chat_message_creator.failed?
diff --git a/plugins/chat/app/services/chat/publisher.rb b/plugins/chat/app/services/chat/publisher.rb
index a14bc288dcd..10ec97971eb 100644
--- a/plugins/chat/app/services/chat/publisher.rb
+++ b/plugins/chat/app/services/chat/publisher.rb
@@ -14,7 +14,7 @@ module Chat
"#{root_message_bus_channel(chat_channel_id)}/thread/#{thread_id}"
end
- def self.calculate_publish_targets(channel, message)
+ def self.calculate_publish_targets(channel, message, staged_thread_id: nil)
return [root_message_bus_channel(channel.id)] if !allow_publish_to_thread?(channel)
if message.thread_om?
@@ -22,8 +22,10 @@ module Chat
root_message_bus_channel(channel.id),
thread_message_bus_channel(channel.id, message.thread_id),
]
- elsif message.thread_reply?
- [thread_message_bus_channel(channel.id, message.thread_id)]
+ elsif staged_thread_id || message.thread_reply?
+ targets = [thread_message_bus_channel(channel.id, message.thread_id)]
+ targets << thread_message_bus_channel(channel.id, staged_thread_id) if staged_thread_id
+ targets
else
[root_message_bus_channel(channel.id)]
end
@@ -33,12 +35,16 @@ module Chat
SiteSetting.enable_experimental_chat_threaded_discussions && channel.threading_enabled
end
- def self.publish_new!(chat_channel, chat_message, staged_id)
- message_bus_targets = calculate_publish_targets(chat_channel, chat_message)
+ def self.publish_new!(chat_channel, chat_message, staged_id, staged_thread_id: nil)
+ message_bus_targets =
+ calculate_publish_targets(chat_channel, chat_message, staged_thread_id: staged_thread_id)
publish_to_targets!(
message_bus_targets,
chat_channel,
- serialize_message_with_type(chat_message, :sent).merge(staged_id: staged_id),
+ serialize_message_with_type(chat_message, :sent).merge(
+ staged_id: staged_id,
+ staged_thread_id: staged_thread_id,
+ ),
)
# NOTE: This means that the read count is only updated in the client
@@ -70,8 +76,15 @@ module Chat
)
end
- def self.publish_thread_created!(chat_channel, chat_message)
- publish_to_channel!(chat_channel, serialize_message_with_type(chat_message, :thread_created))
+ def self.publish_thread_created!(chat_channel, chat_message, thread_id, staged_thread_id)
+ publish_to_channel!(
+ chat_channel,
+ serialize_message_with_type(
+ chat_message,
+ :thread_created,
+ { thread_id: thread_id, staged_thread_id: staged_thread_id },
+ ),
+ )
end
def self.publish_processed!(chat_message)
@@ -215,11 +228,12 @@ module Chat
end
end
- def self.serialize_message_with_type(chat_message, type)
+ def self.serialize_message_with_type(chat_message, type, options = {})
Chat::MessageSerializer
.new(chat_message, { scope: anonymous_guardian, root: :chat_message })
.as_json
.merge(type: type)
+ .merge(options)
end
def self.user_tracking_state_message_bus_channel(user_id)
diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-channel.js b/plugins/chat/assets/javascripts/discourse/components/chat-channel.js
index d02aae4fe48..64ce8abe0b5 100644
--- a/plugins/chat/assets/javascripts/discourse/components/chat-channel.js
+++ b/plugins/chat/assets/javascripts/discourse/components/chat-channel.js
@@ -1,5 +1,6 @@
import { capitalize } from "@ember/string";
import ChatMessage from "discourse/plugins/chat/discourse/models/chat-message";
+import ChatThread from "discourse/plugins/chat/discourse/models/chat-thread";
import Component from "@glimmer/component";
import { bind, debounce } from "discourse-common/utils/decorators";
import { action } from "@ember/object";
@@ -352,7 +353,13 @@ export default class ChatLivePane extends Component {
messageData.newest = true;
}
- messages.push(ChatMessage.create(channel, messageData));
+ const message = ChatMessage.create(channel, messageData);
+
+ if (messageData.thread_id) {
+ message.thread = new ChatThread(channel, { id: messageData.thread_id });
+ }
+
+ messages.push(message);
});
return [messages, results.meta];
@@ -548,7 +555,11 @@ export default class ChatLivePane extends Component {
}
if (data.chat_message.user.id === this.currentUser.id && data.staged_id) {
- const stagedMessage = handleStagedMessage(this.#messagesManager, data);
+ const stagedMessage = handleStagedMessage(
+ this.args.channel,
+ this.#messagesManager,
+ data
+ );
if (stagedMessage) {
return;
}
diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-composer.hbs b/plugins/chat/assets/javascripts/discourse/components/chat-composer.hbs
index f81add49728..6ef7b22d5bc 100644
--- a/plugins/chat/assets/javascripts/discourse/components/chat-composer.hbs
+++ b/plugins/chat/assets/javascripts/discourse/components/chat-composer.hbs
@@ -25,7 +25,7 @@
{{did-update this.didUpdateMessage this.currentMessage}}
{{did-update this.didUpdateInReplyTo this.currentMessage.inReplyTo}}
{{did-insert this.setupAppEvents}}
- {{will-destroy this.teardownAppEvents}}
+ {{will-destroy this.teardown}}
{{will-destroy this.cancelPersistDraft}}
>
diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-composer.js b/plugins/chat/assets/javascripts/discourse/components/chat-composer.js
index d8c9c1d6b04..b0a43a662a3 100644
--- a/plugins/chat/assets/javascripts/discourse/components/chat-composer.js
+++ b/plugins/chat/assets/javascripts/discourse/components/chat-composer.js
@@ -17,6 +17,8 @@ import I18n from "I18n";
import { translations } from "pretty-text/emoji/data";
import { setupHashtagAutocomplete } from "discourse/lib/hashtag-autocomplete";
import { isEmpty, isPresent } from "@ember/utils";
+import ChatMessage from "discourse/plugins/chat/discourse/models/chat-message";
+import { Promise } from "rsvp";
export default class ChatComposer extends Component {
@service capabilities;
@@ -39,7 +41,10 @@ export default class ChatComposer extends Component {
}
get shouldRenderMessageDetails() {
- return this.currentMessage?.editing || this.currentMessage?.inReplyTo;
+ return (
+ this.currentMessage?.editing ||
+ (this.context === "channel" && this.currentMessage?.inReplyTo)
+ );
}
get inlineButtons() {
@@ -70,6 +75,18 @@ export default class ChatComposer extends Component {
);
}
+ @action
+ sendMessage(raw) {
+ const message = ChatMessage.createDraftMessage(this.args.channel, {
+ user: this.currentUser,
+ message: raw,
+ });
+
+ this.args.onSendMessage(message);
+
+ return Promise.resolve();
+ }
+
@action
persistDraft() {}
@@ -133,13 +150,14 @@ export default class ChatComposer extends Component {
}
@action
- teardownAppEvents() {
+ teardown() {
this.appEvents.off("chat:modify-selection", this, "modifySelection");
this.appEvents.off(
"chat:open-insert-link-modal",
this,
"openInsertLinkModal"
);
+ this.pane.sending = false;
}
@action
@@ -221,7 +239,7 @@ export default class ChatComposer extends Component {
}
reportReplyingPresence() {
- if (!this.args.channel) {
+ if (!this.args.channel || !this.currentMessage) {
return;
}
@@ -305,9 +323,13 @@ export default class ChatComposer extends Component {
!this.hasContent &&
!this.currentMessage.editing
) {
- const editableMessage = this.pane?.lastCurrentUserMessage;
- if (editableMessage) {
- this.composer.editMessage(editableMessage);
+ if (event.shiftKey) {
+ this.composer.replyTo(this.pane?.lastMessage);
+ } else {
+ const editableMessage = this.pane?.lastCurrentUserMessage;
+ if (editableMessage) {
+ this.composer.editMessage(editableMessage);
+ }
}
}
diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-drawer/thread.hbs b/plugins/chat/assets/javascripts/discourse/components/chat-drawer/thread.hbs
index b448a84a32f..4aa95ff443b 100644
--- a/plugins/chat/assets/javascripts/discourse/components/chat-drawer/thread.hbs
+++ b/plugins/chat/assets/javascripts/discourse/components/chat-drawer/thread.hbs
@@ -24,7 +24,7 @@
{{did-update this.fetchChannelAndThread @params.threadId}}
>
{{#if this.chat.activeChannel.activeThread}}
-
+
{{/if}}
{{/if}}
\ No newline at end of file
diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-message-actions-desktop.hbs b/plugins/chat/assets/javascripts/discourse/components/chat-message-actions-desktop.hbs
index e33f7a03852..2de4491b18f 100644
--- a/plugins/chat/assets/javascripts/discourse/components/chat-message-actions-desktop.hbs
+++ b/plugins/chat/assets/javascripts/discourse/components/chat-message-actions-desktop.hbs
@@ -1,13 +1,16 @@
{{#if (and this.site.desktopView this.chat.activeMessage.model.id)}}
- {{#if this.chatStateManager.isFullPageActive}}
+ {{#if this.shouldRenderFavoriteReactions}}
{{#each
this.messageInteractor.emojiReactions
key="emoji"
@@ -50,7 +53,12 @@
/>
{{/if}}
- {{#if this.messageInteractor.secondaryButtons.length}}
+ {{#if
+ (and
+ this.messageInteractor.message
+ this.messageInteractor.secondaryButtons.length
+ )
+ }}
{
@@ -45,6 +54,10 @@ export default class ChatMessageActionsDesktop extends Component {
this.context
);
+ const viewport = messageContainer.closest(".popper-viewport");
+ this.size =
+ viewport.clientWidth < REDUCED_WIDTH_THRESHOLD ? REDUCED : FULL;
+
if (!messageContainer) {
return;
}
@@ -57,7 +70,7 @@ export default class ChatMessageActionsDesktop extends Component {
name: "flip",
enabled: true,
options: {
- boundary: messageContainer.closest(".popper-viewport"),
+ boundary: viewport,
fallbackPlacements: ["bottom-end"],
},
},
@@ -73,7 +86,7 @@ export default class ChatMessageActionsDesktop extends Component {
}
@action
- teardownPopper() {
+ teardown() {
this.popper?.destroy();
}
}
diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-message-actions-mobile.hbs b/plugins/chat/assets/javascripts/discourse/components/chat-message-actions-mobile.hbs
index c91d4d181f3..b232493a669 100644
--- a/plugins/chat/assets/javascripts/discourse/components/chat-message-actions-mobile.hbs
+++ b/plugins/chat/assets/javascripts/discourse/components/chat-message-actions-mobile.hbs
@@ -64,6 +64,7 @@
@icon="discourse-emojis"
@title="chat.react"
@forwardEvent={{true}}
+ data-id="react"
/>
{{/if}}
@@ -71,6 +72,7 @@
@@ -82,6 +84,7 @@
@action={{action this.actAndCloseMenu "reply"}}
@icon="reply"
@title="chat.reply"
+ data-id="reply"
/>
{{/if}}
diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-message-in-reply-to-indicator.js b/plugins/chat/assets/javascripts/discourse/components/chat-message-in-reply-to-indicator.js
index 4601fbcc37f..75f2a714c41 100644
--- a/plugins/chat/assets/javascripts/discourse/components/chat-message-in-reply-to-indicator.js
+++ b/plugins/chat/assets/javascripts/discourse/components/chat-message-in-reply-to-indicator.js
@@ -16,7 +16,7 @@ export default class ChatMessageInReplyToIndicator extends Component {
if (this.hasThread) {
return [
...this.args.message.channel.routeModels,
- this.args.message.threadId,
+ this.args.message.thread.id,
];
} else {
return [
@@ -29,7 +29,7 @@ export default class ChatMessageInReplyToIndicator extends Component {
get hasThread() {
return (
this.args.message?.channel?.threadingEnabled &&
- this.args.message?.threadId
+ this.args.message?.thread?.id
);
}
}
diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-message-thread-indicator.hbs b/plugins/chat/assets/javascripts/discourse/components/chat-message-thread-indicator.hbs
index de986b952ac..2412c1192b4 100644
--- a/plugins/chat/assets/javascripts/discourse/components/chat-message-thread-indicator.hbs
+++ b/plugins/chat/assets/javascripts/discourse/components/chat-message-thread-indicator.hbs
@@ -1,6 +1,6 @@
diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-message.hbs b/plugins/chat/assets/javascripts/discourse/components/chat-message.hbs
index 53a5baab681..b122566906b 100644
--- a/plugins/chat/assets/javascripts/discourse/components/chat-message.hbs
+++ b/plugins/chat/assets/javascripts/discourse/components/chat-message.hbs
@@ -22,7 +22,7 @@
(if @message.highlighted "highlighted")
}}
data-id={{@message.id}}
- data-thread-id={{@message.threadId}}
+ data-thread-id={{@message.thread.id}}
{{chat/track-message
(hash
didEnterViewport=(fn @messageDidEnterViewport @message)
diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-message.js b/plugins/chat/assets/javascripts/discourse/components/chat-message.js
index 4d86113e891..a399c0db713 100644
--- a/plugins/chat/assets/javascripts/discourse/components/chat-message.js
+++ b/plugins/chat/assets/javascripts/discourse/components/chat-message.js
@@ -282,15 +282,15 @@ export default class ChatMessage extends Component {
}
get threadingEnabled() {
- return this.args.channel?.threadingEnabled && this.args.message?.threadId;
+ return this.args.channel?.threadingEnabled && !!this.args.message?.thread;
}
get showThreadIndicator() {
return (
this.args.context !== MESSAGE_CONTEXT_THREAD &&
this.threadingEnabled &&
- this.args.message?.threadId !==
- this.args.message?.previousMessage?.threadId
+ this.args.message?.thread &&
+ this.args.message?.threadReplyCount > 0
);
}
@@ -352,7 +352,7 @@ export default class ChatMessage extends Component {
inviteMentioned() {
const userIds = this.mentionWarning.without_membership.mapBy("id");
- ajax(`/chat/${this.args.message.channelId}/invite`, {
+ ajax(`/chat/${this.args.message.channel.id}/invite`, {
method: "PUT",
data: { user_ids: userIds, chat_message_id: this.args.message.id },
}).then(() => {
diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-thread.hbs b/plugins/chat/assets/javascripts/discourse/components/chat-thread.hbs
index e2bae197268..5be4543e540 100644
--- a/plugins/chat/assets/javascripts/discourse/components/chat-thread.hbs
+++ b/plugins/chat/assets/javascripts/discourse/components/chat-thread.hbs
@@ -3,9 +3,9 @@
data-id={{this.thread.id}}
{{did-insert this.setUploadDropZone}}
{{did-insert this.subscribeToUpdates}}
- {{did-insert this.loadMessages}}
{{did-update this.subscribeToUpdates this.thread.id}}
- {{did-update this.loadMessages this.thread.id}}
+ {{did-insert this.loadMessages}}
+ {{did-update this.loadMessages this.thread}}
{{did-insert this.setupMessage}}
{{will-destroy this.unsubscribeFromUpdates}}
>
@@ -42,7 +42,7 @@
@context="thread"
/>
{{/each}}
- {{#if (or this.loading this.loadingMoreFuture)}}
+ {{#if this.loading}}
{{/if}}
diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-thread.js b/plugins/chat/assets/javascripts/discourse/components/chat-thread.js
index baf84060e22..2b5d6e6e74c 100644
--- a/plugins/chat/assets/javascripts/discourse/components/chat-thread.js
+++ b/plugins/chat/assets/javascripts/discourse/components/chat-thread.js
@@ -8,6 +8,7 @@ import { bind, debounce } from "discourse-common/utils/decorators";
import { inject as service } from "@ember/service";
import { schedule } from "@ember/runloop";
import discourseLater from "discourse-common/lib/later";
+import { resetIdle } from "discourse/lib/desktop-notifications";
const PAGE_SIZE = 50;
@@ -25,22 +26,16 @@ export default class ChatThreadPanel extends Component {
@service capabilities;
@tracked loading;
- @tracked loadingMorePast;
@tracked uploadDropZone;
scrollable = null;
get thread() {
- return this.channel.activeThread;
+ return this.args.thread;
}
get channel() {
- return this.chat.activeChannel;
- }
-
- @action
- subscribeToUpdates() {
- this.chatChannelThreadPaneSubscriptionsManager.subscribe(this.thread);
+ return this.thread?.channel;
}
@action
@@ -56,6 +51,11 @@ export default class ChatThreadPanel extends Component {
);
}
+ @action
+ subscribeToUpdates() {
+ this.chatChannelThreadPaneSubscriptionsManager.subscribe(this.thread);
+ }
+
@action
unsubscribeFromUpdates() {
this.chatChannelThreadPaneSubscriptionsManager.unsubscribe();
@@ -69,17 +69,7 @@ export default class ChatThreadPanel extends Component {
@action
loadMessages() {
this.thread.messagesManager.clearMessages();
-
- if (this.args.targetMessageId) {
- this.requestedTargetMessageId = parseInt(this.args.targetMessageId, 10);
- }
-
- // TODO (martin) Loading/scrolling to selected message
- // this.highlightOrFetchMessage(this.requestedTargetMessageId);
- // if (this.requestedTargetMessageId) {
- // } else {
this.fetchMessages();
- // }
}
@action
@@ -97,21 +87,14 @@ export default class ChatThreadPanel extends Component {
return Promise.resolve();
}
- this.loadingMorePast = true;
+ if (this.thread.staged) {
+ this.thread.messagesManager.addMessages([this.thread.originalMessage]);
+ return Promise.resolve();
+ }
+
this.loading = true;
- 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;
-
+ const findArgs = { pageSize: PAGE_SIZE, threadId: this.thread.id };
return this.chatApi
.messages(this.channel.id, findArgs)
.then((results) => {
@@ -123,23 +106,13 @@ export default class ChatThreadPanel extends Component {
);
}
- const [messages, meta] = this.afterFetchCallback(this.channel, results);
+ const [messages, meta] = this.afterFetchCallback(
+ this.channel,
+ this.thread,
+ results
+ );
this.thread.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);
- // }
- //
this.markThreadAsRead();
})
.catch(this.#handleErrors)
@@ -148,16 +121,12 @@ export default class ChatThreadPanel extends Component {
return;
}
- this.requestedTargetMessageId = null;
this.loading = false;
- this.loadingMorePast = false;
-
- // this.fillPaneAttempt();
});
}
@bind
- afterFetchCallback(channel, results) {
+ afterFetchCallback(channel, thread, results) {
const messages = [];
results.chat_messages.forEach((messageData) => {
@@ -170,13 +139,10 @@ export default class ChatThreadPanel extends Component {
);
}
- if (this.requestedTargetMessageId === messageData.id) {
- messageData.expanded = !messageData.hidden;
- } else {
- messageData.expanded = !(messageData.hidden || messageData.deleted_at);
- }
-
- messages.push(ChatMessage.create(channel, messageData));
+ messageData.expanded = !(messageData.hidden || messageData.deleted_at);
+ const message = ChatMessage.create(channel, messageData);
+ message.thread = thread;
+ messages.push(message);
});
return [messages, results.meta];
@@ -191,6 +157,8 @@ export default class ChatThreadPanel extends Component {
@action
onSendMessage(message) {
+ resetIdle();
+
if (message.editing) {
this.#sendEditMessage(message);
} else {
@@ -200,7 +168,7 @@ export default class ChatThreadPanel extends Component {
@action
resetComposer() {
- this.chatChannelThreadComposer.reset(this.channel);
+ this.chatChannelThreadComposer.reset(this.channel, this.thread);
}
@action
@@ -209,34 +177,27 @@ export default class ChatThreadPanel extends Component {
}
#sendNewMessage(message) {
- // TODO (martin) For desktop notifications
- // resetIdle()
+ message.thread = this.thread;
+
if (this.chatChannelThreadPane.sending) {
return;
}
this.chatChannelThreadPane.sending = true;
- // TODO (martin) Handling case when channel is not followed???? IDK if we
- // even let people send messages in threads without this, seems weird.
-
this.thread.stageMessage(message);
const stagedMessage = message;
this.resetComposer();
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.id,
upload_ids: stagedMessage.uploads.map((upload) => upload.id),
- thread_id: stagedMessage.threadId,
+ thread_id: this.thread.staged ? null : stagedMessage.thread.id,
+ staged_thread_id: this.thread.staged ? stagedMessage.thread.id : null,
})
.then(() => {
this.scrollToBottom();
@@ -264,7 +225,7 @@ export default class ChatThreadPanel extends Component {
this.resetComposer();
return this.chatApi
- .editMessage(message.channelId, message.id, data)
+ .editMessage(message.channel.id, message.id, data)
.catch(popupAjaxError)
.finally(() => {
this.chatChannelThreadPane.sending = false;
@@ -280,9 +241,9 @@ export default class ChatThreadPanel extends Component {
return;
}
- this.scrollable.scrollTop = -1;
+ this.scrollable.scrollTop = this.scrollable.scrollHeight + 1;
this.forceRendering(() => {
- this.scrollable.scrollTop = 0;
+ this.scrollable.scrollTop = this.scrollable.scrollHeight;
});
}
@@ -319,7 +280,6 @@ export default class ChatThreadPanel extends Component {
@action
resendStagedMessage() {}
- // resendStagedMessage(stagedMessage) {}
@action
messageDidEnterViewport(message) {
diff --git a/plugins/chat/assets/javascripts/discourse/components/chat/composer/channel.js b/plugins/chat/assets/javascripts/discourse/components/chat/composer/channel.js
index 1116a0155cb..ab17c22186d 100644
--- a/plugins/chat/assets/javascripts/discourse/components/chat/composer/channel.js
+++ b/plugins/chat/assets/javascripts/discourse/components/chat/composer/channel.js
@@ -3,8 +3,6 @@ import { inject as service } from "@ember/service";
import I18n from "I18n";
import discourseDebounce from "discourse-common/lib/debounce";
import { action } from "@ember/object";
-import ChatMessage from "discourse/plugins/chat/discourse/models/chat-message";
-import { Promise } from "rsvp";
export default class ChatComposerChannel extends ChatComposer {
@service("chat-channel-composer") composer;
@@ -14,18 +12,6 @@ export default class ChatComposerChannel extends ChatComposer {
composerId = "channel-composer";
- @action
- sendMessage(raw) {
- const message = ChatMessage.createDraftMessage(this.args.channel, {
- user: this.currentUser,
- message: raw,
- });
-
- this.args.onSendMessage(message);
-
- return Promise.resolve();
- }
-
@action
persistDraft() {
if (this.args.channel?.isDraft) {
diff --git a/plugins/chat/assets/javascripts/discourse/components/chat/composer/thread.js b/plugins/chat/assets/javascripts/discourse/components/chat/composer/thread.js
index ea46f97046b..e1a1086035e 100644
--- a/plugins/chat/assets/javascripts/discourse/components/chat/composer/thread.js
+++ b/plugins/chat/assets/javascripts/discourse/components/chat/composer/thread.js
@@ -1,32 +1,32 @@
import ChatComposer from "../../chat-composer";
import { inject as service } from "@ember/service";
import I18n from "I18n";
-import ChatMessage from "discourse/plugins/chat/discourse/models/chat-message";
-import { Promise } from "rsvp";
import { action } from "@ember/object";
export default class ChatComposerThread extends ChatComposer {
@service("chat-channel-thread-composer") composer;
+ @service("chat-channel-composer") channelComposer;
@service("chat-channel-thread-pane") pane;
+ @service router;
context = "thread";
composerId = "thread-composer";
- @action
- sendMessage(raw) {
- const message = ChatMessage.createDraftMessage(this.args.channel, {
- user: this.currentUser,
- message: raw,
- thread_id: this.args.channel.activeThread.id,
- });
-
- this.args.onSendMessage(message);
-
- return Promise.resolve();
- }
-
get placeholder() {
return I18n.t("chat.placeholder_thread");
}
+
+ @action
+ onKeyDown(event) {
+ if (event.key === "Escape") {
+ this.router.transitionTo(
+ "chat.channel",
+ ...this.args.channel.routeModels
+ );
+ return;
+ }
+
+ super.onKeyDown(event);
+ }
}
diff --git a/plugins/chat/assets/javascripts/discourse/helpers/format-chat-date.js b/plugins/chat/assets/javascripts/discourse/helpers/format-chat-date.js
index cd68b634d5c..0281c252480 100644
--- a/plugins/chat/assets/javascripts/discourse/helpers/format-chat-date.js
+++ b/plugins/chat/assets/javascripts/discourse/helpers/format-chat-date.js
@@ -17,12 +17,12 @@ registerUnbound("format-chat-date", function (message, mode) {
if (message.staged) {
return htmlSafe(
- `${display}`
+ `${display}`
);
} else {
const url = getURL(`/chat/c/-/${message.channel.id}/${message.id}`);
return htmlSafe(
- `${display}`
+ `${display}`
);
}
});
diff --git a/plugins/chat/assets/javascripts/discourse/lib/chat-message-interactor.js b/plugins/chat/assets/javascripts/discourse/lib/chat-message-interactor.js
index d65a3e532ae..239119787fe 100644
--- a/plugins/chat/assets/javascripts/discourse/lib/chat-message-interactor.js
+++ b/plugins/chat/assets/javascripts/discourse/lib/chat-message-interactor.js
@@ -229,8 +229,8 @@ export default class ChatMessageInteractor {
copyLink() {
const { protocol, host } = window.location;
- const channelId = this.message.channelId;
- const threadId = this.message.threadId;
+ const channelId = this.message.channel.id;
+ const threadId = this.message.thread?.id;
let url;
if (threadId) {
@@ -276,7 +276,7 @@ export default class ChatMessageInteractor {
return this.chatApi
.publishReaction(
- this.message.channelId,
+ this.message.channel.id,
this.message.id,
emoji,
reactAction
@@ -329,21 +329,21 @@ export default class ChatMessageInteractor {
@action
delete() {
return this.chatApi
- .trashMessage(this.message.channelId, this.message.id)
+ .trashMessage(this.message.channel.id, this.message.id)
.catch(popupAjaxError);
}
@action
restore() {
return this.chatApi
- .restoreMessage(this.message.channelId, this.message.id)
+ .restoreMessage(this.message.channel.id, this.message.id)
.catch(popupAjaxError);
}
@action
rebake() {
return this.chatApi
- .rebakeMessage(this.message.channelId, this.message.id)
+ .rebakeMessage(this.message.channel.id, this.message.id)
.catch(popupAjaxError);
}
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 a83a34c87c1..fb806415c9b 100644
--- a/plugins/chat/assets/javascripts/discourse/lib/chat-threads-manager.js
+++ b/plugins/chat/assets/javascripts/discourse/lib/chat-threads-manager.js
@@ -4,7 +4,6 @@ import Promise from "rsvp";
import ChatThread from "discourse/plugins/chat/discourse/models/chat-thread";
import { tracked } from "@glimmer/tracking";
import { TrackedObject } from "@ember-compat/tracked-built-ins";
-import { popupAjaxError } from "discourse/lib/ajax-error";
/*
The ChatThreadsManager is responsible for managing the loaded chat threads
@@ -39,11 +38,16 @@ export default class ChatThreadsManager {
return Object.values(this._cached);
}
- store(threadObject) {
+ store(channel, threadObject) {
let model = this.#findStale(threadObject.id);
if (!model) {
- model = new ChatThread(threadObject);
+ if (threadObject instanceof ChatThread) {
+ model = threadObject;
+ } else {
+ model = new ChatThread(channel, threadObject);
+ }
+
this.#cache(model);
}
@@ -59,12 +63,7 @@ export default class ChatThreadsManager {
}
async #find(channelId, threadId) {
- return this.chatApi
- .thread(channelId, threadId)
- .catch(popupAjaxError)
- .then((thread) => {
- return thread;
- });
+ return this.chatApi.thread(channelId, threadId);
}
#cache(thread) {
diff --git a/plugins/chat/assets/javascripts/discourse/models/chat-channel.js b/plugins/chat/assets/javascripts/discourse/models/chat-channel.js
index 8bdc0d26180..c67862f1f4f 100644
--- a/plugins/chat/assets/javascripts/discourse/models/chat-channel.js
+++ b/plugins/chat/assets/javascripts/discourse/models/chat-channel.js
@@ -9,6 +9,7 @@ import ChatThreadsManager from "discourse/plugins/chat/discourse/lib/chat-thread
import ChatMessagesManager from "discourse/plugins/chat/discourse/lib/chat-messages-manager";
import { getOwner } from "discourse-common/lib/get-owner";
import guid from "pretty-text/guid";
+import ChatThread from "discourse/plugins/chat/discourse/models/chat-thread";
export const CHATABLE_TYPES = {
directMessageChannel: "DirectMessage",
@@ -71,6 +72,10 @@ export default class ChatChannel extends RestModel {
return this.messages.findIndex((m) => m.id === message.id);
}
+ findMessage(id) {
+ return this.messagesManager.findMessage(id);
+ }
+
get messages() {
return this.messagesManager.messages;
}
@@ -155,6 +160,23 @@ export default class ChatChannel extends RestModel {
this.channelMessageBusLastId = details.channel_message_bus_last_id;
}
+ createStagedThread(message) {
+ const clonedMessage = message.duplicate();
+
+ const thread = new ChatThread(this, {
+ id: `staged-thread-${message.channel.id}-${message.id}`,
+ original_message: message,
+ staged: true,
+ created_at: moment.utc().format(),
+ });
+
+ clonedMessage.thread = thread;
+ this.threadsManager.store(this, thread);
+ thread.messagesManager.addMessages([clonedMessage]);
+
+ return thread;
+ }
+
stageMessage(message) {
message.id = guid();
message.staged = true;
@@ -163,11 +185,6 @@ export default class ChatChannel extends RestModel {
message.cook();
if (message.inReplyTo) {
- if (!message.inReplyTo.threadId) {
- message.inReplyTo.threadId = guid();
- message.inReplyTo.threadReplyCount = 1;
- }
-
if (!this.threadingEnabled) {
this.messagesManager.addMessages([message]);
}
diff --git a/plugins/chat/assets/javascripts/discourse/models/chat-message.js b/plugins/chat/assets/javascripts/discourse/models/chat-message.js
index 5c4355dcf76..23a64dceebd 100644
--- a/plugins/chat/assets/javascripts/discourse/models/chat-message.js
+++ b/plugins/chat/assets/javascripts/discourse/models/chat-message.js
@@ -31,8 +31,6 @@ export default class ChatMessage {
@tracked deletedAt;
@tracked uploads;
@tracked excerpt;
- @tracked threadId;
- @tracked threadReplyCount;
@tracked reactions;
@tracked reviewableId;
@tracked user;
@@ -50,11 +48,12 @@ export default class ChatMessage {
@tracked highlighted = false;
@tracked firstOfResults = false;
@tracked message;
-
+ @tracked thread;
+ @tracked threadReplyCount;
@tracked _cooked;
constructor(channel, args = {}) {
- this.channel = channel;
+ // when modifying constructor, be sure to update duplicate function accordingly
this.id = args.id;
this.newest = args.newest;
this.firstOfResults = args.firstOfResults;
@@ -62,23 +61,23 @@ export default class ChatMessage {
this.edited = args.edited;
this.availableFlags = args.availableFlags || args.available_flags;
this.hidden = args.hidden;
- this.threadId = args.threadId || args.thread_id;
this.threadReplyCount = args.threadReplyCount || args.thread_reply_count;
- this.channelId = args.channelId || args.chat_channel_id;
this.chatWebhookEvent = args.chatWebhookEvent || args.chat_webhook_event;
this.createdAt = args.createdAt || args.created_at;
this.deletedAt = args.deletedAt || args.deleted_at;
this.excerpt = args.excerpt;
this.reviewableId = args.reviewableId || args.reviewable_id;
this.userFlagStatus = args.userFlagStatus || args.user_flag_status;
+ this.draft = args.draft;
+ this.message = args.message || "";
+ this._cooked = args.cooked || "";
+ this.thread = args.thread;
this.inReplyTo =
args.inReplyTo ||
(args.in_reply_to || args.replyToMsg
? ChatMessage.create(channel, args.in_reply_to || args.replyToMsg)
: null);
- this.draft = args.draft;
- this.message = args.message || "";
- this._cooked = args.cooked || "";
+ this.channel = channel;
this.reactions = this.#initChatMessageReactionModel(
args.id,
args.reactions
@@ -88,6 +87,39 @@ export default class ChatMessage {
this.bookmark = args.bookmark ? Bookmark.create(args.bookmark) : null;
}
+ duplicate() {
+ // This is important as a message can exist in the context of a channel or a thread
+ // The current strategy is to have a different message object in each cases to avoid
+ // side effects
+ const message = new ChatMessage(this.channel, {
+ id: this.id,
+ newest: this.newest,
+ staged: this.staged,
+ edited: this.edited,
+ availableFlags: this.availableFlags,
+ hidden: this.hidden,
+ threadReplyCount: this.threadReplyCount,
+ chatWebhookEvent: this.chatWebhookEvent,
+ createdAt: this.createdAt,
+ deletedAt: this.deletedAt,
+ excerpt: this.excerpt,
+ reviewableId: this.reviewableId,
+ userFlagStatus: this.userFlagStatus,
+ draft: this.draft,
+ message: this.message,
+ cooked: this.cooked,
+ });
+
+ message.thread = this.thread;
+ message.reactions = this.reactions;
+ message.user = this.user;
+ message.inReplyTo = this.inReplyTo;
+ message.bookmark = this.bookmark;
+ message.uploads = this.uploads;
+
+ return message;
+ }
+
get cooked() {
return this._cooked;
}
@@ -131,10 +163,6 @@ export default class ChatMessage {
}
}
- get threadRouteModels() {
- return [...this.channel.routeModels, this.threadId];
- }
-
get read() {
return this.channel.currentUserMembership?.last_read_message_id >= this.id;
}
diff --git a/plugins/chat/assets/javascripts/discourse/models/chat-thread.js b/plugins/chat/assets/javascripts/discourse/models/chat-thread.js
index e44b4406780..ee4926842cc 100644
--- a/plugins/chat/assets/javascripts/discourse/models/chat-thread.js
+++ b/plugins/chat/assets/javascripts/discourse/models/chat-thread.js
@@ -4,6 +4,7 @@ import User from "discourse/models/user";
import { escapeExpression } from "discourse/lib/utilities";
import { tracked } from "@glimmer/tracking";
import guid from "pretty-text/guid";
+import ChatMessage from "discourse/plugins/chat/discourse/models/chat-message";
export const THREAD_STATUSES = {
open: "open",
@@ -13,20 +14,25 @@ export const THREAD_STATUSES = {
};
export default class ChatThread {
+ @tracked id;
@tracked title;
@tracked status;
+ @tracked draft;
+ @tracked staged;
+ @tracked channel;
+ @tracked originalMessage;
+ @tracked threadMessageBusLastId;
messagesManager = new ChatMessagesManager(getOwner(this));
- constructor(args = {}) {
+ constructor(channel, args = {}) {
this.title = args.title;
this.id = args.id;
- this.channelId = args.channel_id;
+ this.channel = channel;
this.status = args.status;
-
- this.originalMessageUser = this.#initUserModel(args.original_message_user);
- this.originalMessage = args.original_message;
- this.originalMessage.user = this.originalMessageUser;
+ this.draft = args.draft;
+ this.staged = args.staged;
+ this.originalMessage = ChatMessage.create(channel, args.original_message);
}
stageMessage(message) {
@@ -39,6 +45,10 @@ export default class ChatThread {
this.messagesManager.addMessages([message]);
}
+ get routeModels() {
+ return [...this.channel.routeModels, this.id];
+ }
+
get messages() {
return this.messagesManager.messages;
}
diff --git a/plugins/chat/assets/javascripts/discourse/routes/chat-channel-thread.js b/plugins/chat/assets/javascripts/discourse/routes/chat-channel-thread.js
index 04151d5250f..54ce405ee3b 100644
--- a/plugins/chat/assets/javascripts/discourse/routes/chat-channel-thread.js
+++ b/plugins/chat/assets/javascripts/discourse/routes/chat-channel-thread.js
@@ -5,24 +5,57 @@ export default class ChatChannelThread extends DiscourseRoute {
@service router;
@service chatStateManager;
@service chat;
+ @service chatStagedThreadMapping;
+ @service chatChannelThreadPane;
- async model(params) {
+ model(params, transition) {
const channel = this.modelFor("chat.channel");
- return channel.threadsManager.find(channel.id, params.threadId);
+
+ return channel.threadsManager
+ .find(channel.id, params.threadId)
+ .catch(() => {
+ transition.abort();
+ this.router.transitionTo("chat.channel", ...channel.routeModels);
+ return;
+ });
}
deactivate() {
- this.#closeThread();
+ this.chatChannelThreadPane.close();
}
- #closeThread() {
- this.chat.activeChannel.activeThread?.messagesManager?.clearMessages();
- this.chat.activeChannel.activeThread = null;
- this.chatStateManager.closeSidePanel();
+ beforeModel(transition) {
+ const channel = this.modelFor("chat.channel");
+
+ if (!channel.threadingEnabled) {
+ transition.abort();
+ this.router.transitionTo("chat.channel", ...channel.routeModels);
+ return;
+ }
+
+ // This is a very special logic to attempt to reconciliate a staged thread id
+ // it happens after creating a new thread and having a temp ID in the URL
+ // if users presses reload at this moment, we would have a 404
+ // replacing the ID in the URL sooner would also cause a reload
+ const params = this.paramsFor("chat.channel.thread");
+ const threadId = params.threadId;
+
+ if (threadId?.startsWith("staged-thread-")) {
+ const mapping = this.chatStagedThreadMapping.getMapping();
+
+ if (mapping[threadId]) {
+ transition.abort();
+
+ this.router.transitionTo(
+ "chat.channel.thread",
+ ...[...channel.routeModels, mapping[threadId]]
+ );
+ return;
+ }
+ }
}
afterModel(model) {
- this.chat.activeChannel.activeThread = model;
- this.chatStateManager.openSidePanel();
+ this.chatChannelThreadPane.open(model);
}
}
diff --git a/plugins/chat/assets/javascripts/discourse/services/chat-api.js b/plugins/chat/assets/javascripts/discourse/services/chat-api.js
index d28d92663fc..6f15734b4b6 100644
--- a/plugins/chat/assets/javascripts/discourse/services/chat-api.js
+++ b/plugins/chat/assets/javascripts/discourse/services/chat-api.js
@@ -40,7 +40,11 @@ export default class ChatApi extends Service {
*/
thread(channelId, threadId) {
return this.#getRequest(`/channels/${channelId}/threads/${threadId}`).then(
- (result) => this.chat.activeChannel.threadsManager.store(result.thread)
+ (result) =>
+ this.chat.activeChannel.threadsManager.store(
+ this.chat.activeChannel,
+ result.thread
+ )
);
}
diff --git a/plugins/chat/assets/javascripts/discourse/services/chat-channel-composer.js b/plugins/chat/assets/javascripts/discourse/services/chat-channel-composer.js
index 6afa1a96a76..58a580bce2c 100644
--- a/plugins/chat/assets/javascripts/discourse/services/chat-channel-composer.js
+++ b/plugins/chat/assets/javascripts/discourse/services/chat-channel-composer.js
@@ -1,60 +1,41 @@
-import { tracked } from "@glimmer/tracking";
-import Service, { inject as service } from "@ember/service";
+import { inject as service } from "@ember/service";
import { action } from "@ember/object";
-import ChatMessage from "discourse/plugins/chat/discourse/models/chat-message";
+import ChatComposer from "./chat-composer";
+import { next } from "@ember/runloop";
-export default class ChatChannelComposer extends Service {
+export default class ChatChannelComposer extends ChatComposer {
@service chat;
- @service chatApi;
- @service chatComposerPresenceManager;
- @service currentUser;
-
- @tracked _message;
-
- @action
- cancel() {
- if (this.message.editing) {
- this.reset();
- } else if (this.message.inReplyTo) {
- this.message.inReplyTo = null;
- }
- }
-
- @action
- reset(channel) {
- this.message = ChatMessage.createDraftMessage(channel, {
- user: this.currentUser,
- });
- }
-
- @action
- clear() {
- this.message.message = "";
- }
-
- @action
- editMessage(message) {
- this.chat.activeMessage = null;
- message.editing = true;
- this.message = message;
- }
-
- @action
- onCancelEditing() {
- this.reset();
- }
+ @service chatChannelThreadComposer;
+ @service router;
@action
replyTo(message) {
this.chat.activeMessage = null;
- this.message.inReplyTo = message;
+ const channel = message.channel;
+
+ if (
+ this.siteSettings.enable_experimental_chat_threaded_discussions &&
+ channel.threadingEnabled
+ ) {
+ let thread;
+ if (message.thread?.id) {
+ thread = message.thread;
+ } else {
+ thread = channel.createStagedThread(message);
+ message.thread = thread;
+ }
+
+ this.router
+ .transitionTo("chat.channel.thread", ...thread.routeModels)
+ .finally(() => this._setReplyToAfterTransition(message));
+ } else {
+ this.message.inReplyTo = message;
+ }
}
- get message() {
- return this._message;
- }
-
- set message(message) {
- this._message = message;
+ _setReplyToAfterTransition(message) {
+ next(() => {
+ this.chatChannelThreadComposer.replyTo(message);
+ });
}
}
diff --git a/plugins/chat/assets/javascripts/discourse/services/chat-channel-pane-subscriptions-manager.js b/plugins/chat/assets/javascripts/discourse/services/chat-channel-pane-subscriptions-manager.js
index 50d73097e65..1d8a7fbe950 100644
--- a/plugins/chat/assets/javascripts/discourse/services/chat-channel-pane-subscriptions-manager.js
+++ b/plugins/chat/assets/javascripts/discourse/services/chat-channel-pane-subscriptions-manager.js
@@ -20,14 +20,6 @@ export default class ChatChannelPaneSubscriptionsManager extends ChatPaneBaseSub
return;
}
- handleThreadCreated(data) {
- const message = this.messagesManager.findMessage(data.chat_message.id);
- if (message) {
- message.threadId = data.chat_message.thread_id;
- message.threadReplyCount = 0;
- }
- }
-
handleThreadOriginalMessageUpdate(data) {
const message = this.messagesManager.findMessage(data.original_message_id);
if (message) {
diff --git a/plugins/chat/assets/javascripts/discourse/services/chat-channel-pane.js b/plugins/chat/assets/javascripts/discourse/services/chat-channel-pane.js
index 86b29e08290..5ed5bee8d1f 100644
--- a/plugins/chat/assets/javascripts/discourse/services/chat-channel-pane.js
+++ b/plugins/chat/assets/javascripts/discourse/services/chat-channel-pane.js
@@ -55,4 +55,8 @@ export default class ChatChannelPane extends Service {
return lastCurrentUserMessage;
}
+
+ get lastMessage() {
+ return this.chat.activeChannel.messages.lastObject;
+ }
}
diff --git a/plugins/chat/assets/javascripts/discourse/services/chat-channel-thread-composer.js b/plugins/chat/assets/javascripts/discourse/services/chat-channel-thread-composer.js
index 81749d4e725..6de4b84b6f8 100644
--- a/plugins/chat/assets/javascripts/discourse/services/chat-channel-thread-composer.js
+++ b/plugins/chat/assets/javascripts/discourse/services/chat-channel-thread-composer.js
@@ -1,13 +1,20 @@
-import ChatChannelComposer from "./chat-channel-composer";
+import ChatComposer from "./chat-composer";
import ChatMessage from "discourse/plugins/chat/discourse/models/chat-message";
import { action } from "@ember/object";
-export default class extends ChatChannelComposer {
+export default class ChatChannelThreadComposer extends ChatComposer {
@action
- reset(channel) {
+ reset(channel, thread) {
this.message = ChatMessage.createDraftMessage(channel, {
user: this.currentUser,
- thread_id: channel.activeThread.id,
});
+ this.message.thread = thread;
+ }
+
+ @action
+ replyTo(message) {
+ this.chat.activeMessage = null;
+ this.message.thread = message.thread;
+ this.message.inReplyTo = message;
}
}
diff --git a/plugins/chat/assets/javascripts/discourse/services/chat-channel-thread-pane-subscriptions-manager.js b/plugins/chat/assets/javascripts/discourse/services/chat-channel-thread-pane-subscriptions-manager.js
index cd636f31c75..c0ffc6aa17d 100644
--- a/plugins/chat/assets/javascripts/discourse/services/chat-channel-thread-pane-subscriptions-manager.js
+++ b/plugins/chat/assets/javascripts/discourse/services/chat-channel-thread-pane-subscriptions-manager.js
@@ -3,7 +3,7 @@ import ChatPaneBaseSubscriptionsManager from "./chat-pane-base-subscriptions-man
export default class ChatChannelThreadPaneSubscriptionsManager extends ChatPaneBaseSubscriptionsManager {
get messageBusChannel() {
- return `/chat/${this.model.channelId}/thread/${this.model.id}`;
+ return `/chat/${this.model.channel.id}/thread/${this.model.id}`;
}
get messageBusLastId() {
@@ -12,7 +12,10 @@ export default class ChatChannelThreadPaneSubscriptionsManager extends ChatPaneB
handleSentMessage(data) {
if (data.chat_message.user.id === this.currentUser.id && data.staged_id) {
- const stagedMessage = this.handleStagedMessageInternal(data);
+ const stagedMessage = this.handleStagedMessageInternal(
+ this.model.channel,
+ data
+ );
if (stagedMessage) {
return;
}
@@ -23,15 +26,6 @@ export default class ChatChannelThreadPaneSubscriptionsManager extends ChatPaneB
data.chat_message
);
this.messagesManager.addMessages([message]);
-
- // TODO (martin) All the scrolling and new message indicator shenanigans,
- // as well as handling marking the thread as read.
- }
-
- // NOTE: noop, there is nothing to do when a thread is created
- // inside the thread panel.
- handleThreadCreated() {
- return;
}
// NOTE: noop, there is nothing to do when a thread original message
diff --git a/plugins/chat/assets/javascripts/discourse/services/chat-channel-thread-pane.js b/plugins/chat/assets/javascripts/discourse/services/chat-channel-thread-pane.js
index dd39dc6dffc..2e03a27da6c 100644
--- a/plugins/chat/assets/javascripts/discourse/services/chat-channel-thread-pane.js
+++ b/plugins/chat/assets/javascripts/discourse/services/chat-channel-thread-pane.js
@@ -3,6 +3,19 @@ import { inject as service } from "@ember/service";
export default class ChatChannelThreadPane extends ChatChannelPane {
@service chatChannelThreadComposer;
+ @service chat;
+ @service chatStateManager;
+
+ close() {
+ this.chat.activeChannel.activeThread?.messagesManager?.clearMessages();
+ this.chat.activeChannel.activeThread = null;
+ this.chatStateManager.closeSidePanel();
+ }
+
+ open(thread) {
+ this.chat.activeChannel.activeThread = thread;
+ this.chatStateManager.openSidePanel();
+ }
get selectedMessageIds() {
return this.chat.activeChannel.activeThread.selectedMessages.mapBy("id");
diff --git a/plugins/chat/assets/javascripts/discourse/services/chat-composer.js b/plugins/chat/assets/javascripts/discourse/services/chat-composer.js
new file mode 100644
index 00000000000..74f01157f04
--- /dev/null
+++ b/plugins/chat/assets/javascripts/discourse/services/chat-composer.js
@@ -0,0 +1,52 @@
+import { tracked } from "@glimmer/tracking";
+import Service, { inject as service } from "@ember/service";
+import { action } from "@ember/object";
+import ChatMessage from "discourse/plugins/chat/discourse/models/chat-message";
+
+export default class ChatComposer extends Service {
+ @service chat;
+ @service currentUser;
+
+ @tracked _message;
+
+ @action
+ cancel() {
+ if (this.message.editing) {
+ this.reset();
+ } else if (this.message.inReplyTo) {
+ this.message.inReplyTo = null;
+ }
+ }
+
+ @action
+ reset(channel) {
+ this.message = ChatMessage.createDraftMessage(channel, {
+ user: this.currentUser,
+ });
+ }
+
+ @action
+ clear() {
+ this.message.message = "";
+ }
+
+ @action
+ editMessage(message) {
+ this.chat.activeMessage = null;
+ message.editing = true;
+ this.message = message;
+ }
+
+ @action
+ onCancelEditing() {
+ this.reset();
+ }
+
+ get message() {
+ return this._message;
+ }
+
+ set message(message) {
+ this._message = message;
+ }
+}
diff --git a/plugins/chat/assets/javascripts/discourse/services/chat-pane-base-subscriptions-manager.js b/plugins/chat/assets/javascripts/discourse/services/chat-pane-base-subscriptions-manager.js
index 12cff6a7556..180946c26d6 100644
--- a/plugins/chat/assets/javascripts/discourse/services/chat-pane-base-subscriptions-manager.js
+++ b/plugins/chat/assets/javascripts/discourse/services/chat-pane-base-subscriptions-manager.js
@@ -6,7 +6,7 @@ import { bind } from "discourse-common/utils/decorators";
// TODO (martin) This export can be removed once we move the handleSentMessage
// code completely out of ChatLivePane
-export function handleStagedMessage(messagesManager, data) {
+export function handleStagedMessage(channel, messagesManager, data) {
const stagedMessage = messagesManager.findStagedMessage(data.staged_id);
if (!stagedMessage) {
@@ -17,17 +17,8 @@ export function handleStagedMessage(messagesManager, data) {
stagedMessage.id = data.chat_message.id;
stagedMessage.staged = false;
stagedMessage.excerpt = data.chat_message.excerpt;
- stagedMessage.threadId = data.chat_message.thread_id;
- stagedMessage.channelId = data.chat_message.chat_channel_id;
+ stagedMessage.channel = channel;
stagedMessage.createdAt = data.chat_message.created_at;
-
- const inReplyToMsg = messagesManager.findMessage(
- data.chat_message.in_reply_to?.id
- );
- if (inReplyToMsg && !inReplyToMsg.threadId) {
- inReplyToMsg.threadId = data.chat_message.thread_id;
- }
-
stagedMessage.cooked = data.chat_message.cooked;
return stagedMessage;
@@ -48,6 +39,7 @@ export function handleStagedMessage(messagesManager, data) {
export default class ChatPaneBaseSubscriptionsManager extends Service {
@service chat;
@service currentUser;
+ @service chatStagedThreadMapping;
get messageBusChannel() {
throw "not implemented";
@@ -75,14 +67,15 @@ export default class ChatPaneBaseSubscriptionsManager extends Service {
if (!this.model) {
return;
}
+
this.messageBus.unsubscribe(this.messageBusChannel, this.onMessage);
this.model = null;
}
// TODO (martin) This can be removed once we move the handleSentMessage
// code completely out of ChatLivePane
- handleStagedMessageInternal(data) {
- return handleStagedMessage(this.messagesManager, data);
+ handleStagedMessageInternal(channel, data) {
+ return handleStagedMessage(channel, this.messagesManager, data);
}
@bind
@@ -122,7 +115,7 @@ export default class ChatPaneBaseSubscriptionsManager extends Service {
this.handleFlaggedMessage(busData);
break;
case "thread_created":
- this.handleThreadCreated(busData);
+ this.handleNewThreadCreated(busData);
break;
case "update_thread_original_message":
this.handleThreadOriginalMessageUpdate(busData);
@@ -223,8 +216,34 @@ export default class ChatPaneBaseSubscriptionsManager extends Service {
}
}
- handleThreadCreated() {
- throw "not implemented";
+ handleNewThreadCreated(data) {
+ this.model.threadsManager
+ .find(this.model.id, data.staged_thread_id, { fetchIfNotFound: false })
+ .then((stagedThread) => {
+ if (stagedThread) {
+ this.chatStagedThreadMapping.setMapping(
+ data.thread_id,
+ stagedThread.id
+ );
+ stagedThread.staged = false;
+ stagedThread.id = data.thread_id;
+ stagedThread.originalMessage.thread = stagedThread;
+ stagedThread.originalMessage.threadReplyCount ??= 1;
+ } else if (data.thread_id) {
+ this.model.threadsManager
+ .find(this.model.id, data.thread_id, { fetchIfNotFound: true })
+ .then((thread) => {
+ const channelOriginalMessage =
+ this.model.messagesManager.findMessage(
+ thread.originalMessage.id
+ );
+
+ if (channelOriginalMessage) {
+ channelOriginalMessage.thread = thread;
+ }
+ });
+ }
+ });
}
handleThreadOriginalMessageUpdate() {
diff --git a/plugins/chat/assets/javascripts/discourse/services/chat-staged-thread-mapping.js b/plugins/chat/assets/javascripts/discourse/services/chat-staged-thread-mapping.js
new file mode 100644
index 00000000000..747e92e3df0
--- /dev/null
+++ b/plugins/chat/assets/javascripts/discourse/services/chat-staged-thread-mapping.js
@@ -0,0 +1,34 @@
+import KeyValueStore from "discourse/lib/key-value-store";
+import Service from "@ember/service";
+
+export default class ChatStagedThreadMapping extends Service {
+ STORE_NAMESPACE = "discourse_chat_";
+ KEY = "staged_thread";
+
+ store = new KeyValueStore(this.STORE_NAMESPACE);
+
+ constructor() {
+ super(...arguments);
+
+ if (!this.store.getObject(this.USER_EMOJIS_STORE_KEY)) {
+ this.storedFavorites = [];
+ }
+ }
+
+ getMapping() {
+ return JSON.parse(this.store.getObject(this.KEY) || "{}");
+ }
+
+ setMapping(id, stagedId) {
+ const mapping = {};
+ mapping[stagedId] = id;
+ this.store.setObject({
+ key: this.KEY,
+ value: JSON.stringify(mapping),
+ });
+ }
+
+ reset() {
+ this.store.setObject({ key: this.KEY, value: "{}" });
+ }
+}
diff --git a/plugins/chat/assets/javascripts/discourse/services/chat-state-manager.js b/plugins/chat/assets/javascripts/discourse/services/chat-state-manager.js
index 6dd753c1692..2aee412c77b 100644
--- a/plugins/chat/assets/javascripts/discourse/services/chat-state-manager.js
+++ b/plugins/chat/assets/javascripts/discourse/services/chat-state-manager.js
@@ -21,9 +21,10 @@ export function resetChatDrawerStateCallbacks() {
export default class ChatStateManager extends Service {
@service chat;
@service router;
- isDrawerExpanded = false;
- isDrawerActive = false;
- isSidePanelExpanded = false;
+
+ @tracked isSidePanelExpanded = false;
+ @tracked isDrawerExpanded = false;
+ @tracked isDrawerActive = false;
@tracked _chatURL = null;
@tracked _appURL = null;
@@ -44,16 +45,16 @@ export default class ChatStateManager extends Service {
}
openSidePanel() {
- this.set("isSidePanelExpanded", true);
+ this.isSidePanelExpanded = true;
}
closeSidePanel() {
- this.set("isSidePanelExpanded", false);
+ this.isSidePanelExpanded = false;
}
didOpenDrawer(url = null) {
- this.set("isDrawerActive", true);
- this.set("isDrawerExpanded", true);
+ this.isDrawerActive = true;
+ this.isDrawerExpanded = true;
if (url) {
this.storeChatURL(url);
@@ -64,27 +65,27 @@ export default class ChatStateManager extends Service {
}
didCloseDrawer() {
- this.set("isDrawerActive", false);
- this.set("isDrawerExpanded", false);
+ this.isDrawerActive = false;
+ this.isDrawerExpanded = false;
this.chat.updatePresence();
this.#publishStateChange();
}
didExpandDrawer() {
- this.set("isDrawerActive", true);
- this.set("isDrawerExpanded", true);
+ this.isDrawerActive = true;
+ this.isDrawerExpanded = true;
this.chat.updatePresence();
}
didCollapseDrawer() {
- this.set("isDrawerActive", true);
- this.set("isDrawerExpanded", false);
+ this.isDrawerActive = true;
+ this.isDrawerExpanded = false;
this.#publishStateChange();
}
didToggleDrawer() {
- this.set("isDrawerExpanded", !this.isDrawerExpanded);
- this.set("isDrawerActive", true);
+ this.isDrawerExpanded = !this.isDrawerExpanded;
+ this.isDrawerActive = true;
this.#publishStateChange();
}
diff --git a/plugins/chat/assets/javascripts/discourse/templates/chat-channel-thread.hbs b/plugins/chat/assets/javascripts/discourse/templates/chat-channel-thread.hbs
index 6a0689fc057..b68ebf99aa6 100644
--- a/plugins/chat/assets/javascripts/discourse/templates/chat-channel-thread.hbs
+++ b/plugins/chat/assets/javascripts/discourse/templates/chat-channel-thread.hbs
@@ -1,2 +1 @@
-{{! ChatThreadList will go here later }}
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/plugins/chat/assets/stylesheets/common/chat-thread.scss b/plugins/chat/assets/stylesheets/common/chat-thread.scss
index a3dbdcf780d..d254fae9b73 100644
--- a/plugins/chat/assets/stylesheets/common/chat-thread.scss
+++ b/plugins/chat/assets/stylesheets/common/chat-thread.scss
@@ -25,8 +25,7 @@
flex-grow: 1;
overscroll-behavior: contain;
display: flex;
- flex-direction: column-reverse;
- will-change: transform;
+ flex-direction: column;
}
.chat-composer__wrapper {
diff --git a/plugins/chat/lib/chat/message_creator.rb b/plugins/chat/lib/chat/message_creator.rb
index 9a608bd51e4..7acc4aceff9 100644
--- a/plugins/chat/lib/chat/message_creator.rb
+++ b/plugins/chat/lib/chat/message_creator.rb
@@ -13,6 +13,7 @@ module Chat
chat_channel:,
in_reply_to_id: nil,
thread_id: nil,
+ staged_thread_id: nil,
user:,
content:,
staged_id: nil,
@@ -31,6 +32,7 @@ module Chat
@incoming_chat_webhook = incoming_chat_webhook
@upload_ids = upload_ids || []
@thread_id = thread_id
+ @staged_thread_id = staged_thread_id
@error = nil
@chat_message =
@@ -57,7 +59,12 @@ module Chat
create_thread
@chat_message.attach_uploads(uploads)
Chat::Draft.where(user_id: @user.id, chat_channel_id: @chat_channel.id).destroy_all
- Chat::Publisher.publish_new!(@chat_channel, @chat_message, @staged_id)
+ Chat::Publisher.publish_new!(
+ @chat_channel,
+ @chat_message,
+ @staged_id,
+ staged_thread_id: @staged_thread_id,
+ )
resolved_thread&.increment_replies_count_cache
Jobs.enqueue(Jobs::Chat::ProcessMessage, { chat_message_id: @chat_message.id })
Chat::Notifier.notify_new(chat_message: @chat_message, timestamp: @chat_message.created_at)
@@ -123,6 +130,8 @@ module Chat
end
def validate_existing_thread!
+ return if @staged_thread_id.present? && @thread_id.blank?
+
return if @thread_id.blank?
@existing_thread = Chat::Thread.find(@thread_id)
@@ -165,7 +174,7 @@ module Chat
def create_thread
return if @in_reply_to_id.blank?
- return if @chat_message.in_thread?
+ return if @chat_message.in_thread? && !@staged_thread_id.present?
if @original_message.thread
thread = @original_message.thread
@@ -177,12 +186,15 @@ module Chat
channel: @chat_message.chat_channel,
)
@chat_message.in_reply_to.thread_id = thread.id
- Chat::Publisher.publish_thread_created!(
- @chat_message.chat_channel,
- @chat_message.in_reply_to,
- )
end
+ Chat::Publisher.publish_thread_created!(
+ @chat_message.chat_channel,
+ @chat_message.in_reply_to,
+ thread.id,
+ @staged_thread_id,
+ )
+
@chat_message.thread_id = thread.id
# NOTE: We intentionally do not try to correct thread IDs within the chain
diff --git a/plugins/chat/spec/components/chat/message_creator_spec.rb b/plugins/chat/spec/components/chat/message_creator_spec.rb
index a94399320dc..f1d390ea27d 100644
--- a/plugins/chat/spec/components/chat/message_creator_spec.rb
+++ b/plugins/chat/spec/components/chat/message_creator_spec.rb
@@ -451,6 +451,30 @@ describe Chat::MessageCreator do
expect(thread_created_message.channel).to eq("/chat/#{public_chat_channel.id}")
end
+ context "when a staged_thread_id is provided" do
+ fab!(:existing_thread) { Fabricate(:chat_thread, channel: public_chat_channel) }
+
+ it "creates a thread and publishes with the staged id" do
+ messages =
+ MessageBus.track_publish do
+ described_class.create(
+ chat_channel: public_chat_channel,
+ user: user1,
+ content: "this is a message",
+ in_reply_to_id: reply_message.id,
+ staged_thread_id: "stagedthreadid",
+ ).chat_message
+ end
+
+ thread_event = messages.find { |m| m.data["type"] == "thread_created" }
+ expect(thread_event.data["staged_thread_id"]).to eq("stagedthreadid")
+ expect(Chat::Thread.find(thread_event.data["thread_id"])).to be_persisted
+
+ send_event = messages.find { |m| m.data["type"] == "sent" }
+ expect(send_event.data["staged_thread_id"]).to eq("stagedthreadid")
+ end
+ end
+
context "when the thread_id is provided" do
fab!(:existing_thread) { Fabricate(:chat_thread, channel: public_chat_channel) }
diff --git a/plugins/chat/spec/requests/chat_controller_spec.rb b/plugins/chat/spec/requests/chat_controller_spec.rb
index 573474f8a5d..ce50fb698db 100644
--- a/plugins/chat/spec/requests/chat_controller_spec.rb
+++ b/plugins/chat/spec/requests/chat_controller_spec.rb
@@ -385,6 +385,31 @@ RSpec.describe Chat::ChatController do
expect(messages.first.data["last_read_message_id"]).to eq(Chat::Message.last.id)
end
+ context "when sending a message in a staged thread" do
+ it "creates the thread and publishes with the staged id" do
+ sign_in(user)
+
+ messages =
+ MessageBus.track_publish do
+ post "/chat/#{chat_channel.id}.json",
+ params: {
+ message: message,
+ in_reply_to_id: message_1.id,
+ staged_thread_id: "stagedthreadid",
+ }
+ end
+
+ expect(response.status).to eq(200)
+
+ thread_event = messages.find { |m| m.data["type"] == "thread_created" }
+ expect(thread_event.data["staged_thread_id"]).to eq("stagedthreadid")
+ expect(Chat::Thread.find(thread_event.data["thread_id"])).to be_persisted
+
+ sent_event = messages.find { |m| m.data["type"] == "sent" }
+ expect(sent_event.data["staged_thread_id"]).to eq("stagedthreadid")
+ end
+ end
+
context "when sending a message in a thread" do
fab!(:thread) do
Fabricate(:chat_thread, channel: chat_channel, original_message: message_1)
diff --git a/plugins/chat/spec/services/chat/publisher_spec.rb b/plugins/chat/spec/services/chat/publisher_spec.rb
index 78e3e3d15f3..6a70614974c 100644
--- a/plugins/chat/spec/services/chat/publisher_spec.rb
+++ b/plugins/chat/spec/services/chat/publisher_spec.rb
@@ -111,6 +111,32 @@ describe Chat::Publisher do
end
end
+ context "when a staged thread has been provided" do
+ fab!(:thread) do
+ Fabricate(
+ :chat_thread,
+ original_message: Fabricate(:chat_message, chat_channel: channel),
+ channel: channel,
+ )
+ end
+
+ before { message.update!(thread: thread) }
+
+ it "generates the correct targets" do
+ targets =
+ described_class.calculate_publish_targets(
+ channel,
+ message,
+ staged_thread_id: "stagedthreadid",
+ )
+
+ expect(targets).to contain_exactly(
+ "/chat/#{channel.id}/thread/#{thread.id}",
+ "/chat/#{channel.id}/thread/stagedthreadid",
+ )
+ end
+ end
+
context "when the message is a thread reply" do
fab!(:thread) do
Fabricate(
diff --git a/plugins/chat/spec/system/archive_channel_spec.rb b/plugins/chat/spec/system/archive_channel_spec.rb
index 292a0595e6d..63228a646b3 100644
--- a/plugins/chat/spec/system/archive_channel_spec.rb
+++ b/plugins/chat/spec/system/archive_channel_spec.rb
@@ -61,11 +61,12 @@ RSpec.describe "Archive channel", type: :system, js: true do
find("#split-topic-name").fill_in(with: "An interesting topic for cats")
click_button(I18n.t("js.chat.channel_archive.title"))
- expect(page).to have_content(I18n.t("js.chat.channel_archive.process_started"))
- expect(page).to have_css(".chat-channel-archive-status")
+ expect(page).to have_css(".chat-channel-archive-status", wait: 15)
end
it "shows an error when the topic is invalid" do
+ Jobs.run_immediately!
+
chat.visit_channel_settings(channel_1)
click_button(I18n.t("js.chat.channel_settings.archive_channel"))
find("#split-topic-name").fill_in(
@@ -73,7 +74,7 @@ RSpec.describe "Archive channel", type: :system, js: true do
)
click_button(I18n.t("js.chat.channel_archive.title"))
- expect(page).not_to have_content(I18n.t("js.chat.channel_archive.process_started"))
+ expect(page).to have_no_content(I18n.t("js.chat.channel_archive.process_started"))
expect(page).to have_content("Title can't have more than 1 emoji")
end
diff --git a/plugins/chat/spec/system/browse_page_spec.rb b/plugins/chat/spec/system/browse_page_spec.rb
index 31392066c38..3cd3cfc7ee0 100644
--- a/plugins/chat/spec/system/browse_page_spec.rb
+++ b/plugins/chat/spec/system/browse_page_spec.rb
@@ -79,7 +79,7 @@ RSpec.describe "Browse page", type: :system, js: true do
context "when results are found" do
it "lists expected results" do
visit("/chat/browse")
- find(".dc-filter-input").fill_in(with: category_channel_1.name)
+ find(".chat-browse-view .dc-filter-input").fill_in(with: category_channel_1.name)
expect(browse_view).to have_content(category_channel_1.name)
expect(browse_view).to have_no_content(category_channel_2.name)
@@ -89,14 +89,14 @@ RSpec.describe "Browse page", type: :system, js: true do
context "when results are not found" do
it "displays the correct message" do
visit("/chat/browse")
- find(".dc-filter-input").fill_in(with: "x")
+ find(".chat-browse-view .dc-filter-input").fill_in(with: "x")
expect(browse_view).to have_content(I18n.t("js.chat.empty_state.title"))
end
it "doesn’t display any channel" do
visit("/chat/browse")
- find(".dc-filter-input").fill_in(with: "x")
+ find(".chat-browse-view .dc-filter-input").fill_in(with: "x")
expect(browse_view).to have_no_content(category_channel_1.name)
expect(browse_view).to have_no_content(category_channel_2.name)
diff --git a/plugins/chat/spec/system/channel_thread_message_echoing_spec.rb b/plugins/chat/spec/system/channel_thread_message_echoing_spec.rb
index 1e3f4274045..035ed2abdd5 100644
--- a/plugins/chat/spec/system/channel_thread_message_echoing_spec.rb
+++ b/plugins/chat/spec/system/channel_thread_message_echoing_spec.rb
@@ -69,8 +69,8 @@ describe "Channel thread message echoing", type: :system, js: true do
chat_page.visit_channel(channel)
channel_page.message_thread_indicator(thread.original_message).click
expect(side_panel).to have_open_thread(thread)
- open_thread.send_message(thread.id, "new thread message")
- expect(open_thread).to have_message(thread.id, text: "new thread message")
+ open_thread.send_message("new thread message")
+ expect(open_thread).to have_message(thread_id: thread.id, text: "new thread message")
new_message = thread.reload.replies.last
expect(channel_page).not_to have_css(channel_page.message_by_id_selector(new_message.id))
end
diff --git a/plugins/chat/spec/system/chat_message/channel_spec.rb b/plugins/chat/spec/system/chat_message/channel_spec.rb
index a91c6b94511..3027f515eff 100644
--- a/plugins/chat/spec/system/chat_message/channel_spec.rb
+++ b/plugins/chat/spec/system/chat_message/channel_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-RSpec.describe "Chat message", type: :system, js: true do
+RSpec.describe "Chat message - channel", type: :system, js: true do
fab!(:current_user) { Fabricate(:user) }
fab!(:channel_1) { Fabricate(:chat_channel) }
fab!(:message_1) { Fabricate(:chat_message, chat_channel: channel_1) }
diff --git a/plugins/chat/spec/system/chat_message/thread_spec.rb b/plugins/chat/spec/system/chat_message/thread_spec.rb
index 187a6aef2cc..15f6e639308 100644
--- a/plugins/chat/spec/system/chat_message/thread_spec.rb
+++ b/plugins/chat/spec/system/chat_message/thread_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-RSpec.describe "Chat message - channel", type: :system, js: true do
+RSpec.describe "Chat message - thread", type: :system, js: true do
fab!(:current_user) { Fabricate(:user) }
fab!(:other_user) { Fabricate(:user) }
fab!(:channel_1) { Fabricate(:chat_channel) }
@@ -25,13 +25,13 @@ RSpec.describe "Chat message - channel", type: :system, js: true do
context "when hovering a message" do
it "adds an active class" do
- last_message = thread_1.chat_messages.last
+ first_message = thread_1.chat_messages.first
chat.visit_thread(thread_1)
- thread.hover_message(last_message)
+ thread.hover_message(first_message)
expect(page).to have_css(
- ".chat-thread[data-id='#{thread_1.id}'] [data-id='#{last_message.id}'] .chat-message.is-active",
+ ".chat-thread[data-id='#{thread_1.id}'] [data-id='#{first_message.id}'] .chat-message.is-active",
)
end
end
diff --git a/plugins/chat/spec/system/deleted_message_spec.rb b/plugins/chat/spec/system/deleted_message_spec.rb
index 384c64d2c1a..918a1939e32 100644
--- a/plugins/chat/spec/system/deleted_message_spec.rb
+++ b/plugins/chat/spec/system/deleted_message_spec.rb
@@ -46,6 +46,7 @@ RSpec.describe "Deleted message", type: :system, js: true do
channel_1.update!(threading_enabled: true)
SiteSetting.enable_experimental_chat_threaded_discussions = true
chat_system_user_bootstrap(user: other_user, channel: channel_1)
+ Chat::Thread.update_counts
end
it "hides the deleted messages" do
@@ -55,8 +56,8 @@ RSpec.describe "Deleted message", type: :system, js: true do
expect(channel_page).to have_message(id: message_1.id)
expect(channel_page).to have_message(id: message_2.id)
- expect(open_thread).to have_message(thread.id, id: message_4.id)
- expect(open_thread).to have_message(thread.id, id: message_5.id)
+ expect(open_thread).to have_message(thread_id: thread.id, id: message_4.id)
+ expect(open_thread).to have_message(thread_id: thread.id, id: message_5.id)
Chat::Publisher.publish_bulk_delete!(
channel_1,
@@ -65,8 +66,8 @@ RSpec.describe "Deleted message", type: :system, js: true do
expect(channel_page).to have_no_message(id: message_1.id)
expect(channel_page).to have_no_message(id: message_2.id)
- expect(open_thread).to have_no_message(thread.id, id: message_4.id)
- expect(open_thread).to have_no_message(thread.id, id: message_5.id)
+ expect(open_thread).to have_no_message(thread_id: thread.id, id: message_4.id)
+ expect(open_thread).to have_no_message(thread_id: thread.id, id: message_5.id)
end
end
end
diff --git a/plugins/chat/spec/system/message_thread_indicator_spec.rb b/plugins/chat/spec/system/message_thread_indicator_spec.rb
index ca73c23c0e7..92ae18a221f 100644
--- a/plugins/chat/spec/system/message_thread_indicator_spec.rb
+++ b/plugins/chat/spec/system/message_thread_indicator_spec.rb
@@ -86,8 +86,8 @@ describe "Thread indicator for chat messages", type: :system, js: true do
message_without_thread = Fabricate(:chat_message, chat_channel: channel, user: other_user)
chat_page.visit_channel(channel)
channel_page.reply_to(message_without_thread)
- channel_page.fill_composer("this is a reply to make a new thread")
- channel_page.click_send_message
+ open_thread.fill_composer("this is a reply to make a new thread")
+ open_thread.click_send_message
expect(channel_page).to have_thread_indicator(message_without_thread)
@@ -108,7 +108,7 @@ describe "Thread indicator for chat messages", type: :system, js: true do
)
channel_page.message_thread_indicator(thread_1.original_message).click
expect(side_panel).to have_open_thread(thread_1)
- open_thread.send_message(thread_1.id, "new thread message")
+ open_thread.send_message("new thread message")
expect(channel_page.message_thread_indicator(thread_1.original_message)).to have_css(
".chat-message-thread-indicator__replies-count",
text: I18n.t("js.chat.thread.replies", count: 4),
diff --git a/plugins/chat/spec/system/page_objects/chat/chat.rb b/plugins/chat/spec/system/page_objects/chat/chat.rb
index 8fc07f6479b..a3bc3a90c58 100644
--- a/plugins/chat/spec/system/page_objects/chat/chat.rb
+++ b/plugins/chat/spec/system/page_objects/chat/chat.rb
@@ -25,6 +25,7 @@ module PageObjects
def visit_thread(thread)
visit(thread.url)
+ has_no_css?(".chat-skeleton")
end
def visit_channel_settings(channel)
diff --git a/plugins/chat/spec/system/page_objects/chat/chat_channel.rb b/plugins/chat/spec/system/page_objects/chat/chat_channel.rb
index 395c4b2c955..7daa59c4672 100644
--- a/plugins/chat/spec/system/page_objects/chat/chat_channel.rb
+++ b/plugins/chat/spec/system/page_objects/chat/chat_channel.rb
@@ -3,6 +3,10 @@
module PageObjects
module Pages
class ChatChannel < PageObjects::Pages::Base
+ def replying_to?(message)
+ find(".chat-channel .chat-reply", text: message.message)
+ end
+
def type_in_composer(input)
find(".chat-channel .chat-composer__input").click # makes helper more reliable by ensuring focus is not lost
find(".chat-channel .chat-composer__input").send_keys(input)
@@ -49,7 +53,7 @@ module PageObjects
def click_message_action_mobile(message, message_action)
expand_message_actions_mobile(message, delay: 0.5)
wait_for_animation(find(".chat-message-actions"), timeout: 5)
- find(".chat-message-action-item[data-id=\"#{message_action}\"] button").click
+ find(".chat-message-actions [data-id=\"#{message_action}\"]").click
end
def hover_message(message)
@@ -114,8 +118,12 @@ module PageObjects
end
def reply_to(message)
- hover_message(message)
- find(".reply-btn").click
+ if page.has_css?("html.mobile-view", wait: 0)
+ click_message_action_mobile(message, "reply")
+ else
+ hover_message(message)
+ find(".reply-btn").click
+ end
end
def has_bookmarked_message?(message)
@@ -167,18 +175,22 @@ module PageObjects
def check_message_presence(exists: true, text: nil, id: nil)
css_method = exists ? :has_css? : :has_no_css?
if text
- send(css_method, ".chat-message-text", text: text, wait: 5)
+ find(".chat-channel").send(css_method, ".chat-message-text", text: text, wait: 5)
elsif id
- send(css_method, ".chat-message-container[data-id=\"#{id}\"]", wait: 10)
+ find(".chat-channel").send(
+ css_method,
+ ".chat-message-container[data-id=\"#{id}\"]",
+ wait: 10,
+ )
end
end
- def has_thread_indicator?(message)
- has_css?(message_thread_indicator_selector(message))
+ def has_thread_indicator?(message, text: nil)
+ has_css?(message_thread_indicator_selector(message), text: text)
end
- def has_no_thread_indicator?(message)
- has_no_css?(message_thread_indicator_selector(message))
+ def has_no_thread_indicator?(message, text: nil)
+ has_no_css?(message_thread_indicator_selector(message), text: text)
end
def message_thread_indicator(message)
diff --git a/plugins/chat/spec/system/page_objects/chat/chat_side_panel.rb b/plugins/chat/spec/system/page_objects/chat/chat_side_panel.rb
index fa6c15b6859..78d932244e3 100644
--- a/plugins/chat/spec/system/page_objects/chat/chat_side_panel.rb
+++ b/plugins/chat/spec/system/page_objects/chat/chat_side_panel.rb
@@ -3,8 +3,12 @@
module PageObjects
module Pages
class ChatSidePanel < PageObjects::Pages::Base
- def has_open_thread?(thread)
- has_css?(".chat-side-panel .chat-thread[data-id='#{thread.id}']")
+ def has_open_thread?(thread = nil)
+ if thread
+ has_css?(".chat-side-panel .chat-thread[data-id='#{thread.id}']")
+ else
+ has_css?(".chat-side-panel .chat-thread")
+ end
end
def has_no_open_thread?
diff --git a/plugins/chat/spec/system/page_objects/chat/chat_thread.rb b/plugins/chat/spec/system/page_objects/chat/chat_thread.rb
index ed6016b9f42..2ef08a9fa35 100644
--- a/plugins/chat/spec/system/page_objects/chat/chat_thread.rb
+++ b/plugins/chat/spec/system/page_objects/chat/chat_thread.rb
@@ -19,10 +19,6 @@ module PageObjects
header.has_content?(content)
end
- def thread_selector_by_id(id)
- ".chat-thread[data-id=\"#{id}\"]"
- end
-
def has_no_loading_skeleton?
has_no_css?(".chat-thread__messages .chat-skeleton")
end
@@ -41,42 +37,32 @@ module PageObjects
find(".chat-thread .chat-composer__input").click # ensures autocomplete is closed and not masking anything
end
- def send_message(id, text = nil)
+ def send_message(text = nil)
text = text.chomp if text.present? # having \n on the end of the string counts as an Enter keypress
fill_composer(text)
- click_send_message(id)
+ click_send_message
click_composer
end
- def click_send_message(id)
- find(thread_selector_by_id(id)).find(
- ".chat-composer.is-send-enabled .chat-composer__send-btn",
- ).click
+ def click_send_message
+ find(".chat-thread .chat-composer.is-send-enabled .chat-composer__send-btn").click
end
- def has_message?(thread_id, text: nil, id: nil)
- check_message_presence(thread_id, exists: true, text: text, id: id)
+ def has_message?(text: nil, id: nil, thread_id: nil)
+ check_message_presence(exists: true, text: text, id: id, thread_id: thread_id)
end
- def has_no_message?(thread_id, text: nil, id: nil)
- check_message_presence(thread_id, exists: false, text: text, id: id)
+ def has_no_message?(text: nil, id: nil, thread_id: nil)
+ check_message_presence(exists: false, text: text, id: id, thread_id: thread_id)
end
- def check_message_presence(thread_id, exists: true, text: nil, id: nil)
+ def check_message_presence(exists: true, text: nil, id: nil, thread_id: nil)
css_method = exists ? :has_css? : :has_no_css?
+ selector = thread_id ? ".chat-thread[data-id=\"#{thread_id}\"]" : ".chat-thread"
if text
- find(thread_selector_by_id(thread_id)).send(
- css_method,
- ".chat-message-text",
- text: text,
- wait: 5,
- )
+ find(selector).send(css_method, ".chat-message-text", text: text, wait: 5)
elsif id
- find(thread_selector_by_id(thread_id)).send(
- css_method,
- ".chat-message-container[data-id=\"#{id}\"]",
- wait: 10,
- )
+ find(selector).send(css_method, ".chat-message-container[data-id=\"#{id}\"]", wait: 10)
end
end
diff --git a/plugins/chat/spec/system/page_objects/chat_drawer/chat_drawer.rb b/plugins/chat/spec/system/page_objects/chat_drawer/chat_drawer.rb
index a6befde3676..8d07c4dd16f 100644
--- a/plugins/chat/spec/system/page_objects/chat_drawer/chat_drawer.rb
+++ b/plugins/chat/spec/system/page_objects/chat_drawer/chat_drawer.rb
@@ -31,8 +31,12 @@ module PageObjects
find("#{VISIBLE_DRAWER} .chat-drawer-header__full-screen-btn").click
end
- def has_open_thread?(thread)
- has_css?("#{VISIBLE_DRAWER} .chat-thread[data-id='#{thread.id}']")
+ def has_open_thread?(thread = nil)
+ if thread
+ has_css?("#{VISIBLE_DRAWER} .chat-thread[data-id='#{thread.id}']")
+ else
+ has_css?("#{VISIBLE_DRAWER} .chat-thread")
+ end
end
def has_open_channel?(channel)
diff --git a/plugins/chat/spec/system/reply_to_message/drawer_spec.rb b/plugins/chat/spec/system/reply_to_message/drawer_spec.rb
new file mode 100644
index 00000000000..8678063e512
--- /dev/null
+++ b/plugins/chat/spec/system/reply_to_message/drawer_spec.rb
@@ -0,0 +1,99 @@
+# frozen_string_literal: true
+
+RSpec.describe "Reply to message - channel - drawer", type: :system, js: true do
+ let(:chat_page) { PageObjects::Pages::Chat.new }
+ let(:channel_page) { PageObjects::Pages::ChatChannel.new }
+ let(:thread_page) { PageObjects::Pages::ChatThread.new }
+ let(:drawer_page) { PageObjects::Pages::ChatDrawer.new }
+
+ fab!(:current_user) { Fabricate(:user) }
+ fab!(:channel_1) { Fabricate(:category_channel) }
+ fab!(:original_message) do
+ Fabricate(:chat_message, chat_channel: channel_1, user: Fabricate(:user))
+ end
+
+ before do
+ SiteSetting.enable_experimental_chat_threaded_discussions = true
+ chat_system_bootstrap
+ channel_1.update!(threading_enabled: true)
+ channel_1.add(current_user)
+ sign_in(current_user)
+ end
+
+ context "when the message has not current thread" do
+ it "starts a thread" do
+ visit("/")
+ chat_page.open_from_header
+ drawer_page.open_channel(channel_1)
+ channel_page.reply_to(original_message)
+ expect(drawer_page).to have_open_thread
+
+ thread_page.fill_composer("reply to message")
+ thread_page.click_send_message
+
+ expect(thread_page).to have_message(text: "reply to message")
+
+ drawer_page.back
+
+ expect(channel_page).to have_thread_indicator(original_message)
+ end
+ end
+
+ context "when the message has an existing thread" do
+ fab!(:message_1) do
+ creator =
+ Chat::MessageCreator.new(
+ chat_channel: channel_1,
+ in_reply_to_id: original_message.id,
+ user: Fabricate(:user),
+ content: Faker::Lorem.paragraph,
+ )
+ creator.create
+ creator.chat_message
+ end
+
+ it "replies to the existing thread" do
+ visit("/")
+ chat_page.open_from_header
+ drawer_page.open_channel(channel_1)
+
+ expect(channel_page).to have_thread_indicator(original_message, text: "1")
+
+ channel_page.reply_to(original_message)
+
+ expect(drawer_page).to have_open_thread
+
+ thread_page.fill_composer("reply to message")
+ thread_page.click_send_message
+
+ expect(thread_page).to have_message(text: message_1.message)
+ expect(thread_page).to have_message(text: "reply to message")
+
+ drawer_page.back
+
+ expect(channel_page).to have_thread_indicator(original_message, text: "2")
+ expect(channel_page).to have_no_message(text: "reply to message")
+ end
+ end
+
+ context "with threading disabled" do
+ before { channel_1.update!(threading_enabled: false) }
+
+ it "makes a reply in the channel" do
+ visit("/")
+ chat_page.open_from_header
+ drawer_page.open_channel(channel_1)
+ channel_page.reply_to(original_message)
+
+ expect(page).to have_selector(
+ ".chat-channel .chat-reply__excerpt",
+ text: original_message.message,
+ )
+
+ channel_page.fill_composer("reply to message")
+ channel_page.click_send_message
+
+ expect(channel_page).to have_message(text: "reply to message")
+ end
+ end
+end
diff --git a/plugins/chat/spec/system/reply_to_message/full_page_spec.rb b/plugins/chat/spec/system/reply_to_message/full_page_spec.rb
new file mode 100644
index 00000000000..1ec1101be0a
--- /dev/null
+++ b/plugins/chat/spec/system/reply_to_message/full_page_spec.rb
@@ -0,0 +1,103 @@
+# frozen_string_literal: true
+
+RSpec.describe "Reply to message - channel - full page", type: :system, js: true do
+ let(:chat_page) { PageObjects::Pages::Chat.new }
+ let(:channel_page) { PageObjects::Pages::ChatChannel.new }
+ let(:thread_page) { PageObjects::Pages::ChatThread.new }
+ let(:side_panel_page) { PageObjects::Pages::ChatSidePanel.new }
+
+ fab!(:current_user) { Fabricate(:user) }
+ fab!(:channel_1) { Fabricate(:category_channel) }
+ fab!(:original_message) do
+ Fabricate(:chat_message, chat_channel: channel_1, user: Fabricate(:user))
+ end
+
+ before do
+ SiteSetting.enable_experimental_chat_threaded_discussions = true
+ chat_system_bootstrap
+ channel_1.add(current_user)
+ sign_in(current_user)
+ channel_1.update!(threading_enabled: true)
+ end
+
+ context "when the message has not current thread" do
+ it "starts a thread" do
+ chat_page.visit_channel(channel_1)
+ channel_page.reply_to(original_message)
+
+ expect(side_panel_page).to have_open_thread
+
+ thread_page.fill_composer("reply to message")
+ thread_page.click_send_message
+
+ expect(thread_page).to have_message(text: "reply to message")
+ expect(channel_page).to have_thread_indicator(original_message)
+ end
+
+ context "when reloading after creating thread" do
+ it "correctly loads the thread" do
+ chat_page.visit_channel(channel_1)
+ channel_page.reply_to(original_message)
+ thread_page.fill_composer("reply to message")
+ thread_page.click_send_message
+
+ expect(thread_page).to have_message(text: "reply to message")
+
+ refresh
+
+ expect(thread_page).to have_message(text: "reply to message")
+ end
+ end
+ end
+
+ context "when the message has an existing thread" do
+ fab!(:message_1) do
+ creator =
+ Chat::MessageCreator.new(
+ chat_channel: channel_1,
+ in_reply_to_id: original_message.id,
+ user: Fabricate(:user),
+ content: Faker::Lorem.paragraph,
+ )
+ creator.create
+ creator.chat_message
+ end
+
+ it "replies to the existing thread" do
+ chat_page.visit_channel(channel_1)
+
+ expect(channel_page).to have_thread_indicator(original_message, text: "1")
+
+ channel_page.reply_to(original_message)
+
+ expect(side_panel_page).to have_open_thread
+
+ thread_page.fill_composer("reply to message")
+ thread_page.click_send_message
+
+ expect(thread_page).to have_message(text: message_1.message)
+ expect(thread_page).to have_message(text: "reply to message")
+ expect(channel_page).to have_thread_indicator(original_message, text: "2")
+ expect(channel_page).to have_no_message(text: "reply to message")
+ end
+ end
+
+ context "with threading disabled" do
+ before { channel_1.update!(threading_enabled: false) }
+
+ it "makes a reply in the channel" do
+ chat_page.visit_channel(channel_1)
+ channel_page.reply_to(original_message)
+
+ expect(page).to have_selector(
+ ".chat-channel .chat-reply__excerpt",
+ text: original_message.message,
+ )
+
+ channel_page.fill_composer("reply to message")
+ channel_page.click_send_message
+
+ expect(channel_page).to have_message(text: "reply to message")
+ end
+ end
+end
diff --git a/plugins/chat/spec/system/reply_to_message/mobile_spec.rb b/plugins/chat/spec/system/reply_to_message/mobile_spec.rb
new file mode 100644
index 00000000000..9a1b1fc9cf3
--- /dev/null
+++ b/plugins/chat/spec/system/reply_to_message/mobile_spec.rb
@@ -0,0 +1,109 @@
+# frozen_string_literal: true
+
+RSpec.describe "Reply to message - channel - mobile", type: :system, js: true, mobile: true do
+ let(:chat_page) { PageObjects::Pages::Chat.new }
+ let(:channel_page) { PageObjects::Pages::ChatChannel.new }
+ let(:thread_page) { PageObjects::Pages::ChatThread.new }
+ let(:side_panel_page) { PageObjects::Pages::ChatSidePanel.new }
+
+ fab!(:current_user) { Fabricate(:user) }
+ fab!(:channel_1) { Fabricate(:category_channel) }
+ fab!(:original_message) do
+ Fabricate(:chat_message, chat_channel: channel_1, user: Fabricate(:user))
+ end
+
+ before do
+ SiteSetting.enable_experimental_chat_threaded_discussions = true
+ chat_system_bootstrap
+ channel_1.update!(threading_enabled: true)
+ channel_1.add(current_user)
+ sign_in(current_user)
+ end
+
+ context "when the message has not current thread" do
+ it "starts a thread" do
+ chat_page.visit_channel(channel_1)
+ channel_page.reply_to(original_message)
+
+ expect(side_panel_page).to have_open_thread
+
+ thread_page.fill_composer("reply to message")
+ thread_page.click_send_message
+
+ expect(thread_page).to have_message(text: "reply to message")
+
+ thread_page.close
+
+ expect(channel_page).to have_thread_indicator(original_message)
+ end
+
+ context "when reloading after creating thread" do
+ it "correctly loads the thread" do
+ chat_page.visit_channel(channel_1)
+ channel_page.reply_to(original_message)
+ thread_page.fill_composer("reply to message")
+ thread_page.click_send_message
+
+ expect(thread_page).to have_message(text: "reply to message")
+
+ refresh
+
+ expect(thread_page).to have_message(text: "reply to message")
+ end
+ end
+ end
+
+ context "when the message has an existing thread" do
+ fab!(:message_1) do
+ creator =
+ Chat::MessageCreator.new(
+ chat_channel: channel_1,
+ in_reply_to_id: original_message.id,
+ user: Fabricate(:user),
+ content: Faker::Lorem.paragraph,
+ )
+ creator.create
+ creator.chat_message
+ end
+
+ it "replies to the existing thread" do
+ chat_page.visit_channel(channel_1)
+
+ expect(channel_page).to have_thread_indicator(original_message, text: "1")
+
+ channel_page.reply_to(original_message)
+
+ expect(side_panel_page).to have_open_thread
+
+ thread_page.fill_composer("reply to message")
+ thread_page.click_send_message
+
+ expect(thread_page).to have_message(text: message_1.message)
+ expect(thread_page).to have_message(text: "reply to message")
+
+ thread_page.close
+
+ expect(channel_page).to have_thread_indicator(original_message, text: "2")
+ expect(channel_page).to have_no_message(text: "reply to message")
+ end
+ end
+
+ context "with threading disabled" do
+ before { channel_1.update!(threading_enabled: false) }
+
+ it "makes a reply in the channel" do
+ chat_page.visit_channel(channel_1)
+ channel_page.reply_to(original_message)
+
+ expect(page).to have_selector(
+ ".chat-channel .chat-reply__excerpt",
+ text: original_message.message,
+ )
+
+ channel_page.fill_composer("reply to message")
+ channel_page.click_send_message
+
+ expect(channel_page).to have_message(text: "reply to message")
+ end
+ end
+end
diff --git a/plugins/chat/spec/system/reply_to_message/smoke_spec.rb b/plugins/chat/spec/system/reply_to_message/smoke_spec.rb
new file mode 100644
index 00000000000..f2c5ef66134
--- /dev/null
+++ b/plugins/chat/spec/system/reply_to_message/smoke_spec.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+RSpec.describe "Reply to message - smoke", type: :system, js: true do
+ let(:chat_page) { PageObjects::Pages::Chat.new }
+ let(:channel_page) { PageObjects::Pages::ChatChannel.new }
+ let(:thread_page) { PageObjects::Pages::ChatThread.new }
+
+ fab!(:user_1) { Fabricate(:user) }
+ fab!(:user_2) { Fabricate(:user) }
+ fab!(:channel_1) { Fabricate(:category_channel) }
+ fab!(:original_message) { Fabricate(:chat_message, chat_channel: channel_1) }
+
+ before do
+ SiteSetting.enable_experimental_chat_threaded_discussions = true
+ chat_system_bootstrap
+ channel_1.add(user_1)
+ channel_1.add(user_2)
+ channel_1.update!(threading_enabled: true)
+ end
+
+ context "when two users create a thread on the same message" do
+ it "works" do
+ using_session(:user_1) do
+ sign_in(user_1)
+ chat_page.visit_channel(channel_1)
+ channel_page.reply_to(original_message)
+ end
+
+ using_session(:user_2) do
+ sign_in(user_2)
+ chat_page.visit_channel(channel_1)
+ channel_page.reply_to(original_message)
+ end
+
+ using_session(:user_1) do
+ thread_page.fill_composer("user1reply")
+ thread_page.click_send_message
+
+ expect(channel_page).to have_thread_indicator(original_message, text: 1)
+
+ expect(thread_page).to have_message(text: "user1reply")
+ end
+
+ using_session(:user_2) do |session|
+ expect(thread_page).to have_message(text: "user1reply")
+ expect(channel_page).to have_thread_indicator(original_message, text: 1)
+
+ thread_page.fill_composer("user2reply")
+ thread_page.click_send_message
+
+ expect(thread_page).to have_message(text: "user2reply")
+ expect(channel_page).to have_thread_indicator(original_message, text: 2)
+
+ refresh
+
+ expect(thread_page).to have_message(text: "user1reply")
+ expect(thread_page).to have_message(text: "user2reply")
+ expect(channel_page).to have_thread_indicator(original_message, text: 2)
+
+ session.quit
+ end
+
+ using_session(:user_1) do |session|
+ expect(thread_page).to have_message(text: "user2reply")
+ expect(channel_page).to have_thread_indicator(original_message, text: 2)
+
+ refresh
+
+ expect(thread_page).to have_message(text: "user1reply")
+ expect(thread_page).to have_message(text: "user2reply")
+ expect(channel_page).to have_thread_indicator(original_message, text: 2)
+
+ session.quit
+ end
+ end
+ end
+end
diff --git a/plugins/chat/spec/system/shortcuts/chat_composer_spec.rb b/plugins/chat/spec/system/shortcuts/chat_composer_spec.rb
index 457b1e0d3f9..624851f28da 100644
--- a/plugins/chat/spec/system/shortcuts/chat_composer_spec.rb
+++ b/plugins/chat/spec/system/shortcuts/chat_composer_spec.rb
@@ -92,11 +92,10 @@ RSpec.describe "Shortcuts | chat composer", type: :system, js: true do
fab!(:message_1) do
Fabricate(:chat_message, message: "message 1", chat_channel: channel_1, user: current_user)
end
- before { Fabricate(:chat_message, message: "message 2", chat_channel: channel_1) }
+ fab!(:message_2) { Fabricate(:chat_message, message: "message 2", chat_channel: channel_1) }
it "edits last editable message" do
chat.visit_channel(channel_1)
- expect(channel_page).to have_message(id: message_1.id)
find(".chat-composer__input").send_keys(:arrow_up)
@@ -116,5 +115,15 @@ RSpec.describe "Shortcuts | chat composer", type: :system, js: true do
page.driver.browser.network_conditions = { offline: false }
end
end
+
+ context "with shift" do
+ it "starts replying to the last message" do
+ chat.visit_channel(channel_1)
+
+ find(".chat-composer__input").send_keys(%i[shift arrow_up])
+
+ expect(channel_page).to be_replying_to(message_2)
+ end
+ end
end
end
diff --git a/plugins/chat/spec/system/single_thread_spec.rb b/plugins/chat/spec/system/single_thread_spec.rb
index cf6978a42fa..3300e83a7c5 100644
--- a/plugins/chat/spec/system/single_thread_spec.rb
+++ b/plugins/chat/spec/system/single_thread_spec.rb
@@ -123,8 +123,8 @@ describe "Single thread in side panel", type: :system, js: true do
chat_page.visit_channel(channel)
channel_page.message_thread_indicator(thread.original_message).click
expect(side_panel).to have_open_thread(thread)
- thread_page.send_message(thread.id, "new thread message")
- expect(thread_page).to have_message(thread.id, text: "new thread message")
+ thread_page.send_message("new thread message")
+ expect(thread_page).to have_message(thread_id: thread.id, text: "new thread message")
thread_message = thread.replies.last
expect(thread_message.chat_channel_id).to eq(channel.id)
expect(thread_message.thread.channel_id).to eq(channel.id)
@@ -134,8 +134,8 @@ describe "Single thread in side panel", type: :system, js: true do
chat_page.visit_channel(channel)
channel_page.message_thread_indicator(thread.original_message).click
expect(side_panel).to have_open_thread(thread)
- thread_page.send_message(thread.id, "new thread message")
- expect(thread_page).to have_message(thread.id, text: "new thread message")
+ thread_page.send_message("new thread message")
+ expect(thread_page).to have_message(thread_id: thread.id, text: "new thread message")
thread_message = thread.reload.replies.last
expect(channel_page).not_to have_css(channel_page.message_by_id_selector(thread_message.id))
end
@@ -157,19 +157,19 @@ describe "Single thread in side panel", type: :system, js: true do
using_session(:tab_2) do
expect(side_panel).to have_open_thread(thread)
- thread_page.send_message(thread.id, "the other user message")
- expect(thread_page).to have_message(thread.id, text: "the other user message")
+ thread_page.send_message("the other user message")
+ expect(thread_page).to have_message(thread_id: thread.id, text: "the other user message")
end
using_session(:tab_1) do
expect(side_panel).to have_open_thread(thread)
- expect(thread_page).to have_message(thread.id, text: "the other user message")
- thread_page.send_message(thread.id, "this is a test message")
- expect(thread_page).to have_message(thread.id, text: "this is a test message")
+ expect(thread_page).to have_message(thread_id: thread.id, text: "the other user message")
+ thread_page.send_message("this is a test message")
+ expect(thread_page).to have_message(thread_id: thread.id, text: "this is a test message")
end
using_session(:tab_2) do
- expect(thread_page).to have_message(thread.id, text: "this is a test message")
+ expect(thread_page).to have_message(thread_id: thread.id, text: "this is a test message")
end
end