From 550895a97023c66106a38533140f19bf7420dcca Mon Sep 17 00:00:00 2001 From: Joffrey JAFFEUX Date: Thu, 1 Feb 2024 18:27:38 +0100 Subject: [PATCH] FEATURE: adds a link to original message (#25503) This commit adds a link to the original message of a thread, this link will: - load the channel message and highlight it while keeping thread panel open on desktop - open the channel and highlight the message in mobile (and close thread panel, as mobile never shows channel and thread in the same view) Co-authored-by: chapoi <101828855+chapoi@users.noreply.github.com> --- .../javascripts/discourse/chat-route-map.js | 3 ++ .../discourse/components/chat-message.gjs | 13 +++-- .../components/chat/message/info.gjs | 42 ++++++++++++++-- .../discourse/models/chat-message.js | 4 ++ .../chat-channel-near-message-with-thread.js | 30 +++++++++++ .../stylesheets/common/chat-message-info.scss | 50 ++++++++++++++----- plugins/chat/config/locales/client.en.yml | 1 + .../chat/spec/system/single_thread_spec.rb | 21 +++++++- 8 files changed, 141 insertions(+), 23 deletions(-) create mode 100644 plugins/chat/assets/javascripts/discourse/routes/chat-channel-near-message-with-thread.js diff --git a/plugins/chat/assets/javascripts/discourse/chat-route-map.js b/plugins/chat/assets/javascripts/discourse/chat-route-map.js index ef2da85c5a1..bd3826c0adc 100644 --- a/plugins/chat/assets/javascripts/discourse/chat-route-map.js +++ b/plugins/chat/assets/javascripts/discourse/chat-route-map.js @@ -2,6 +2,9 @@ export default function () { this.route("chat", function () { this.route("channel", { path: "/c/:channelTitle/:channelId" }, function () { this.route("near-message", { path: "/:messageId" }); + this.route("near-message-with-thread", { + path: "/:messageId/t/:threadId", + }); this.route("threads", { path: "/t" }); this.route("thread", { path: "/t/:threadId" }, function () { this.route("near-message", { path: "/:messageId" }); diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-message.gjs b/plugins/chat/assets/javascripts/discourse/components/chat-message.gjs index 3504fa40a4d..53afda8f2de 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-message.gjs +++ b/plugins/chat/assets/javascripts/discourse/components/chat-message.gjs @@ -99,9 +99,7 @@ export default class ChatMessage extends Component { } get pane() { - return this.args.context === MESSAGE_CONTEXT_THREAD - ? this.chatThreadPane - : this.chatChannelPane; + return this.threadContext ? this.chatThreadPane : this.chatChannelPane; } get messageInteractor() { @@ -459,7 +457,7 @@ export default class ChatMessage extends Component { get hideReplyToInfo() { return ( - this.args.context === MESSAGE_CONTEXT_THREAD || + this.threadContext || this.args.message?.inReplyTo?.id === this.args.message?.previousMessage?.id || this.threadingEnabled @@ -475,13 +473,17 @@ export default class ChatMessage extends Component { get showThreadIndicator() { return ( - this.args.context !== MESSAGE_CONTEXT_THREAD && + !this.threadContext && this.threadingEnabled && this.args.message?.thread && this.args.message?.thread.preview.replyCount > 0 ); } + get threadContext() { + return this.args.context === MESSAGE_CONTEXT_THREAD; + } + #teardownMentionedUsers() { this.args.message.mentionedUsers.forEach((user) => { user.stopTrackingStatus(); @@ -573,6 +575,7 @@ export default class ChatMessage extends Component { @@ -141,6 +162,19 @@ export default class ChatMessageInfo extends Component { {{/if}} {{/if}} + + {{#if (and @threadContext @message.isOriginalThreadMessage)}} + + + {{i18n "chat.see_in"}} + + + + {{/if}} {{/if}} diff --git a/plugins/chat/assets/javascripts/discourse/models/chat-message.js b/plugins/chat/assets/javascripts/discourse/models/chat-message.js index 1e20cb906f7..def8a64b05f 100644 --- a/plugins/chat/assets/javascripts/discourse/models/chat-message.js +++ b/plugins/chat/assets/javascripts/discourse/models/chat-message.js @@ -160,6 +160,10 @@ export default class ChatMessage { return this.channel.currentUserMembership?.lastReadMessageId >= this.id; } + get isOriginalThreadMessage() { + return this.thread?.originalMessage?.id === this.id; + } + @cached get index() { return this.manager?.messages?.indexOf(this); diff --git a/plugins/chat/assets/javascripts/discourse/routes/chat-channel-near-message-with-thread.js b/plugins/chat/assets/javascripts/discourse/routes/chat-channel-near-message-with-thread.js new file mode 100644 index 00000000000..e1aed35196c --- /dev/null +++ b/plugins/chat/assets/javascripts/discourse/routes/chat-channel-near-message-with-thread.js @@ -0,0 +1,30 @@ +import { inject as service } from "@ember/service"; +import DiscourseRoute from "discourse/routes/discourse"; + +// This route is only here as a convenience method for a clean `/c/:channelTitle/:channelId/:messageId/t/:threadId` URL. +// It's not a real route, it just redirects to the real route after setting a param on the controller. +export default class ChatChannelNearMessageWithThread extends DiscourseRoute { + @service router; + @service site; + + beforeModel() { + const channel = this.modelFor("chat-channel"); + const { messageId, threadId } = this.paramsFor(this.routeName); + this.controllerFor("chat-channel").set("messageId", null); + + if ( + messageId || + this.controllerFor("chat-channel").get("targetMessageId") + ) { + this.controllerFor("chat-channel").set("targetMessageId", messageId); + } + + if (threadId && this.site.desktopView) { + this.router.replaceWith( + "chat.channel.thread", + ...channel.routeModels, + threadId + ); + } + } +} diff --git a/plugins/chat/assets/stylesheets/common/chat-message-info.scss b/plugins/chat/assets/stylesheets/common/chat-message-info.scss index f7c40db33a6..2bc7dc7b983 100644 --- a/plugins/chat/assets/stylesheets/common/chat-message-info.scss +++ b/plugins/chat/assets/stylesheets/common/chat-message-info.scss @@ -2,6 +2,7 @@ display: flex; align-items: center; justify-content: flex-start; + gap: 0.25rem; } .chat-message-info__username { @@ -10,7 +11,6 @@ & + .chat-message-info__bot-indicator, & + .chat-message-info__date { - margin-left: 0.25em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; @@ -26,14 +26,10 @@ .chat-message-info__bot-indicator { text-transform: uppercase; - padding: 0.25em; + padding: 0.25rem; background: var(--primary-low); border-radius: var(--d-border-radius); font-size: var(--font-down-2); - - & + .chat-message-info__date { - margin-left: 0.25em; - } } .chat-message-info__date { @@ -46,10 +42,6 @@ color: var(--primary); } } - - & + .chat-message-info__flag { - margin-left: 0.25em; - } } .chat-message-info__flag { @@ -61,17 +53,49 @@ .d-icon-bookmark { color: var(--primary-low-mid); font-size: var(--font-down-2); - margin-left: 0.5em; + margin-left: 0.5rem; } } .chat-message-info__status { display: flex; - margin-left: 0.2em; - margin-right: 0.2em; + margin-left: 0.2rem; + margin-right: 0.2rem; .emoji { width: 16px; height: 16px; } } + +.chat-message-info__original-message { + display: flex; + align-items: center; + font-size: var(--font-down-2); + color: var(--primary-high); + gap: 0.5em; + line-height: 1.2; + background: var(--tertiary-very-low); + padding: 0.25em 0.75em; + border-radius: 8px; + + .chat-channel-title { + gap: 0.25em; + } + + .chat-channel-name { + color: var(--tertiary); + } + + .chat-channel-icon { + .d-icon { + height: 0.9em; + width: 0.9em; + } + } + + &__text, + &__text:visited { + color: var(--primary); + } +} diff --git a/plugins/chat/config/locales/client.en.yml b/plugins/chat/config/locales/client.en.yml index bbdb2ae2e6b..ad8d3adefe0 100644 --- a/plugins/chat/config/locales/client.en.yml +++ b/plugins/chat/config/locales/client.en.yml @@ -51,6 +51,7 @@ en: cancel_reply: "Cancel reply" chat_channels: "Channels" browse_all_channels: "Browse all channels" + see_in: "See in" move_to_channel: title: "Move messages to channel" instructions: diff --git a/plugins/chat/spec/system/single_thread_spec.rb b/plugins/chat/spec/system/single_thread_spec.rb index c69afe94f6d..3cd32345c4b 100644 --- a/plugins/chat/spec/system/single_thread_spec.rb +++ b/plugins/chat/spec/system/single_thread_spec.rb @@ -15,7 +15,7 @@ describe "Single thread in side panel", type: :system do sign_in(current_user) end - context "when threading_enabled is false for the channel" do + context "when threading is disabled for the channel" do fab!(:channel) { Fabricate(:chat_channel) } before { channel.update!(threading_enabled: false) } @@ -82,6 +82,17 @@ describe "Single thread in side panel", type: :system do expect(chat_drawer_page).to have_open_channel(channel) end + it "highlights the message in the channel when clicking original message link" do + chat_page.visit_thread(thread) + + find(".chat-message-info__original-message").click + + expect(channel_page.messages).to have_message( + id: thread.original_message.id, + highlighted: true, + ) + end + it "opens the side panel for a single thread from the indicator" do chat_page.visit_channel(channel) channel_page.message_thread_indicator(thread.original_message).click @@ -194,6 +205,14 @@ describe "Single thread in side panel", type: :system do expect(side_panel).to have_open_thread(thread) end + + it "navigates back to channel when clicking original message link", mobile: true do + chat_page.visit_thread(thread) + + find(".chat-message-info__original-message").click + + expect(page).to have_current_path("/chat/c/#{channel.slug}/#{channel.id}") + end end context "when messages are separated by a day" do