diff --git a/app/assets/javascripts/discourse/app/components/sidebar/switch-panel-buttons.hbs b/app/assets/javascripts/discourse/app/components/sidebar/switch-panel-buttons.hbs index e558957c56c..214eec48cc6 100644 --- a/app/assets/javascripts/discourse/app/components/sidebar/switch-panel-buttons.hbs +++ b/app/assets/javascripts/discourse/app/components/sidebar/switch-panel-buttons.hbs @@ -5,5 +5,6 @@ @icon={{button.switchButtonIcon}} @disabled={{this.isSwitching}} @translatedLabel={{button.switchButtonLabel}} + data-key={{button.key}} /> {{/each}} \ No newline at end of file diff --git a/app/assets/javascripts/discourse/app/components/sidebar/switch-panel-buttons.js b/app/assets/javascripts/discourse/app/components/sidebar/switch-panel-buttons.js index 329bf69863e..613de5909d2 100644 --- a/app/assets/javascripts/discourse/app/components/sidebar/switch-panel-buttons.js +++ b/app/assets/javascripts/discourse/app/components/sidebar/switch-panel-buttons.js @@ -16,9 +16,13 @@ export default class SwitchPanelButtons extends Component { const url = panel.lastKnownURL || panel.switchButtonDefaultUrl; const destination = url === "/" ? `discovery.${defaultHomepage()}` : url; - this.router.transitionTo(destination).finally(() => { - this.isSwitching = false; - this.sidebarState.setPanel(panel.key); - }); + this.router + .transitionTo(destination) + .then(() => { + this.sidebarState.setPanel(panel.key); + }) + .finally(() => { + this.isSwitching = false; + }); } } diff --git a/app/models/user_option.rb b/app/models/user_option.rb index 87232465f5d..1fb652a271f 100644 --- a/app/models/user_option.rb +++ b/app/models/user_option.rb @@ -292,6 +292,7 @@ end # sidebar_link_to_filtered_list :boolean default(FALSE), not null # sidebar_show_count_of_new_items :boolean default(FALSE), not null # watched_precedence_over_muted :boolean +# chat_separate_sidebar_mode :integer default(0), not null # # Indexes # diff --git a/plugins/chat/app/models/chat/separate_sidebar_mode_site_setting.rb b/plugins/chat/app/models/chat/separate_sidebar_mode_site_setting.rb new file mode 100644 index 00000000000..f435457acfd --- /dev/null +++ b/plugins/chat/app/models/chat/separate_sidebar_mode_site_setting.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module Chat + class SeparateSidebarModeSiteSetting < EnumSiteSetting + def self.valid_value?(val) + values.any? { |v| v[:value] == val } + end + + def self.values + @values ||= [ + { name: "admin.site_settings.chat_separate_sidebar_mode.never", value: "never" }, + { name: "admin.site_settings.chat_separate_sidebar_mode.always", value: "always" }, + { name: "admin.site_settings.chat_separate_sidebar_mode.fullscreen", value: "fullscreen" }, + ] + end + + def self.translate_names? + true + end + end +end diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-drawer.js b/plugins/chat/assets/javascripts/discourse/components/chat-drawer.js index 7732db38cff..543143d4689 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-drawer.js +++ b/plugins/chat/assets/javascripts/discourse/components/chat-drawer.js @@ -171,8 +171,8 @@ export default Component.extend({ @action openURL(url = null) { this.chat.activeChannel = null; - this.chatStateManager.didOpenDrawer(url); this.chatDrawerRouter.stateFor(this._routeFromURL(url)); + this.chatStateManager.didOpenDrawer(url); }, _routeFromURL(url) { diff --git a/plugins/chat/assets/javascripts/discourse/components/chat/header/icon.hbs b/plugins/chat/assets/javascripts/discourse/components/chat/header/icon.hbs index 00fb2474a4f..3fd922ddef2 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat/header/icon.hbs +++ b/plugins/chat/assets/javascripts/discourse/components/chat/header/icon.hbs @@ -2,8 +2,9 @@ href={{this.href}} tabindex="0" class={{concat-class "icon" "btn-flat" (if this.isActive "active")}} + title={{i18n this.title}} > - {{d-icon "d-chat"}} + {{d-icon this.icon}} {{#unless this.currentUserInDnD}} <Chat::Header::Icon::UnreadIndicator @urgentCount={{@urgentCount}} diff --git a/plugins/chat/assets/javascripts/discourse/components/chat/header/icon.js b/plugins/chat/assets/javascripts/discourse/components/chat/header/icon.js index 6dbf81707d7..7491ec82d31 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat/header/icon.js +++ b/plugins/chat/assets/javascripts/discourse/components/chat/header/icon.js @@ -1,7 +1,7 @@ import { inject as service } from "@ember/service"; import Component from "@glimmer/component"; import getURL from "discourse-common/lib/get-url"; - +import { getUserChatSeparateSidebarMode } from "discourse/plugins/chat/discourse/lib/get-user-chat-separate-sidebar-mode"; export default class ChatHeaderIcon extends Component { @service currentUser; @service site; @@ -12,6 +12,10 @@ export default class ChatHeaderIcon extends Component { return this.args.currentUserInDnD || this.currentUser.isInDoNotDisturb(); } + get chatSeparateSidebarMode() { + return getUserChatSeparateSidebarMode(this.currentUser); + } + get isActive() { return ( this.args.isActive || @@ -20,13 +24,38 @@ export default class ChatHeaderIcon extends Component { ); } + get title() { + if ( + this.chatStateManager.isFullPageActive && + !this.chatSeparateSidebarMode.never + ) { + return "sidebar.panels.forum.label"; + } + + return "chat.title_capitalized"; + } + + get icon() { + if ( + this.chatStateManager.isFullPageActive && + !this.chatSeparateSidebarMode.never + ) { + return "random"; + } + + return "d-chat"; + } + get href() { - if (this.chatStateManager.isFullPageActive) { - if (this.site.mobileView) { - return getURL("/chat"); - } else { - return getURL(this.router.currentURL); - } + if (this.site.mobileView && this.chatStateManager.isFullPageActive) { + return getURL("/chat"); + } + + if ( + this.chatStateManager.isFullPageActive && + !this.chatSeparateSidebarMode.never + ) { + return getURL(this.chatStateManager.lastKnownAppURL || "/"); } if (this.chatStateManager.isDrawerActive) { diff --git a/plugins/chat/assets/javascripts/discourse/controllers/preferences-chat.js b/plugins/chat/assets/javascripts/discourse/controllers/preferences-chat.js index b5e346b62c4..7621e51818c 100644 --- a/plugins/chat/assets/javascripts/discourse/controllers/preferences-chat.js +++ b/plugins/chat/assets/javascripts/discourse/controllers/preferences-chat.js @@ -14,38 +14,65 @@ const CHAT_ATTRS = [ "chat_sound", "chat_email_frequency", "chat_header_indicator_preference", + "chat_separate_sidebar_mode", +]; + +const EMAIL_FREQUENCY_OPTIONS = [ + { name: I18n.t("chat.email_frequency.never"), value: "never" }, + { name: I18n.t("chat.email_frequency.when_away"), value: "when_away" }, ]; export const HEADER_INDICATOR_PREFERENCE_NEVER = "never"; export const HEADER_INDICATOR_PREFERENCE_DM_AND_MENTIONS = "dm_and_mentions"; export const HEADER_INDICATOR_PREFERENCE_ALL_NEW = "all_new"; - -const EMAIL_FREQUENCY_OPTIONS = [ - { name: I18n.t(`chat.email_frequency.never`), value: "never" }, - { name: I18n.t(`chat.email_frequency.when_away`), value: "when_away" }, -]; - const HEADER_INDICATOR_OPTIONS = [ { - name: I18n.t(`chat.header_indicator_preference.all_new`), + name: I18n.t("chat.header_indicator_preference.all_new"), value: HEADER_INDICATOR_PREFERENCE_ALL_NEW, }, { - name: I18n.t(`chat.header_indicator_preference.dm_and_mentions`), + name: I18n.t("chat.header_indicator_preference.dm_and_mentions"), value: HEADER_INDICATOR_PREFERENCE_DM_AND_MENTIONS, }, { - name: I18n.t(`chat.header_indicator_preference.never`), + name: I18n.t("chat.header_indicator_preference.never"), value: HEADER_INDICATOR_PREFERENCE_NEVER, }, ]; +const CHAT_SEPARATE_SIDEBAR_MODE_OPTIONS = [ + { + name: I18n.t("admin.site_settings.chat_separate_sidebar_mode.always"), + value: "always", + }, + { + name: I18n.t("admin.site_settings.chat_separate_sidebar_mode.fullscreen"), + value: "fullscreen", + }, + { + name: I18n.t("admin.site_settings.chat_separate_sidebar_mode.never"), + value: "never", + }, +]; + export default class PreferencesChatController extends Controller { @service chatAudioManager; + @service siteSettings; + subpageTitle = I18n.t("chat.admin.title"); emailFrequencyOptions = EMAIL_FREQUENCY_OPTIONS; headerIndicatorOptions = HEADER_INDICATOR_OPTIONS; + chatSeparateSidebarModeOptions = CHAT_SEPARATE_SIDEBAR_MODE_OPTIONS; + + get chatSeparateSidebarMode() { + const mode = this.model.get("user_option.chat_separate_sidebar_mode"); + if (mode === "default") { + return this.siteSettings.chat_separate_sidebar_mode; + } else { + return mode; + } + } @discourseComputed chatSounds() { diff --git a/plugins/chat/assets/javascripts/discourse/initializers/chat-sidebar.js b/plugins/chat/assets/javascripts/discourse/initializers/chat-sidebar.js index e9c8d2f593a..5e7af1fd0bb 100644 --- a/plugins/chat/assets/javascripts/discourse/initializers/chat-sidebar.js +++ b/plugins/chat/assets/javascripts/discourse/initializers/chat-sidebar.js @@ -11,6 +11,7 @@ import { decorateUsername } from "discourse/helpers/decorate-username-selector"; import { until } from "discourse/lib/formatter"; import { inject as service } from "@ember/service"; import ChatModalNewMessage from "discourse/plugins/chat/discourse/components/chat/modal/new-message"; +import getURL from "discourse-common/lib/get-url"; export default { name: "chat-sidebar", @@ -23,6 +24,18 @@ export default { this.siteSettings = container.lookup("service:site-settings"); + withPluginApi("1.8.0", (api) => { + api.addSidebarPanel( + (BaseCustomSidebarPanel) => + class ChatSidebarPanel extends BaseCustomSidebarPanel { + key = "chat"; + switchButtonLabel = I18n.t("sidebar.panels.chat.label"); + switchButtonIcon = "d-chat"; + switchButtonDefaultUrl = getURL("/chat"); + } + ); + }); + withPluginApi("1.3.0", (api) => { if (this.siteSettings.enable_public_channels) { api.addSidebarSection( @@ -180,7 +193,8 @@ export default { }; return SidebarChatChannelsSection; - } + }, + "chat" ); } @@ -414,7 +428,8 @@ export default { }; return SidebarChatDirectMessagesSection; - } + }, + "chat" ); }); }, diff --git a/plugins/chat/assets/javascripts/discourse/initializers/chat-user-options.js b/plugins/chat/assets/javascripts/discourse/initializers/chat-user-options.js index c410763d713..5fdc01b7fd5 100644 --- a/plugins/chat/assets/javascripts/discourse/initializers/chat-user-options.js +++ b/plugins/chat/assets/javascripts/discourse/initializers/chat-user-options.js @@ -6,6 +6,7 @@ const IGNORE_CHANNEL_WIDE_MENTION = "ignore_channel_wide_mention"; const CHAT_SOUND = "chat_sound"; const CHAT_EMAIL_FREQUENCY = "chat_email_frequency"; const CHAT_HEADER_INDICATOR_PREFERENCE = "chat_header_indicator_preference"; +const CHAT_SEPARATE_SIDEBAR_MODE = "chat_separate_sidebar_mode"; export default { name: "chat-user-options", @@ -20,6 +21,7 @@ export default { api.addSaveableUserOptionField(CHAT_SOUND); api.addSaveableUserOptionField(CHAT_EMAIL_FREQUENCY); api.addSaveableUserOptionField(CHAT_HEADER_INDICATOR_PREFERENCE); + api.addSaveableUserOptionField(CHAT_SEPARATE_SIDEBAR_MODE); } }); }, diff --git a/plugins/chat/assets/javascripts/discourse/lib/get-user-chat-separate-sidebar-mode.js b/plugins/chat/assets/javascripts/discourse/lib/get-user-chat-separate-sidebar-mode.js new file mode 100644 index 00000000000..456f364b15a --- /dev/null +++ b/plugins/chat/assets/javascripts/discourse/lib/get-user-chat-separate-sidebar-mode.js @@ -0,0 +1,9 @@ +export function getUserChatSeparateSidebarMode(user) { + const mode = user?.get("user_option.chat_separate_sidebar_mode"); + + return { + never: "never" === mode, + always: "always" === mode, + fullscreen: "fullscreen" === mode, + }; +} diff --git a/plugins/chat/assets/javascripts/discourse/routes/chat.js b/plugins/chat/assets/javascripts/discourse/routes/chat.js index cae606d06b2..3186d64d233 100644 --- a/plugins/chat/assets/javascripts/discourse/routes/chat.js +++ b/plugins/chat/assets/javascripts/discourse/routes/chat.js @@ -4,11 +4,13 @@ import { defaultHomepage } from "discourse/lib/utilities"; import { inject as service } from "@ember/service"; import { scrollTop } from "discourse/mixins/scroll-top"; import { schedule } from "@ember/runloop"; - +import { withPluginApi } from "discourse/lib/plugin-api"; +import { getUserChatSeparateSidebarMode } from "discourse/plugins/chat/discourse/lib/get-user-chat-separate-sidebar-mode"; export default class ChatRoute extends DiscourseRoute { @service chat; @service router; @service chatStateManager; + @service currentUser; titleToken() { return I18n.t("chat.title_capitalized"); @@ -57,6 +59,16 @@ export default class ChatRoute extends DiscourseRoute { } activate() { + withPluginApi("1.8.0", (api) => { + api.setSidebarPanel("chat"); + if (getUserChatSeparateSidebarMode(this.currentUser).never) { + api.setCombinedSidebarMode(); + api.hideSidebarSwitchPanelButtons(); + } else { + api.setSeparatedSidebarMode(); + } + }); + this.chatStateManager.storeAppURL(); this.chat.updatePresence(); @@ -68,6 +80,23 @@ export default class ChatRoute extends DiscourseRoute { } deactivate(transition) { + withPluginApi("1.8.0", (api) => { + api.setSidebarPanel("main"); + + const chatSeparateSidebarMode = getUserChatSeparateSidebarMode( + this.currentUser + ); + if (chatSeparateSidebarMode.fullscreen) { + api.setCombinedSidebarMode(); + api.showSidebarSwitchPanelButtons(); + } else if (chatSeparateSidebarMode.always) { + api.setSeparatedSidebarMode(); + } else { + api.setCombinedSidebarMode(); + api.hideSidebarSwitchPanelButtons(); + } + }); + if (transition) { const url = this.router.urlFor(transition.from.name); this.chatStateManager.storeChatURL(url); 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 88608e1a41d..c1956798632 100644 --- a/plugins/chat/assets/javascripts/discourse/services/chat-state-manager.js +++ b/plugins/chat/assets/javascripts/discourse/services/chat-state-manager.js @@ -4,6 +4,8 @@ import { tracked } from "@glimmer/tracking"; import KeyValueStore from "discourse/lib/key-value-store"; import Site from "discourse/models/site"; import getURL from "discourse-common/lib/get-url"; +import { getUserChatSeparateSidebarMode } from "discourse/plugins/chat/discourse/lib/get-user-chat-separate-sidebar-mode"; +import { withPluginApi } from "discourse/lib/plugin-api"; const PREFERRED_MODE_KEY = "preferred_mode"; const PREFERRED_MODE_STORE_NAMESPACE = "discourse_chat_"; @@ -56,6 +58,16 @@ export default class ChatStateManager extends Service { } didOpenDrawer(url = null) { + withPluginApi("1.8.0", (api) => { + if (getUserChatSeparateSidebarMode(this.currentUser).always) { + api.setSidebarPanel("main"); + api.setSeparatedSidebarMode(); + api.hideSidebarSwitchPanelButtons(); + } else { + api.setCombinedSidebarMode(); + } + }); + this.isDrawerActive = true; this.isDrawerExpanded = true; @@ -68,6 +80,24 @@ export default class ChatStateManager extends Service { } didCloseDrawer() { + withPluginApi("1.8.0", (api) => { + api.setSidebarPanel("main"); + + const chatSeparateSidebarMode = getUserChatSeparateSidebarMode( + this.currentUser + ); + if (chatSeparateSidebarMode.fullscreen) { + api.setCombinedSidebarMode(); + api.showSidebarSwitchPanelButtons(); + } else if (chatSeparateSidebarMode.always) { + api.setSeparatedSidebarMode(); + api.showSidebarSwitchPanelButtons(); + } else { + api.setCombinedSidebarMode(); + api.hideSidebarSwitchPanelButtons(); + } + }); + this.isDrawerActive = false; this.isDrawerExpanded = false; this.chat.updatePresence(); diff --git a/plugins/chat/assets/javascripts/discourse/templates/preferences/chat.hbs b/plugins/chat/assets/javascripts/discourse/templates/preferences/chat.hbs index 15eb67ad181..b90fe1b44b8 100644 --- a/plugins/chat/assets/javascripts/discourse/templates/preferences/chat.hbs +++ b/plugins/chat/assets/javascripts/discourse/templates/preferences/chat.hbs @@ -99,6 +99,23 @@ /> </div> +<div + class="control-group chat-setting controls-dropdown" + data-setting-name="user_chat_separate_sidebar_mode" +> + <label for="user_chat_separate_sidebar_mode"> + {{i18n "chat.separate_sidebar_mode.title"}} + </label> + + <ComboBox + @valueProperty="value" + @content={{this.chatSeparateSidebarModeOptions}} + @value={{this.chatSeparateSidebarMode}} + @id="user_chat_separate_sidebar_mode" + @onChange={{action (mut this.model.user_option.chat_separate_sidebar_mode)}} + /> +</div> + <SaveControls @id="user_chat_preference_save" @model={{this.model}} diff --git a/plugins/chat/assets/javascripts/discourse/widgets/chat-header-icon.js b/plugins/chat/assets/javascripts/discourse/widgets/chat-header-icon.js index dcacd94f499..30717cdc102 100644 --- a/plugins/chat/assets/javascripts/discourse/widgets/chat-header-icon.js +++ b/plugins/chat/assets/javascripts/discourse/widgets/chat-header-icon.js @@ -4,7 +4,6 @@ import { hbs } from "ember-cli-htmlbars"; export default createWidget("chat-header-icon", { tagName: "li.header-dropdown-toggle.chat-header-icon", - title: "chat.title_capitalized", services: ["chat"], diff --git a/plugins/chat/config/locales/client.en.yml b/plugins/chat/config/locales/client.en.yml index f77cb687258..86e0e27b9f1 100644 --- a/plugins/chat/config/locales/client.en.yml +++ b/plugins/chat/config/locales/client.en.yml @@ -4,6 +4,10 @@ en: site_settings: categories: chat: Chat + chat_separate_sidebar_mode: + always: "Always" + fullscreen: "When chat is in fullscreen" + never: "Never" logs: staff_actions: actions: @@ -104,6 +108,8 @@ en: all_new: "All New Messages" dm_and_mentions: "Direct Messages and Mentions" never: "Never" + separate_sidebar_mode: + title: "Show separate sidebar modes for forum and chat" enable: "Enable chat" flag: "Flag" emoji: "Insert emoji" @@ -685,3 +691,8 @@ en: sections: chat: title: Chat + + sidebar: + panels: + chat: + label: "Chat" diff --git a/plugins/chat/config/locales/server.en.yml b/plugins/chat/config/locales/server.en.yml index a8b4c143a43..a8027d07859 100644 --- a/plugins/chat/config/locales/server.en.yml +++ b/plugins/chat/config/locales/server.en.yml @@ -1,5 +1,6 @@ en: site_settings: + chat_separate_sidebar_mode: "Show separate sidebar modes for forum and chat." chat_enabled: "Enable the chat plugin." enable_public_channels: "Enable public channels based on categories." chat_allowed_groups: "Users in these groups can chat. Note that staff can always access chat." diff --git a/plugins/chat/config/settings.yml b/plugins/chat/config/settings.yml index e5958c82cde..dd5e54ecb7c 100644 --- a/plugins/chat/config/settings.yml +++ b/plugins/chat/config/settings.yml @@ -116,3 +116,8 @@ chat: max_chat_draft_length: default: 50_000 hidden: true + chat_separate_sidebar_mode: + client: true + default: "never" + type: enum + enum: "Chat::SeparateSidebarModeSiteSetting" diff --git a/plugins/chat/db/migrate/20230722124044_add_chat_separate_sidebar_mode_user_option.rb b/plugins/chat/db/migrate/20230722124044_add_chat_separate_sidebar_mode_user_option.rb new file mode 100644 index 00000000000..597252808d2 --- /dev/null +++ b/plugins/chat/db/migrate/20230722124044_add_chat_separate_sidebar_mode_user_option.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddChatSeparateSidebarModeUserOption < ActiveRecord::Migration[7.0] + def change + add_column :user_options, :chat_separate_sidebar_mode, :integer, default: 0, null: false + end +end diff --git a/plugins/chat/lib/chat/user_option_extension.rb b/plugins/chat/lib/chat/user_option_extension.rb index 290f6361d55..467a78c9b47 100644 --- a/plugins/chat/lib/chat/user_option_extension.rb +++ b/plugins/chat/lib/chat/user_option_extension.rb @@ -14,19 +14,32 @@ module Chat @chat_email_frequencies ||= { never: 0, when_away: 1 } end + # Avoid attempting to override when autoloading + if !base.method_defined?(:send_chat_email_never?) + base.enum :chat_email_frequency, base.chat_email_frequencies, prefix: "send_chat_email" + end + def base.chat_header_indicator_preferences @chat_header_indicator_preferences ||= { all_new: 0, dm_and_mentions: 1, never: 2 } end - if !base.method_defined?(:send_chat_email_never?) # Avoid attempting to override when autoloading - base.enum :chat_email_frequency, base.chat_email_frequencies, prefix: "send_chat_email" - end - - if !base.method_defined?(:chat_header_indicator_never?) # Avoid attempting to override when autoloading + # Avoid attempting to override when autoloading + if !base.method_defined?(:chat_header_indicator_never?) base.enum :chat_header_indicator_preference, base.chat_header_indicator_preferences, prefix: "chat_header_indicator" end + + def base.chat_separate_sidebar_mode + @chat_separate_sidebar_mode ||= { default: 0, never: 1, always: 2, fullscreen: 3 } + end + + # Avoid attempting to override when autoloading + if !base.method_defined?(:chat_separate_sidebar_mode_default?) + base.enum :chat_separate_sidebar_mode, + base.chat_separate_sidebar_mode, + prefix: "chat_separate_sidebar_mode" + end end end end diff --git a/plugins/chat/plugin.rb b/plugins/chat/plugin.rb index 16584f9de06..d022eecb42d 100644 --- a/plugins/chat/plugin.rb +++ b/plugins/chat/plugin.rb @@ -47,6 +47,7 @@ after_initialize do UserUpdater::OPTION_ATTR.push(:ignore_channel_wide_mention) UserUpdater::OPTION_ATTR.push(:chat_email_frequency) UserUpdater::OPTION_ATTR.push(:chat_header_indicator_preference) + UserUpdater::OPTION_ATTR.push(:chat_separate_sidebar_mode) register_reviewable_type Chat::ReviewableMessage @@ -297,6 +298,12 @@ after_initialize do object.chat_header_indicator_preference end + add_to_serializer(:user_option, :chat_separate_sidebar_mode) { object.chat_separate_sidebar_mode } + + add_to_serializer(:current_user_option, :chat_separate_sidebar_mode) do + object.chat_separate_sidebar_mode + end + RETENTION_SETTINGS_TO_USER_OPTION_FIELDS = { chat_channel_retention_days: :dismissed_channel_retention_reminder, chat_dm_retention_days: :dismissed_dm_retention_reminder, diff --git a/plugins/chat/spec/models/user_option_spec.rb b/plugins/chat/spec/models/user_option_spec.rb new file mode 100644 index 00000000000..481f7e3b55c --- /dev/null +++ b/plugins/chat/spec/models/user_option_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe UserOption do + describe "#chat_separate_sidebar_mode" do + it "is present" do + expect(described_class.new.chat_separate_sidebar_mode).to eq("default") + end + end +end diff --git a/plugins/chat/spec/serializer/current_user_serializer_spec.rb b/plugins/chat/spec/serializer/current_user_serializer_spec.rb index 2e2fd20a0ef..d1a2a7c52ed 100644 --- a/plugins/chat/spec/serializer/current_user_serializer_spec.rb +++ b/plugins/chat/spec/serializer/current_user_serializer_spec.rb @@ -13,6 +13,12 @@ RSpec.describe CurrentUserSerializer do current_user.user_option.update(chat_enabled: true) end + describe "#chat_separate_sidebar_mode" do + it "is present" do + expect(serializer.as_json[:user_option][:chat_separate_sidebar_mode]).to eq("default") + end + end + describe "#chat_drafts" do context "when user can't chat" do before { SiteSetting.chat_allowed_groups = Group::AUTO_GROUPS[:staff] } diff --git a/plugins/chat/spec/serializer/user_serializer_spec.rb b/plugins/chat/spec/serializer/user_serializer_spec.rb new file mode 100644 index 00000000000..f4b1ed28c05 --- /dev/null +++ b/plugins/chat/spec/serializer/user_serializer_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +RSpec.describe UserSerializer do + fab!(:current_user) { Fabricate(:user) } + + let(:serializer) do + described_class.new(current_user, scope: Guardian.new(current_user), root: false) + end + + describe "#chat_separate_sidebar_mode" do + it "is present" do + expect(serializer.as_json[:user_option][:chat_separate_sidebar_mode]).to eq("default") + end + end +end diff --git a/plugins/chat/spec/system/navigation_spec.rb b/plugins/chat/spec/system/navigation_spec.rb index 40bead2f3d0..b41513f9198 100644 --- a/plugins/chat/spec/system/navigation_spec.rb +++ b/plugins/chat/spec/system/navigation_spec.rb @@ -19,6 +19,9 @@ RSpec.describe "Navigation", type: :system do before do chat_system_bootstrap(current_user, [category_channel, category_channel_2]) + current_user.user_option.update( + chat_separate_sidebar_mode: UserOption.chat_separate_sidebar_modes[:never], + ) sign_in(current_user) end @@ -36,7 +39,6 @@ RSpec.describe "Navigation", type: :system do context "when clicking chat icon on mobile and is viewing channel" do it "navigates to index", mobile: true do - visit("/chat") chat_page.visit_channel(category_channel_2) chat_page.open_from_header @@ -44,18 +46,6 @@ RSpec.describe "Navigation", type: :system do end end - context "when clicking chat icon on desktop and is viewing channel" do - it "stays on channel page" do - visit("/chat") - chat_page.visit_channel(category_channel_2) - chat_page.open_from_header - - expect(page).to have_current_path( - chat.channel_path(category_channel_2.slug, category_channel_2.id), - ) - end - end - context "when visiting /chat" do it "opens full page" do chat_page.open diff --git a/plugins/chat/spec/system/page_objects/chat/chat.rb b/plugins/chat/spec/system/page_objects/chat/chat.rb index 6a77ed8402b..d00db1c3735 100644 --- a/plugins/chat/spec/system/page_objects/chat/chat.rb +++ b/plugins/chat/spec/system/page_objects/chat/chat.rb @@ -17,6 +17,12 @@ module PageObjects ) end + def prefers_drawer + page.execute_script( + "window.localStorage.setItem('discourse_chat_preferred_mode', '\"DRAWER_CHAT\"');", + ) + end + def open_from_header find(".chat-header-icon").click end diff --git a/plugins/chat/spec/system/page_objects/chat/components/header.rb b/plugins/chat/spec/system/page_objects/chat/components/header.rb new file mode 100644 index 00000000000..b9c78052946 --- /dev/null +++ b/plugins/chat/spec/system/page_objects/chat/components/header.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module PageObjects + module Components + module Chat + class Header < PageObjects::Components::Base + def has_open_chat_button? + has_css?(".d-header .chat-header-icon .d-icon-d-chat") + end + + def has_open_forum_button? + has_css?(".d-header .chat-header-icon .d-icon-random") + end + + def has_no_chat_button? + has_no_css?(".d-header .chat-header-icon") + end + end + end + end +end diff --git a/plugins/chat/spec/system/separate_sidebar_mode_spec.rb b/plugins/chat/spec/system/separate_sidebar_mode_spec.rb new file mode 100644 index 00000000000..265a3b22984 --- /dev/null +++ b/plugins/chat/spec/system/separate_sidebar_mode_spec.rb @@ -0,0 +1,202 @@ +# frozen_string_literal: true + +RSpec.describe "Separate sidebar mode", type: :system do + let(:chat_page) { PageObjects::Pages::Chat.new } + let(:sidebar_page) { PageObjects::Pages::Sidebar.new } + let(:sidebar_component) { PageObjects::Components::NavigationMenu::Sidebar.new } + let(:chat_drawer_page) { PageObjects::Pages::ChatDrawer.new } + let(:header_component) { PageObjects::Components::Chat::Header.new } + + fab!(:current_user) { Fabricate(:user) } + fab!(:channel_1) { Fabricate(:chat_channel) } + + before do + SiteSetting.navigation_menu = "sidebar" + channel_1.add(current_user) + chat_system_bootstrap + sign_in(current_user) + end + + describe "when separate sidebar mode is never" do + before do + current_user.user_option.update!( + chat_separate_sidebar_mode: UserOption.chat_separate_sidebar_modes[:never], + ) + end + + context "with drawer" do + before { chat_page.prefers_drawer } + + it "has the expected behavior" do + visit("/") + + expect(sidebar_component).to have_no_switch_button + expect(header_component).to have_open_chat_button + expect(sidebar_component).to have_section("Categories") + expect(sidebar_component).to have_section("chat-channels") + + sidebar_page.open_channel(channel_1) + + expect(sidebar_component).to have_no_switch_button + expect(header_component).to have_open_chat_button + expect(sidebar_component).to have_section("Categories") + expect(sidebar_component).to have_section("chat-channels") + + chat_drawer_page.close + + expect(sidebar_component).to have_no_switch_button + expect(header_component).to have_open_chat_button + expect(sidebar_component).to have_section("Categories") + expect(sidebar_component).to have_section("chat-channels") + end + end + + context "with full page" do + before { chat_page.prefers_full_page } + + it "has the expected behavior" do + visit("/") + + expect(sidebar_component).to have_no_switch_button + expect(header_component).to have_open_chat_button + expect(sidebar_component).to have_section("Categories") + expect(sidebar_component).to have_section("chat-channels") + + sidebar_page.open_channel(channel_1) + + expect(sidebar_component).to have_no_switch_button + expect(header_component).to have_open_chat_button + expect(sidebar_component).to have_section("Categories") + expect(sidebar_component).to have_section("chat-channels") + + find("#site-logo").click + + expect(sidebar_component).to have_no_switch_button + expect(header_component).to have_open_chat_button + expect(sidebar_component).to have_section("Categories") + expect(sidebar_component).to have_section("chat-channels") + end + end + end + + describe "when separate sidebar mode is always" do + before do + current_user.user_option.update( + chat_separate_sidebar_mode: UserOption.chat_separate_sidebar_modes[:always], + ) + end + + context "with drawer" do + before { chat_page.prefers_drawer } + + it "has the expected behavior" do + visit("/") + + expect(sidebar_component).to have_switch_button("chat") + expect(header_component).to have_open_chat_button + expect(sidebar_component).to have_no_section("chat-channels") + + chat_page.open_from_header + + expect(sidebar_component).to have_no_switch_button + expect(header_component).to have_open_chat_button + expect(sidebar_component).to have_no_section("chat-channels") + + chat_drawer_page.close + + expect(sidebar_component).to have_switch_button("chat") + expect(header_component).to have_open_chat_button + expect(sidebar_component).to have_no_section("chat-channels") + end + end + + context "with full page" do + before { chat_page.prefers_full_page } + + it "has the expected behavior" do + visit("/") + + expect(sidebar_component).to have_switch_button("chat") + expect(header_component).to have_open_chat_button + expect(sidebar_component).to have_no_section("chat-channels") + expect(sidebar_component).to have_section("Categories") + + chat_page.open_from_header + + expect(sidebar_component).to have_switch_button("main") + expect(header_component).to have_open_forum_button + expect(sidebar_component).to have_section("chat-channels") + expect(sidebar_component).to have_no_section("Categories") + + find("#site-logo").click + + expect(sidebar_component).to have_switch_button("chat") + expect(header_component).to have_open_chat_button + expect(sidebar_component).to have_no_section("chat-channels") + expect(sidebar_component).to have_section("Categories") + end + end + end + + describe "when separate sidebar mode is fullscreen" do + before do + current_user.user_option.update( + chat_separate_sidebar_mode: UserOption.chat_separate_sidebar_modes[:fullscreen], + ) + end + + context "with drawer" do + before { chat_page.prefers_drawer } + + it "has the expected behavior" do + visit("/") + + expect(sidebar_component).to have_switch_button + expect(header_component).to have_open_chat_button + expect(sidebar_component).to have_section("Categories") + expect(sidebar_component).to have_section("chat-channels") + + sidebar_page.open_channel(channel_1) + + expect(sidebar_component).to have_no_switch_button + expect(header_component).to have_open_chat_button + expect(sidebar_component).to have_section("Categories") + expect(sidebar_component).to have_section("chat-channels") + + chat_drawer_page.close + + expect(sidebar_component).to have_switch_button + expect(header_component).to have_open_chat_button + expect(sidebar_component).to have_section("Categories") + expect(sidebar_component).to have_section("chat-channels") + end + end + + context "with full page" do + before { chat_page.prefers_full_page } + + it "has the expected behavior" do + visit("/") + + expect(sidebar_component).to have_switch_button("chat") + expect(header_component).to have_open_chat_button + expect(sidebar_component).to have_section("chat-channels") + expect(sidebar_component).to have_section("Categories") + + sidebar_page.open_channel(channel_1) + + expect(sidebar_component).to have_switch_button("main") + expect(header_component).to have_open_forum_button + expect(sidebar_component).to have_section("chat-channels") + expect(sidebar_component).to have_no_section("Categories") + + find("#site-logo").click + + expect(sidebar_component).to have_switch_button("chat") + expect(header_component).to have_open_chat_button + expect(sidebar_component).to have_section("chat-channels") + expect(sidebar_component).to have_section("Categories") + end + end + end +end diff --git a/plugins/chat/spec/system/user_chat_preferences_spec.rb b/plugins/chat/spec/system/user_chat_preferences_spec.rb index 69ade2eb6a1..3657893c172 100644 --- a/plugins/chat/spec/system/user_chat_preferences_spec.rb +++ b/plugins/chat/spec/system/user_chat_preferences_spec.rb @@ -25,22 +25,32 @@ RSpec.describe "User chat preferences", type: :system do it "can select chat sound" do visit("/u/#{current_user.username}/preferences/chat") - find("#user_chat_sounds .select-kit-header[data-value]").click - find("[data-value='bell']").click + select_kit = PageObjects::Components::SelectKit.new("#user_chat_sounds") + select_kit.expand + select_kit.select_row_by_value("bell") find(".save-changes").click - expect(page).to have_css("#user_chat_sounds .select-kit-header[data-value='bell']") + expect(select_kit).to have_selected_value("bell") end it "can select header_indicator_preference" do visit("/u/#{current_user.username}/preferences/chat") - find("#user_chat_header_indicator_preference .select-kit-header[data-value]").click - find("[data-value='dm_and_mentions']").click + select_kit = PageObjects::Components::SelectKit.new("#user_chat_header_indicator_preference") + select_kit.expand + select_kit.select_row_by_value("dm_and_mentions") find(".save-changes").click - expect(page).to have_css( - "#user_chat_header_indicator_preference .select-kit-header[data-value='dm_and_mentions']", - ) + expect(select_kit).to have_selected_value("dm_and_mentions") + end + + it "can select separate sidebar mode" do + visit("/u/#{current_user.username}/preferences/chat") + select_kit = PageObjects::Components::SelectKit.new("#user_chat_separate_sidebar_mode") + select_kit.expand + select_kit.select_row_by_value("fullscreen") + find(".save-changes").click + + expect(select_kit).to have_selected_value("fullscreen") end context "as an admin on another user's preferences" do diff --git a/plugins/chat/test/javascripts/components/chat-header-icon-test.js b/plugins/chat/test/javascripts/components/chat-header-icon-test.js new file mode 100644 index 00000000000..192fb6914e8 --- /dev/null +++ b/plugins/chat/test/javascripts/components/chat-header-icon-test.js @@ -0,0 +1,44 @@ +import { module, test } from "qunit"; +import { setupRenderingTest } from "discourse/tests/helpers/component-test"; +import { render } from "@ember/test-helpers"; +import { hbs } from "ember-cli-htmlbars"; +import sinon from "sinon"; +import I18n from "I18n"; + +module("Discourse Chat | Component | chat-header-icon", function (hooks) { + setupRenderingTest(hooks); + + hooks.beforeEach(function () {}); + + test("full page - never separated sidebar mode", async function (assert) { + this.currentUser.user_option.chat_separate_sidebar_mode = "never"; + sinon + .stub(this.owner.lookup("service:chat-state-manager"), "isFullPageActive") + .value(true); + + await render(hbs`<Chat::Header::Icon />`); + + assert + .dom(".icon.btn-flat") + .hasAttribute("title", I18n.t("chat.title_capitalized")) + .hasAttribute("href", "/chat"); + + assert.dom(".d-icon-d-chat").exists(); + }); + + test("full page - always separated mode", async function (assert) { + this.currentUser.user_option.chat_separate_sidebar_mode = "always"; + sinon + .stub(this.owner.lookup("service:chat-state-manager"), "isFullPageActive") + .value(true); + + await render(hbs`<Chat::Header::Icon />`); + + assert + .dom(".icon.btn-flat") + .hasAttribute("title", I18n.t("sidebar.panels.forum.label")) + .hasAttribute("href", "/latest"); + + assert.dom(".d-icon-random").exists(); + }); +}); diff --git a/spec/system/page_objects/components/navigation_menu/base.rb b/spec/system/page_objects/components/navigation_menu/base.rb index a7b1ab7edcd..7f41ea2447a 100644 --- a/spec/system/page_objects/components/navigation_menu/base.rb +++ b/spec/system/page_objects/components/navigation_menu/base.rb @@ -34,6 +34,22 @@ module PageObjects has_no_css?(".sidebar-sections [data-section-name='#{name.parameterize}']") end + def has_switch_button?(key = nil) + if key + page.has_css?(".sidebar__panel-switch-button[data-key='#{key.parameterize}']") + else + page.has_css?(".sidebar__panel-switch-button") + end + end + + def has_no_switch_button?(key = nil) + if key + page.has_no_css?(".sidebar__panel-switch-button[data-key='#{key.parameterize}']") + else + page.has_no_css?(".sidebar__panel-switch-button") + end + end + def has_categories_section? has_section?("Categories") end