diff --git a/plugins/chat/assets/javascripts/discourse/initializers/chat-setup.js b/plugins/chat/assets/javascripts/discourse/initializers/chat-setup.js index b0ad79f345f..dd3ac70dd3b 100644 --- a/plugins/chat/assets/javascripts/discourse/initializers/chat-setup.js +++ b/plugins/chat/assets/javascripts/discourse/initializers/chat-setup.js @@ -117,6 +117,14 @@ export default { api.addToHeaderIcons("chat-header-icon"); + api.addChatDrawerStateCallback(({ isDrawerActive }) => { + if (isDrawerActive) { + document.body.classList.add("chat-drawer-active"); + } else { + document.body.classList.remove("chat-drawer-active"); + } + }); + api.decorateChatMessage(function (chatMessage, chatChannel) { if (!this.currentUser) { return; diff --git a/plugins/chat/assets/javascripts/discourse/pre-initializers/chat-plugin-api.js b/plugins/chat/assets/javascripts/discourse/pre-initializers/chat-plugin-api.js index 15c94c5ff66..d562718727d 100644 --- a/plugins/chat/assets/javascripts/discourse/pre-initializers/chat-plugin-api.js +++ b/plugins/chat/assets/javascripts/discourse/pre-initializers/chat-plugin-api.js @@ -4,6 +4,7 @@ import { resetChatMessageDecorators, } from "discourse/plugins/chat/discourse/components/chat-message"; import { registerChatComposerButton } from "discourse/plugins/chat/discourse/lib/chat-composer-buttons"; +import { addChatDrawerStateCallback } from "discourse/plugins/chat/discourse/services/chat-state-manager"; /** * Class exposing the javascript API available to plugins and themes. @@ -19,6 +20,15 @@ import { registerChatComposerButton } from "discourse/plugins/chat/discourse/lib * @param {ChatChannel} chatChannel - model */ +/** + * Callback used to decorate a chat message + * + * @callback PluginApi~chatDrawerStateCallbak + * @param {Object} state + * @param {boolean} state.isDrawerActive - is the chat drawer active + * @param {boolean} state.isDrawerExpanded - is the chat drawer expanded + */ + /** * Decorate a chat message * @@ -65,6 +75,22 @@ import { registerChatComposerButton } from "discourse/plugins/chat/discourse/lib * }); */ +/** + * Callback when the sate of the chat drawer changes + * + * @memberof PluginApi + * @instance + * @function addChatDrawerStateCallback + * @param {PluginApi~chatDrawerStateCallbak} callback + * @example + * + * api.addChatDrawerStateCallback(({isDrawerExpanded, isDrawerActive}) => { + * if (isDrawerActive && isDrawerExpanded) { + * // do something + * } + * }); + */ + export default { name: "chat-plugin-api", after: "inject-discourse-objects", @@ -88,6 +114,14 @@ export default { }, }); } + + if (!apiPrototype.hasOwnProperty("addChatDrawerStateCallback")) { + Object.defineProperty(apiPrototype, "addChatDrawerStateCallback", { + value(callback) { + addChatDrawerStateCallback(callback); + }, + }); + } }); }, 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 466f9eb1da1..79f67e15a2e 100644 --- a/plugins/chat/assets/javascripts/discourse/services/chat-state-manager.js +++ b/plugins/chat/assets/javascripts/discourse/services/chat-state-manager.js @@ -9,6 +9,15 @@ const PREFERRED_MODE_STORE_NAMESPACE = "discourse_chat_"; const FULL_PAGE_CHAT = "FULL_PAGE_CHAT"; const DRAWER_CHAT = "DRAWER_CHAT"; +let chatDrawerStateCallbacks = []; + +export function addChatDrawerStateCallback(callback) { + chatDrawerStateCallbacks.push(callback); +} + +export function resetChatDrawerStateCallbacks() { + chatDrawerStateCallbacks = []; +} export default class ChatStateManager extends Service { @service chat; @service router; @@ -51,12 +60,14 @@ export default class ChatStateManager extends Service { } this.chat.updatePresence(); + this.#publishStateChange(); } didCloseDrawer() { this.set("isDrawerActive", false); this.set("isDrawerExpanded", false); this.chat.updatePresence(); + this.#publishStateChange(); } didExpandDrawer() { @@ -68,11 +79,13 @@ export default class ChatStateManager extends Service { didCollapseDrawer() { this.set("isDrawerActive", true); this.set("isDrawerExpanded", false); + this.#publishStateChange(); } didToggleDrawer() { this.set("isDrawerExpanded", !this.isDrawerExpanded); this.set("isDrawerActive", true); + this.#publishStateChange(); } get isFullPagePreferred() { @@ -119,4 +132,13 @@ export default class ChatStateManager extends Service { get lastKnownChatURL() { return this._chatURL || "/chat"; } + + #publishStateChange() { + const state = { + isDrawerActive: this.isDrawerActive, + isDrawerExpanded: this.isDrawerExpanded, + }; + + chatDrawerStateCallbacks.forEach((callback) => callback(state)); + } } diff --git a/plugins/chat/spec/system/drawer_spec.rb b/plugins/chat/spec/system/drawer_spec.rb index 60877091745..1807887a92f 100644 --- a/plugins/chat/spec/system/drawer_spec.rb +++ b/plugins/chat/spec/system/drawer_spec.rb @@ -54,6 +54,20 @@ RSpec.describe "Drawer", type: :system, js: true do end end + context "when toggling open/close" do + it "toggles a css class on body" do + visit("/") + + chat_page.open_from_header + + expect(page.find("body.chat-drawer-active")).to be_visible + + drawer.close + + expect(page.find("body:not(.chat-drawer-active)")).to be_visible + end + end + context "when going from drawer to full page" do fab!(:channel_1) { Fabricate(:chat_channel) } fab!(:channel_2) { Fabricate(:chat_channel) } diff --git a/plugins/chat/test/javascripts/unit/services/chat-state-manager-test.js b/plugins/chat/test/javascripts/unit/services/chat-state-manager-test.js index cca77828a08..7c7ba806b2e 100644 --- a/plugins/chat/test/javascripts/unit/services/chat-state-manager-test.js +++ b/plugins/chat/test/javascripts/unit/services/chat-state-manager-test.js @@ -2,6 +2,11 @@ import { module, test } from "qunit"; import { setupTest } from "ember-qunit"; import Site from "discourse/models/site"; import sinon from "sinon"; +import { getOwner } from "discourse-common/lib/get-owner"; +import { + addChatDrawerStateCallback, + resetChatDrawerStateCallbacks, +} from "discourse/plugins/chat/discourse/services/chat-state-manager"; module( "Discourse Chat | Unit | Service | chat-state-manager", @@ -9,7 +14,7 @@ module( setupTest(hooks); hooks.beforeEach(function () { - this.subject = this.owner.lookup("service:chat-state-manager"); + this.subject = getOwner(this).lookup("service:chat-state-manager"); }); hooks.afterEach(function () { @@ -129,5 +134,24 @@ module( assert.strictEqual(this.subject.lastKnownChatURL, "/foo"); sinon.assert.calledTwice(stub); }); + + test("callbacks", function (assert) { + this.state = null; + addChatDrawerStateCallback((state) => { + this.state = state; + }); + + this.subject.didOpenDrawer(); + + assert.strictEqual(this.state.isDrawerActive, true); + assert.strictEqual(this.state.isDrawerExpanded, true); + + this.subject.didCloseDrawer(); + + assert.strictEqual(this.state.isDrawerActive, false); + assert.strictEqual(this.state.isDrawerExpanded, false); + + resetChatDrawerStateCallbacks(); + }); } );