diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-channel.gjs b/plugins/chat/assets/javascripts/discourse/components/chat-channel.gjs index 98a1caaf077..bdaab6a9d9d 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-channel.gjs +++ b/plugins/chat/assets/javascripts/discourse/components/chat-channel.gjs @@ -54,7 +54,6 @@ export default class ChatChannel extends Component { @service chat; @service chatApi; @service chatChannelsManager; - @service chatComposerPresenceManager; @service chatDraftsManager; @service chatEmojiPickerManager; @service chatStateManager; diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-composer.js b/plugins/chat/assets/javascripts/discourse/components/chat-composer.js index 718950e8f26..6fb08faee38 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-composer.js +++ b/plugins/chat/assets/javascripts/discourse/components/chat-composer.js @@ -30,13 +30,15 @@ import { chatComposerButtons } from "discourse/plugins/chat/discourse/lib/chat-c import ChatMessageInteractor from "discourse/plugins/chat/discourse/lib/chat-message-interactor"; import TextareaInteractor from "discourse/plugins/chat/discourse/lib/textarea-interactor"; +const CHAT_PRESENCE_KEEP_ALIVE = 5 * 1000; // 5 seconds + export default class ChatComposer extends Component { @service capabilities; @service site; @service siteSettings; @service store; @service chat; - @service chatComposerPresenceManager; + @service composerPresenceManager; @service chatComposerWarningsTracker; @service appEvents; @service chatEmojiReactionStore; @@ -256,9 +258,10 @@ export default class ChatComposer extends Component { return; } - this.chatComposerPresenceManager.notifyState( + this.composerPresenceManager.notifyState( this.presenceChannelName, - !this.draft.editing && this.hasContent + !this.draft.editing && this.hasContent, + CHAT_PRESENCE_KEEP_ALIVE ); } diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-thread.gjs b/plugins/chat/assets/javascripts/discourse/components/chat-thread.gjs index d58797bcc83..b5020270b96 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-thread.gjs +++ b/plugins/chat/assets/javascripts/discourse/components/chat-thread.gjs @@ -46,7 +46,6 @@ export default class ChatThread extends Component { @service capabilities; @service chat; @service chatApi; - @service chatComposerPresenceManager; @service chatHistory; @service chatDraftsManager; @service chatThreadComposer; diff --git a/plugins/chat/assets/javascripts/discourse/services/chat-composer-presence-manager.js b/plugins/chat/assets/javascripts/discourse/services/chat-composer-presence-manager.js deleted file mode 100644 index bba9c23e5b8..00000000000 --- a/plugins/chat/assets/javascripts/discourse/services/chat-composer-presence-manager.js +++ /dev/null @@ -1,54 +0,0 @@ -import { cancel, debounce } from "@ember/runloop"; -import Service, { service } from "@ember/service"; -import { isTesting } from "discourse-common/config/environment"; - -const KEEP_ALIVE_DURATION_SECONDS = 10; - -// This service is loosely based on discourse-presence's ComposerPresenceManager service -// It is a singleton which receives notifications each time the value of the chat composer changes -// This service ensures that a single browser can only be 'replying' to a single chatChannel at -// one time, and automatically 'leaves' the channel if the composer value hasn't changed for 10 seconds -export default class ChatComposerPresenceManager extends Service { - @service presence; - - willDestroy() { - this.leave(); - } - - notifyState(channelName, replying) { - if (!replying) { - this.leave(); - return; - } - - if (this._channelName !== channelName) { - this._enter(channelName); - this._channelName = channelName; - } - - if (!isTesting()) { - this._autoLeaveTimer = debounce( - this, - this.leave, - KEEP_ALIVE_DURATION_SECONDS * 1000 - ); - } - } - - leave() { - this._presentChannel?.leave(); - this._presentChannel = null; - this._channelName = null; - if (this._autoLeaveTimer) { - cancel(this._autoLeaveTimer); - this._autoLeaveTimer = null; - } - } - - _enter(channelName) { - this.leave(); - - this._presentChannel = this.presence.getChannel(channelName); - this._presentChannel.enter(); - } -} diff --git a/plugins/discourse-presence/assets/javascripts/discourse/components/composer-presence-display.gjs b/plugins/discourse-presence/assets/javascripts/discourse/components/composer-presence-display.gjs index c4a90414c36..7da31830ce8 100644 --- a/plugins/discourse-presence/assets/javascripts/discourse/components/composer-presence-display.gjs +++ b/plugins/discourse-presence/assets/javascripts/discourse/components/composer-presence-display.gjs @@ -18,60 +18,58 @@ export default class ComposerPresenceDisplay extends Component { @tracked editChannel; setupReplyChannel = helperFn((_, on) => { - const topic = this.args.model.topic; + const { topic } = this.args.model; + if (!topic || !this.isReply) { return; } - const replyChannel = this.presence.getChannel( - `/discourse-presence/reply/${topic.id}` - ); - replyChannel.subscribe(); + const name = `/discourse-presence/reply/${topic.id}`; + const replyChannel = this.presence.getChannel(name); this.replyChannel = replyChannel; + replyChannel.subscribe(); on.cleanup(() => replyChannel.unsubscribe()); }); setupWhisperChannel = helperFn((_, on) => { - if ( - !this.args.model.topic || - !this.isReply || - !this.currentUser.staff || - !this.currentUser.whisperer - ) { + const { topic } = this.args.model; + const { whisperer } = this.currentUser; + + if (!topic || !this.isReply || !whisperer) { return; } - const whisperChannel = this.presence.getChannel( - `/discourse-presence/whisper/${this.args.model.topic.id}` - ); - whisperChannel.subscribe(); + const name = `/discourse-presence/whisper/${topic.id}`; + const whisperChannel = this.presence.getChannel(name); this.whisperChannel = whisperChannel; + whisperChannel.subscribe(); on.cleanup(() => whisperChannel.unsubscribe()); }); setupEditChannel = helperFn((_, on) => { - if (!this.args.model.post || !this.isEdit) { + const { post } = this.args.model; + + if (!post || !this.isEdit) { return; } - const editChannel = this.presence.getChannel( - `/discourse-presence/edit/${this.args.model.post.id}` - ); - editChannel.subscribe(); + const name = `/discourse-presence/edit/${post.id}`; + const editChannel = this.presence.getChannel(name); this.editChannel = editChannel; + editChannel.subscribe(); on.cleanup(() => editChannel.unsubscribe()); }); notifyState = helperFn((_, on) => { - const { topic, post, reply } = this.args.model; - const raw = this.isEdit ? post?.raw || "" : ""; + const { topic, post, replyDirty } = this.args.model; const entity = this.isEdit ? post : topic; - if (reply !== raw) { - this.composerPresenceManager.notifyState(this.state, entity?.id); + if (entity) { + const name = `/discourse-presence/${this.state}/${entity.id}`; + this.composerPresenceManager.notifyState(name, replyDirty); } on.cleanup(() => this.composerPresenceManager.leave()); @@ -85,12 +83,15 @@ export default class ComposerPresenceDisplay extends Component { return this.state === "edit"; } + @cached get state() { - if (this.args.model.editingPost) { + const { editingPost, whisper, replyingToTopic } = this.args.model; + + if (editingPost) { return "edit"; - } else if (this.args.model.whisper) { + } else if (whisper) { return "whisper"; - } else if (this.args.model.replyingToTopic) { + } else if (replyingToTopic) { return "reply"; } } @@ -98,6 +99,7 @@ export default class ComposerPresenceDisplay extends Component { @cached get users() { let users; + if (this.isEdit) { users = this.editChannel?.users || []; } else { diff --git a/plugins/discourse-presence/assets/javascripts/discourse/components/topic-presence-display.gjs b/plugins/discourse-presence/assets/javascripts/discourse/components/topic-presence-display.gjs index 0431b6928b7..e85890bb6bf 100644 --- a/plugins/discourse-presence/assets/javascripts/discourse/components/topic-presence-display.gjs +++ b/plugins/discourse-presence/assets/javascripts/discourse/components/topic-presence-display.gjs @@ -15,26 +15,33 @@ export default class TopicPresenceDisplay extends Component { @tracked whisperChannel; setupReplyChannel = helperFn((_, on) => { - const replyChannel = this.presence.getChannel( - `/discourse-presence/reply/${this.args.topic.id}` - ); - replyChannel.subscribe(); - this.replyChannel = replyChannel; + const { topic } = this.args; - on.cleanup(() => replyChannel.unsubscribe()); - }); - - setupWhisperChannels = helperFn((_, on) => { - if (!this.currentUser.staff) { + if (!topic) { return; } - const whisperChannel = this.presence.getChannel( - `/discourse-presence/whisper/${this.args.topic.id}` - ); - whisperChannel.subscribe(); + const name = `/discourse-presence/reply/${topic.id}`; + const replyChannel = this.presence.getChannel(name); + this.replyChannel = replyChannel; + + replyChannel.subscribe(); + on.cleanup(() => replyChannel.unsubscribe()); + }); + + setupWhisperChannel = helperFn((_, on) => { + const { topic } = this.args; + const { whisperer } = this.currentUser; + + if (!topic || !whisperer) { + return; + } + + const name = `/discourse-presence/whisper/${topic.id}`; + const whisperChannel = this.presence.getChannel(name); this.whisperChannel = whisperChannel; + whisperChannel.subscribe(); on.cleanup(() => whisperChannel.unsubscribe()); }); @@ -50,7 +57,7 @@ export default class TopicPresenceDisplay extends Component {