FEATURE: Initial chat thread indicator and disabling echo mode in channels (#21047)

This commit introduces a new thread indicator for channels with `threading_enabled`
set to true and the `enable_exp` site setting set to true. In addition, in the main channel
stream we now hide all messages that are linked to threads except for the original message,
disabling the concept of an "echo mode" for now, we may revisit this in future. We also
remove the jigsaw puzzle "Open Thread" button for message actions, since the thread
indicator can just be used instead.

This also stops the `Chat::Publisher` from sending any messages related to chat
messages that are linked to a thread, unless that chat message is the OM of the
thread. A subsequent PR will link up all MessageBus events within the thread panel,
and for the message indicators.

Another subsequent PR will add the excerpt of the latest message in each thread,
as well as the avatars of the users messaging in the thread.

Co-authored-by: Joffrey JAFFEUX <j.jaffeux@gmail.com>
This commit is contained in:
Martin Brennan
2023-04-12 11:09:06 +10:00
committed by GitHub
co-authored by Joffrey JAFFEUX
parent f0435844df
commit 584a17c948
26 changed files with 506 additions and 79 deletions
@@ -35,7 +35,10 @@
@value={{readonly this.value}}
@input={{action "onTextareaInput" value="target.value"}}
@type="text"
@class="chat-composer-input"
@class={{concat-class
"chat-composer-input"
(concat "chat-composer-input--" @context)
}}
@disabled={{this.disableComposer}}
@autocorrect="on"
@autocapitalize="sentences"
@@ -550,6 +550,19 @@ export default class ChatLivePane extends Component {
case "flag":
this.handleFlaggedMessage(data);
break;
case "thread_created":
this.handleThreadCreated(data);
break;
}
}
handleThreadCreated(data) {
const message = this.args.channel.messagesManager.findMessage(
data.chat_message.id
);
if (message) {
message.threadId = data.chat_message.thread_id;
message.threadReplyCount = 1;
}
}
@@ -773,7 +786,14 @@ export default class ChatLivePane extends Component {
stagedMessage.inReplyTo = this.chatChannelComposer.replyToMsg;
}
this.args.channel.messagesManager.addMessages([stagedMessage]);
if (stagedMessage.inReplyTo) {
if (!this.args.channel.threadingEnabled) {
this.args.channel.messagesManager.addMessages([stagedMessage]);
}
} else {
this.args.channel.messagesManager.addMessages([stagedMessage]);
}
if (!this.args.channel.messagesManager.canLoadMoreFuture) {
this.scrollToLatestMessage();
}
@@ -50,15 +50,6 @@
/>
{{/if}}
{{#if this.messageInteractor.canOpenThread}}
<DButton
@class="btn-flat chat-message-thread-btn"
@action={{this.messageInteractor.openThread}}
@icon="puzzle-piece"
@title="chat.threads.open"
/>
{{/if}}
{{#if this.messageInteractor.secondaryButtons.length}}
<DropdownSelectBox
@class="more-buttons"
@@ -0,0 +1,12 @@
<LinkTo
@route="chat.channel.thread"
@models={{@message.threadRouteModels}}
class="chat-message-thread-indicator"
>
<span class="chat-message-thread-indicator__replies-count">
{{i18n "chat.thread.replies" count=@message.threadReplyCount}}
</span>
<span class="chat-message-thread-indicator__view-thread">
{{i18n "chat.thread.view_thread"}}
</span>
</LinkTo>
@@ -0,0 +1,3 @@
import Component from "@glimmer/component";
export default class ChatMessageThreadIndicator extends Component {}
@@ -21,6 +21,7 @@
(if @message.highlighted "highlighted")
}}
data-id={{@message.id}}
data-thread-id={{@message.threadId}}
{{chat/track-message
(hash
didEnterViewport=(fn @messageDidEnterViewport @message)
@@ -61,6 +62,7 @@
(if @message.staged "chat-message-staged")
(if @message.deletedAt "deleted")
(if (and @message.inReplyTo (not this.hideReplyToInfo)) "is-reply")
(if this.showThreadIndicator "is-threaded")
(if this.hideUserInfo "user-info-hidden")
(if @message.error "errored")
(if @message.bookmark "chat-message-bookmarked")
@@ -189,6 +191,10 @@
</div>
{{/if}}
</div>
{{#if this.showThreadIndicator}}
<ChatMessageThreadIndicator @message={{@message}} />
{{/if}}
</div>
{{/if}}
{{/if}}
@@ -1,8 +1,8 @@
import { isTesting } from "discourse-common/config/environment";
import { action } from "@ember/object";
import Component from "@glimmer/component";
import I18n from "I18n";
import optionalService from "discourse/lib/optional-service";
import { action } from "@ember/object";
import { ajax } from "discourse/lib/ajax";
import { cancel, schedule } from "@ember/runloop";
import { inject as service } from "@ember/service";
@@ -260,7 +260,21 @@ export default class ChatMessage extends Component {
return (
this.args.context === MESSAGE_CONTEXT_THREAD ||
this.args.message?.inReplyTo?.id ===
this.args.message?.previousMessage?.id
this.args.message?.previousMessage?.id ||
this.threadingEnabled
);
}
get threadingEnabled() {
return this.args.channel?.threadingEnabled && this.args.message?.threadId;
}
get showThreadIndicator() {
return (
this.args.context !== MESSAGE_CONTEXT_THREAD &&
this.threadingEnabled &&
this.args.message?.threadId !==
this.args.message?.previousMessage?.threadId
);
}
@@ -120,14 +120,6 @@ export default class ChatMessageInteractor {
);
}
get canOpenThread() {
return (
this.context !== MESSAGE_CONTEXT_THREAD &&
this.message.channel?.threadingEnabled &&
this.message?.threadId
);
}
get canRebakeMessage() {
return (
this.currentUser?.staff &&
@@ -212,14 +204,6 @@ export default class ChatMessageInteractor {
});
}
if (this.canOpenThread) {
buttons.push({
id: "openThread",
name: I18n.t("chat.threads.open"),
icon: "puzzle-piece",
});
}
return buttons;
}
@@ -364,15 +348,6 @@ export default class ChatMessageInteractor {
this.composer.editButtonClicked(this.message.id);
}
@action
openThread() {
this.router.transitionTo(
"chat.channel.thread",
...this.message.channel.routeModels,
this.message.threadId
);
}
@action
openEmojiPicker(_, { target }) {
const pickerState = {
@@ -31,6 +31,7 @@ export default class ChatMessage {
@tracked excerpt;
@tracked message;
@tracked threadId;
@tracked threadReplyCount;
@tracked reactions;
@tracked reviewableId;
@tracked user;
@@ -59,6 +60,7 @@ export default class ChatMessage {
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;
@@ -81,6 +83,10 @@ export default class ChatMessage {
this.bookmark = args.bookmark ? Bookmark.create(args.bookmark) : null;
}
get threadRouteModels() {
return [...this.channel.routeModels, this.threadId];
}
get read() {
return this.channel.currentUserMembership?.last_read_message_id >= this.id;
}