From 42801c950faa0c116b278b11c5320b88c62d1526 Mon Sep 17 00:00:00 2001 From: Joffrey JAFFEUX Date: Mon, 9 Oct 2023 14:11:16 +0200 Subject: [PATCH] UI: redesigned settings/members (#23804) This PR is a first step towards private groups. It redesigns settings/members area of a channel and also drops the "about" page which is now mixed into settings. This commit is also: - introducing chat-form, a small DSL to create forms, ideally I would want something in core for this - introducing a DToggleSwitch page object component to simplify testing toggles - migrating various components to gjs --- .../discourse/app/helpers/category-badge.js | 8 +- .../discourse/app/helpers/replace-emoji.js | 8 +- .../float-kit/addon/lib/constants.js | 2 +- .../javascripts/discourse/chat-route-map.js | 1 - .../components/chat-channel-about-view.hbs | 93 --- .../components/chat-channel-about-view.js | 11 - .../components/chat-channel-info.gjs | 85 +++ .../components/chat-channel-members-view.hbs | 42 -- .../components/chat-channel-members-view.js | 66 -- .../components/chat-channel-members.gjs | 125 ++++ .../chat-channel-settings-saved-indicator.hbs | 14 - .../chat-channel-settings-saved-indicator.js | 28 - .../components/chat-channel-settings-view.hbs | 199 ------ .../components/chat-channel-settings-view.js | 203 ------ .../components/chat-channel-settings.gjs | 581 ++++++++++++++++++ ...xt.js => chat-retention-reminder-text.gjs} | 6 + .../chat-retention-reminder-text.hbs | 3 - .../discourse/components/chat-user-info.gjs | 25 + .../discourse/components/chat-user-info.hbs | 8 - .../discourse/components/chat-user-info.js | 8 - .../discourse/components/chat/form.gjs | 14 + .../discourse/components/chat/form/row.gjs | 48 ++ .../components/chat/form/section.gjs | 22 + .../chat/modal/edit-channel-name.hbs | 10 +- .../discourse/components/dc-filter-input.gjs | 58 ++ .../discourse/components/dc-filter-input.hbs | 26 - .../discourse/components/dc-filter-input.js | 3 - .../controllers/chat-channel-info-about.js | 26 - .../controllers/chat-channel-info-members.js | 3 - .../controllers/chat-channel-info-settings.js | 3 - .../controllers/chat-channel-info.js | 34 - .../javascripts/discourse/lib/collection.js | 5 +- .../routes/chat-channel-info-about.js | 12 - .../routes/chat-channel-info-index.js | 12 +- .../routes/chat-channel-info-members.js | 6 +- .../routes/chat-channel-info-settings.js | 13 - .../discourse/services/chat-api.js | 5 +- .../services/chat-channels-manager.js | 13 +- .../discourse/services/chat-guardian.js | 5 +- .../templates/chat-channel-info-about.hbs | 5 - .../templates/chat-channel-info-members.hbs | 2 +- .../templates/chat-channel-info-settings.hbs | 2 +- .../discourse/templates/chat-channel-info.hbs | 66 +- .../stylesheets/common/chat-channel-info.scss | 136 +--- .../common/chat-channel-members.scss | 32 + ...chat-channel-settings-saved-indicator.scss | 9 - .../common/chat-channel-settings.scss | 18 + .../assets/stylesheets/common/chat-form.scss | 134 ++-- .../chat/assets/stylesheets/common/index.scss | 3 +- .../stylesheets/mobile/chat-channel-info.scss | 0 .../mobile/chat-channel-members.scss | 3 + .../mobile/chat-channel-settings.scss | 3 + .../chat/assets/stylesheets/mobile/index.scss | 3 +- plugins/chat/config/locales/client.en.yml | 18 +- .../spec/system/channel_about_page_spec.rb | 138 ----- .../spec/system/channel_info_pages_spec.rb | 38 -- .../spec/system/channel_members_page_spec.rb | 20 +- .../spec/system/channel_settings_page_spec.rb | 371 ++++++----- plugins/chat/spec/system/drawer_spec.rb | 2 +- .../spec/system/page_objects/chat/chat.rb | 4 - ...nnel_about.rb => chat_channel_settings.rb} | 6 +- .../page_objects/modals/chat_edit_modal.rb | 2 +- ...t-channel-settings-saved-indicator-test.js | 31 - .../chat-channel-settings-view-test.js | 25 - .../components/d_toggle_switch.rb | 22 + 65 files changed, 1424 insertions(+), 1503 deletions(-) delete mode 100644 plugins/chat/assets/javascripts/discourse/components/chat-channel-about-view.hbs delete mode 100644 plugins/chat/assets/javascripts/discourse/components/chat-channel-about-view.js create mode 100644 plugins/chat/assets/javascripts/discourse/components/chat-channel-info.gjs delete mode 100644 plugins/chat/assets/javascripts/discourse/components/chat-channel-members-view.hbs delete mode 100644 plugins/chat/assets/javascripts/discourse/components/chat-channel-members-view.js create mode 100644 plugins/chat/assets/javascripts/discourse/components/chat-channel-members.gjs delete mode 100644 plugins/chat/assets/javascripts/discourse/components/chat-channel-settings-saved-indicator.hbs delete mode 100644 plugins/chat/assets/javascripts/discourse/components/chat-channel-settings-saved-indicator.js delete mode 100644 plugins/chat/assets/javascripts/discourse/components/chat-channel-settings-view.hbs delete mode 100644 plugins/chat/assets/javascripts/discourse/components/chat-channel-settings-view.js create mode 100644 plugins/chat/assets/javascripts/discourse/components/chat-channel-settings.gjs rename plugins/chat/assets/javascripts/discourse/components/{chat-retention-reminder-text.js => chat-retention-reminder-text.gjs} (90%) delete mode 100644 plugins/chat/assets/javascripts/discourse/components/chat-retention-reminder-text.hbs create mode 100644 plugins/chat/assets/javascripts/discourse/components/chat-user-info.gjs delete mode 100644 plugins/chat/assets/javascripts/discourse/components/chat-user-info.hbs delete mode 100644 plugins/chat/assets/javascripts/discourse/components/chat-user-info.js create mode 100644 plugins/chat/assets/javascripts/discourse/components/chat/form.gjs create mode 100644 plugins/chat/assets/javascripts/discourse/components/chat/form/row.gjs create mode 100644 plugins/chat/assets/javascripts/discourse/components/chat/form/section.gjs create mode 100644 plugins/chat/assets/javascripts/discourse/components/dc-filter-input.gjs delete mode 100644 plugins/chat/assets/javascripts/discourse/components/dc-filter-input.hbs delete mode 100644 plugins/chat/assets/javascripts/discourse/components/dc-filter-input.js delete mode 100644 plugins/chat/assets/javascripts/discourse/controllers/chat-channel-info-about.js delete mode 100644 plugins/chat/assets/javascripts/discourse/controllers/chat-channel-info-members.js delete mode 100644 plugins/chat/assets/javascripts/discourse/controllers/chat-channel-info-settings.js delete mode 100644 plugins/chat/assets/javascripts/discourse/controllers/chat-channel-info.js delete mode 100644 plugins/chat/assets/javascripts/discourse/routes/chat-channel-info-about.js delete mode 100644 plugins/chat/assets/javascripts/discourse/routes/chat-channel-info-settings.js delete mode 100644 plugins/chat/assets/javascripts/discourse/templates/chat-channel-info-about.hbs create mode 100644 plugins/chat/assets/stylesheets/common/chat-channel-members.scss delete mode 100644 plugins/chat/assets/stylesheets/common/chat-channel-settings-saved-indicator.scss create mode 100644 plugins/chat/assets/stylesheets/common/chat-channel-settings.scss delete mode 100644 plugins/chat/assets/stylesheets/mobile/chat-channel-info.scss create mode 100644 plugins/chat/assets/stylesheets/mobile/chat-channel-members.scss create mode 100644 plugins/chat/assets/stylesheets/mobile/chat-channel-settings.scss delete mode 100644 plugins/chat/spec/system/channel_about_page_spec.rb delete mode 100644 plugins/chat/spec/system/channel_info_pages_spec.rb rename plugins/chat/spec/system/page_objects/chat/{chat_channel_about.rb => chat_channel_settings.rb} (68%) delete mode 100644 plugins/chat/test/javascripts/components/chat-channel-settings-saved-indicator-test.js delete mode 100644 plugins/chat/test/javascripts/components/chat-channel-settings-view-test.js create mode 100644 spec/system/page_objects/components/d_toggle_switch.rb diff --git a/app/assets/javascripts/discourse/app/helpers/category-badge.js b/app/assets/javascripts/discourse/app/helpers/category-badge.js index b9e51674c91..ec697eecc63 100644 --- a/app/assets/javascripts/discourse/app/helpers/category-badge.js +++ b/app/assets/javascripts/discourse/app/helpers/category-badge.js @@ -1,12 +1,14 @@ import { categoryLinkHTML } from "discourse/helpers/category-link"; -import { registerUnbound } from "discourse-common/lib/helpers"; import { isPresent } from "@ember/utils"; +import { registerRawHelper } from "discourse-common/lib/helpers"; -registerUnbound("category-badge", function (cat, options) { +registerRawHelper("category-badge", categoryBadge); + +export default function categoryBadge(cat, options = {}) { return categoryLinkHTML(cat, { hideParent: options.hideParent, allowUncategorized: options.allowUncategorized, categoryStyle: options.categoryStyle, link: isPresent(options.link) ? options.link : false, }); -}); +} diff --git a/app/assets/javascripts/discourse/app/helpers/replace-emoji.js b/app/assets/javascripts/discourse/app/helpers/replace-emoji.js index 81d78a3a1f4..13c1f7ea193 100644 --- a/app/assets/javascripts/discourse/app/helpers/replace-emoji.js +++ b/app/assets/javascripts/discourse/app/helpers/replace-emoji.js @@ -1,9 +1,11 @@ import { emojiUnescape } from "discourse/lib/text"; import { htmlSafe, isHTMLSafe } from "@ember/template"; -import { registerUnbound } from "discourse-common/lib/helpers"; import { escapeExpression } from "discourse/lib/utilities"; +import { registerRawHelper } from "discourse-common/lib/helpers"; -registerUnbound("replace-emoji", (text, options) => { +registerRawHelper("replace-emoji", replaceEmoji); + +export default function replaceEmoji(text, options) { text = isHTMLSafe(text) ? text.toString() : escapeExpression(text); return htmlSafe(emojiUnescape(text, options)); -}); +} diff --git a/app/assets/javascripts/float-kit/addon/lib/constants.js b/app/assets/javascripts/float-kit/addon/lib/constants.js index 64a1a39c25e..6590e1820d8 100644 --- a/app/assets/javascripts/float-kit/addon/lib/constants.js +++ b/app/assets/javascripts/float-kit/addon/lib/constants.js @@ -70,7 +70,7 @@ import DDefaultToast from "float-kit/components/d-default-toast"; export const TOAST = { options: { autoClose: true, - duration: 10000, + duration: 3000, component: DDefaultToast, }, }; diff --git a/plugins/chat/assets/javascripts/discourse/chat-route-map.js b/plugins/chat/assets/javascripts/discourse/chat-route-map.js index 7237e535014..2131b6b82cd 100644 --- a/plugins/chat/assets/javascripts/discourse/chat-route-map.js +++ b/plugins/chat/assets/javascripts/discourse/chat-route-map.js @@ -12,7 +12,6 @@ export default function () { "channel.info", { path: "/c/:channelTitle/:channelId/info" }, function () { - this.route("about", { path: "/about" }); this.route("members", { path: "/members" }); this.route("settings", { path: "/settings" }); } diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-channel-about-view.hbs b/plugins/chat/assets/javascripts/discourse/components/chat-channel-about-view.hbs deleted file mode 100644 index 23046d405b3..00000000000 --- a/plugins/chat/assets/javascripts/discourse/components/chat-channel-about-view.hbs +++ /dev/null @@ -1,93 +0,0 @@ -{{#if this.channel.isCategoryChannel}} -
-
- -
- {{category-badge - this.channel.chatable - link=true - allowUncategorized=true - }} -
-
-
-{{/if}} - -
-
- -
-
- {{replace-emoji this.channel.title}} -
-
- {{this.channel.slug}} -
-
-
-
- -{{#if - (or (chat-guardian "can-edit-chat-channel") this.channel.description.length) -}} -
-
- - -
-
- {{#if this.channel.description.length}} - {{this.channel.description}} - {{else}} -
- {{i18n "chat.channel_edit_description_modal.description"}} -
- {{/if}} -
-
-
-
-{{/if}} - -
- -
\ No newline at end of file diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-channel-about-view.js b/plugins/chat/assets/javascripts/discourse/components/chat-channel-about-view.js deleted file mode 100644 index c7e1aa5b65a..00000000000 --- a/plugins/chat/assets/javascripts/discourse/components/chat-channel-about-view.js +++ /dev/null @@ -1,11 +0,0 @@ -import Component from "@ember/component"; -import { inject as service } from "@ember/service"; - -export default class ChatChannelAboutView extends Component { - @service chat; - tagName = ""; - channel = null; - onEditChatChannelName = null; - onEditChatChannelDescription = null; - isLoading = false; -} diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-channel-info.gjs b/plugins/chat/assets/javascripts/discourse/components/chat-channel-info.gjs new file mode 100644 index 00000000000..3face999589 --- /dev/null +++ b/plugins/chat/assets/javascripts/discourse/components/chat-channel-info.gjs @@ -0,0 +1,85 @@ +import Component from "@glimmer/component"; +import { inject as service } from "@ember/service"; +import icon from "discourse-common/helpers/d-icon"; +import { LinkTo } from "@ember/routing"; +import ChatChannelTitle from "discourse/plugins/chat/discourse/components/chat-channel-title"; +import ChatChannelStatus from "discourse/plugins/chat/discourse/components/chat-channel-status"; +import I18n from "I18n"; + +export default class ChatChannelMessageEmojiPicker extends Component { + + + @service chatChannelInfoRouteOriginManager; + @service site; + + membersLabel = I18n.t("chat.channel_info.tabs.members"); + settingsLabel = I18n.t("chat.channel_info.tabs.settings"); + backToChannelLabel = I18n.t("chat.channel_info.back_to_all_channel"); + backToAllChannelsLabel = I18n.t("chat.channel_info.back_to_channel"); + + get showTabs() { + return ( + this.site.desktopView && + this.args.channel.membershipsCount > 1 && + this.args.channel.isOpen + ); + } +} diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-channel-members-view.hbs b/plugins/chat/assets/javascripts/discourse/components/chat-channel-members-view.hbs deleted file mode 100644 index 54f52fe97b1..00000000000 --- a/plugins/chat/assets/javascripts/discourse/components/chat-channel-members-view.hbs +++ /dev/null @@ -1,42 +0,0 @@ -{{#if (gt this.channel.membershipsCount 0)}} - -
-
- - {{d-icon "search"}} -
- -
-
- {{#each this.members as |membership|}} -
- -
- {{else}} - {{#if this.members.fetchedOnce}} -
- {{i18n "chat.channel.no_memberships_found"}} -
- {{/if}} - {{/each}} -
-
-
- -
-{{else}} -
- {{i18n "chat.channel.no_memberships"}} -
-{{/if}} \ No newline at end of file diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-channel-members-view.js b/plugins/chat/assets/javascripts/discourse/components/chat-channel-members-view.js deleted file mode 100644 index 0912bd77b07..00000000000 --- a/plugins/chat/assets/javascripts/discourse/components/chat-channel-members-view.js +++ /dev/null @@ -1,66 +0,0 @@ -import { INPUT_DELAY } from "discourse-common/config/environment"; -import Component from "@ember/component"; -import { action } from "@ember/object"; -import { schedule } from "@ember/runloop"; -import discourseDebounce from "discourse-common/lib/debounce"; -import { inject as service } from "@ember/service"; - -export default class ChatChannelMembersView extends Component { - @service chatApi; - - tagName = ""; - channel = null; - isSearchFocused = false; - onlineUsers = null; - filter = null; - inputSelector = "channel-members-view__search-input"; - members = null; - - didInsertElement() { - this._super(...arguments); - - if (!this.channel) { - return; - } - - this._focusSearch(); - this.set("members", this.chatApi.listChannelMemberships(this.channel.id)); - this.members.load(); - - this.appEvents.on("chat:refresh-channel-members", this, "onFilterMembers"); - } - - willDestroyElement() { - this._super(...arguments); - - this.appEvents.off("chat:refresh-channel-members", this, "onFilterMembers"); - } - - @action - onFilterMembers(username) { - this.set("filter", username); - this.set("members", this.chatApi.listChannelMemberships(this.channel.id)); - - discourseDebounce( - this, - this.members.load, - { username: this.filter }, - INPUT_DELAY - ); - } - - @action - load() { - discourseDebounce(this, this.members.load, INPUT_DELAY); - } - - _focusSearch() { - if (this.capabilities.isIpadOS || this.site.mobileView) { - return; - } - - schedule("afterRender", () => { - document.getElementsByClassName(this.inputSelector)[0]?.focus(); - }); - } -} diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-channel-members.gjs b/plugins/chat/assets/javascripts/discourse/components/chat-channel-members.gjs new file mode 100644 index 00000000000..bc102c97d83 --- /dev/null +++ b/plugins/chat/assets/javascripts/discourse/components/chat-channel-members.gjs @@ -0,0 +1,125 @@ +import Component from "@glimmer/component"; +import { action } from "@ember/object"; +import { inject as service } from "@ember/service"; +import ChatUserInfo from "discourse/plugins/chat/discourse/components/chat-user-info"; +import gt from "truth-helpers/helpers/gt"; +import { cached, tracked } from "@glimmer/tracking"; +import { INPUT_DELAY } from "discourse-common/config/environment"; +import discourseDebounce from "discourse-common/lib/debounce"; +import { modifier } from "ember-modifier"; +import isElementInViewport from "discourse/lib/is-element-in-viewport"; +import DcFilterInput from "discourse/plugins/chat/discourse/components/dc-filter-input"; +import I18n from "I18n"; +import { hash } from "@ember/helper"; +import { schedule } from "@ember/runloop"; + +export default class ChatChannelMembers extends Component { + + + @service chatApi; + @service modal; + @service loadingSlider; + + @tracked filter = ""; + + filterPlaceholder = I18n.t("chat.members_view.filter_placeholder"); + noMembershipsFoundLabel = I18n.t("chat.channel.no_memberships_found"); + noMembershipsLabel = I18n.t("chat.channel.no_memberships"); + + focusInput = modifier((element) => { + schedule("afterRender", () => { + element.focus(); + }); + }); + + fill = modifier((element) => { + this.resizeObserver = new ResizeObserver(() => { + if (isElementInViewport(element)) { + this.load(); + } + }); + + this.resizeObserver.observe(element); + + return () => { + this.resizeObserver.disconnect(); + }; + }); + + loadMore = modifier((element) => { + this.intersectionObserver = new IntersectionObserver(this.load); + this.intersectionObserver.observe(element); + + return () => { + this.intersectionObserver.disconnect(); + }; + }); + + get noResults() { + return this.members.fetchedOnce && !this.members.loading; + } + + @cached + get members() { + const params = {}; + if (this.filter?.length) { + params.username = this.filter; + } + + return this.chatApi.listChannelMemberships(this.args.channel.id, params); + } + + @action + load() { + discourseDebounce(this, this.debouncedLoad, INPUT_DELAY); + } + + @action + mutFilter(event) { + this.filter = event.target.value; + this.load(); + } + + async debouncedLoad() { + this.loadingSlider.transitionStarted(); + await this.members.load({ limit: 20 }); + this.loadingSlider.transitionEnded(); + } +} diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-channel-settings-saved-indicator.hbs b/plugins/chat/assets/javascripts/discourse/components/chat-channel-settings-saved-indicator.hbs deleted file mode 100644 index 43cb508c047..00000000000 --- a/plugins/chat/assets/javascripts/discourse/components/chat-channel-settings-saved-indicator.hbs +++ /dev/null @@ -1,14 +0,0 @@ - - {{#if this.isActive}} - {{d-icon "check"}} - {{i18n "saved"}} - {{/if}} - \ No newline at end of file diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-channel-settings-saved-indicator.js b/plugins/chat/assets/javascripts/discourse/components/chat-channel-settings-saved-indicator.js deleted file mode 100644 index 292e3af2308..00000000000 --- a/plugins/chat/assets/javascripts/discourse/components/chat-channel-settings-saved-indicator.js +++ /dev/null @@ -1,28 +0,0 @@ -import discourseLater from "discourse-common/lib/later"; -import Component from "@glimmer/component"; -import { action } from "@ember/object"; -import { tracked } from "@glimmer/tracking"; -import { cancel } from "@ember/runloop"; - -const ACTIVE_DURATION = 2000; - -export default class ChatChannelSettingsSavedIndicator extends Component { - @tracked isActive = false; - property = null; - - @action - activate() { - cancel(this._deactivateHandler); - - this.isActive = true; - - this._deactivateHandler = discourseLater(() => { - this.isActive = false; - }, ACTIVE_DURATION); - } - - @action - teardown() { - cancel(this._deactivateHandler); - } -} diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-channel-settings-view.hbs b/plugins/chat/assets/javascripts/discourse/components/chat-channel-settings-view.hbs deleted file mode 100644 index 9ef7414e025..00000000000 --- a/plugins/chat/assets/javascripts/discourse/components/chat-channel-settings-view.hbs +++ /dev/null @@ -1,199 +0,0 @@ -
-
- -
- -
-
- - {{#unless @channel.currentUserMembership.muted}} -
- -
- -
-
- -
- -
- -
-
- {{/unless}} -
- {{d-icon "info-circle"}} - -
-
- -{{#if this.adminSectionAvailable}} -

- {{i18n "chat.settings.admin_title"}} -

- - {{#if this.autoJoinAvailable}} -
-
- - -

- {{i18n - "chat.settings.auto_join_users_info" - category=@channel.chatable.name - }} -

-
-
- {{/if}} - - {{#if this.togglingChannelWideMentionsAvailable}} -
-
- - -

- {{i18n - "chat.settings.channel_wide_mentions_description" - channel=@channel.title - }} -

-
-
- {{/if}} - -
-
- - -
-
-{{/if}} - -{{#unless @channel.isDirectMessageChannel}} -
- {{#if (chat-guardian "can-edit-chat-channel")}} - {{#if (chat-guardian "can-archive-channel" @channel)}} -
- -
- {{/if}} - - {{#if @channel.isClosed}} -
- -
- {{else}} -
- -
- {{/if}} - -
- -
- {{/if}} -
-{{/unless}} \ No newline at end of file diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-channel-settings-view.js b/plugins/chat/assets/javascripts/discourse/components/chat-channel-settings-view.js deleted file mode 100644 index a5c980d5ed1..00000000000 --- a/plugins/chat/assets/javascripts/discourse/components/chat-channel-settings-view.js +++ /dev/null @@ -1,203 +0,0 @@ -import Component from "@glimmer/component"; -import { action } from "@ember/object"; -import { inject as service } from "@ember/service"; -import I18n from "I18n"; -import ChatModalArchiveChannel from "discourse/plugins/chat/discourse/components/chat/modal/archive-channel"; -import ChatModalDeleteChannel from "discourse/plugins/chat/discourse/components/chat/modal/delete-channel"; -import ChatModalToggleChannelStatus from "discourse/plugins/chat/discourse/components/chat/modal/toggle-channel-status"; - -const NOTIFICATION_LEVELS = [ - { name: I18n.t("chat.notification_levels.never"), value: "never" }, - { name: I18n.t("chat.notification_levels.mention"), value: "mention" }, - { name: I18n.t("chat.notification_levels.always"), value: "always" }, -]; - -const MUTED_OPTIONS = [ - { name: I18n.t("chat.settings.muted_on"), value: true }, - { name: I18n.t("chat.settings.muted_off"), value: false }, -]; - -const AUTO_ADD_USERS_OPTIONS = [ - { name: I18n.t("yes_value"), value: true }, - { name: I18n.t("no_value"), value: false }, -]; - -const THREADING_ENABLED_OPTIONS = [ - { name: I18n.t("chat.settings.threading_enabled"), value: true }, - { name: I18n.t("chat.settings.threading_disabled"), value: false }, -]; - -const CHANNEL_WIDE_MENTIONS_OPTIONS = [ - { name: I18n.t("yes_value"), value: true }, - { - name: I18n.t("no_value"), - value: false, - }, -]; - -export default class ChatChannelSettingsView extends Component { - @service chat; - @service chatApi; - @service chatGuardian; - @service currentUser; - @service siteSettings; - @service router; - @service dialog; - @service modal; - - notificationLevels = NOTIFICATION_LEVELS; - mutedOptions = MUTED_OPTIONS; - threadingEnabledOptions = THREADING_ENABLED_OPTIONS; - autoAddUsersOptions = AUTO_ADD_USERS_OPTIONS; - channelWideMentionsOptions = CHANNEL_WIDE_MENTIONS_OPTIONS; - isSavingNotificationSetting = false; - savedDesktopNotificationLevel = false; - savedMobileNotificationLevel = false; - savedMuted = false; - - get togglingChannelWideMentionsAvailable() { - return this.args.channel.isCategoryChannel; - } - - get autoJoinAvailable() { - return ( - this.siteSettings.max_chat_auto_joined_users > 0 && - this.args.channel.isCategoryChannel - ); - } - - get adminSectionAvailable() { - return ( - this.chatGuardian.canEditChatChannel() && - (this.autoJoinAvailable || this.togglingChannelWideMentionsAvailable) - ); - } - - get canArchiveChannel() { - return ( - this.siteSettings.chat_allow_archiving_channels && - !this.args.channel.isArchived && - !this.args.channel.isReadOnly - ); - } - - @action - saveNotificationSettings(frontendKey, backendKey, newValue) { - if (this.args.channel.currentUserMembership[frontendKey] === newValue) { - return; - } - - const settings = {}; - settings[backendKey] = newValue; - return this.chatApi - .updateCurrentUserChannelNotificationsSettings( - this.args.channel.id, - settings - ) - .then((result) => { - this.args.channel.currentUserMembership[frontendKey] = - result.membership[backendKey]; - }); - } - - @action - onArchiveChannel() { - return this.modal.show(ChatModalArchiveChannel, { - model: { channel: this.args.channel }, - }); - } - - @action - onDeleteChannel() { - return this.modal.show(ChatModalDeleteChannel, { - model: { channel: this.args.channel }, - }); - } - - @action - onToggleChannelState() { - this.modal.show(ChatModalToggleChannelStatus, { model: this.args.channel }); - } - - @action - onToggleAutoJoinUsers() { - if (!this.args.channel.autoJoinUsers) { - this.onEnableAutoJoinUsers(); - } else { - this.onDisableAutoJoinUsers(); - } - } - - @action - onToggleThreadingEnabled(value) { - return this._updateChannelProperty( - this.args.channel, - "threading_enabled", - value - ).then((result) => { - this.args.channel.threadingEnabled = result.channel.threading_enabled; - }); - } - - @action - onToggleChannelWideMentions() { - const newValue = !this.args.channel.allowChannelWideMentions; - if (this.args.channel.allowChannelWideMentions === newValue) { - return; - } - - return this._updateChannelProperty( - this.args.channel, - "allow_channel_wide_mentions", - newValue - ).then((result) => { - this.args.channel.allowChannelWideMentions = - result.channel.allow_channel_wide_mentions; - }); - } - - onDisableAutoJoinUsers() { - if (this.args.channel.autoJoinUsers === false) { - return; - } - - return this._updateChannelProperty( - this.args.channel, - "auto_join_users", - false - ).then((result) => { - this.args.channel.autoJoinUsers = result.channel.auto_join_users; - }); - } - - onEnableAutoJoinUsers() { - if (this.args.channel.autoJoinUsers === true) { - return; - } - - this.dialog.confirm({ - message: I18n.t("chat.settings.auto_join_users_warning", { - category: this.args.channel.chatable.name, - }), - didConfirm: () => - this._updateChannelProperty( - this.args.channel, - "auto_join_users", - true - ).then((result) => { - this.args.channel.autoJoinUsers = result.channel.auto_join_users; - }), - }); - } - - _updateChannelProperty(channel, property, value) { - const payload = {}; - payload[property] = value; - - return this.chatApi.updateChannel(channel.id, payload).catch((event) => { - if (event.jqXHR?.responseJSON?.errors) { - this.flash(event.jqXHR.responseJSON.errors.join("\n"), "error"); - } - }); - } -} diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-channel-settings.gjs b/plugins/chat/assets/javascripts/discourse/components/chat-channel-settings.gjs new file mode 100644 index 00000000000..b2b562c0046 --- /dev/null +++ b/plugins/chat/assets/javascripts/discourse/components/chat-channel-settings.gjs @@ -0,0 +1,581 @@ +import Component from "@glimmer/component"; +import ChatForm from "discourse/plugins/chat/discourse/components/chat/form"; +import DToggleSwitch from "discourse/components/d-toggle-switch"; +import { action } from "@ember/object"; +import { inject as service } from "@ember/service"; +import ChatModalArchiveChannel from "discourse/plugins/chat/discourse/components/chat/modal/archive-channel"; +import ChatModalDeleteChannel from "discourse/plugins/chat/discourse/components/chat/modal/delete-channel"; +import ChatModalToggleChannelStatus from "discourse/plugins/chat/discourse/components/chat/modal/toggle-channel-status"; +import { on } from "@ember/modifier"; +import I18n from "I18n"; +import { fn, hash } from "@ember/helper"; +import { popupAjaxError } from "discourse/lib/ajax-error"; +import ComboBox from "select-kit/components/combo-box"; +import ChatRetentionReminderText from "discourse/plugins/chat/discourse/components/chat-retention-reminder-text"; +import DButton from "discourse/components/d-button"; +import ToggleChannelMembershipButton from "discourse/plugins/chat/discourse/components/toggle-channel-membership-button"; +import replaceEmoji from "discourse/helpers/replace-emoji"; +import ChatModalEditChannelName from "discourse/plugins/chat/discourse/components/chat/modal/edit-channel-name"; +import categoryBadge from "discourse/helpers/category-badge"; +import ChatModalEditChannelDescription from "discourse/plugins/chat/discourse/components/chat/modal/edit-channel-description"; +import { LinkTo } from "@ember/routing"; + +const NOTIFICATION_LEVELS = [ + { name: I18n.t("chat.notification_levels.never"), value: "never" }, + { name: I18n.t("chat.notification_levels.mention"), value: "mention" }, + { name: I18n.t("chat.notification_levels.always"), value: "always" }, +]; + +export default class ChatAboutScreen extends Component { + + + @service chatApi; + @service chatGuardian; + @service currentUser; + @service siteSettings; + @service dialog; + @service modal; + @service site; + @service toasts; + + notificationLevels = NOTIFICATION_LEVELS; + + settingsSectionTitle = I18n.t("chat.settings.settings_title"); + channelInfoSectionTitle = I18n.t("chat.settings.info_title"); + categoryLabel = I18n.t("chat.settings.category_label"); + historyLabel = I18n.t("chat.settings.history_label"); + adminSectionTitle = I18n.t("chat.settings.admin_title"); + membersLabel = I18n.t("chat.settings.tabs.members_label"); + descriptionSectionTitle = I18n.t("chat.about_view.description"); + titleSectionTitle = I18n.t("chat.about_view.title"); + descriptionPlaceholder = I18n.t( + "chat.channel_edit_description_modal.description" + ); + toggleThreadingLabel = I18n.t("chat.settings.channel_threading_label"); + toggleThreadingDescription = I18n.t( + "chat.settings.channel_threading_description" + ); + muteSectionLabel = I18n.t("chat.settings.mute"); + channelWideMentionsLabel = I18n.t( + "chat.settings.channel_wide_mentions_label" + ); + autoJoinLabel = I18n.t("chat.settings.auto_join_users_label"); + desktopNotificationsLevelLabel = I18n.t( + "chat.settings.desktop_notification_level" + ); + mobileNotificationsLevelLabel = I18n.t( + "chat.settings.mobile_notification_level" + ); + + get canEditChannel() { + return this.chatGuardian.canEditChatChannel(); + } + + get shouldRenderTitleSection() { + return this.args.channel.isCategoryChannel; + } + + get shouldRenderDescriptionSection() { + return this.args.channel.isCategoryChannel; + } + + get shouldRenderStatusSection() { + return this.args.channel.isCategoryChannel; + } + + get shouldRenderArchiveRow() { + return this.chatGuardian.canArchiveChannel(this.args.channel); + } + + get toggleChannelWideMentionsAvailable() { + return this.args.channel.isCategoryChannel && this.args.channel.isOpen; + } + + get toggleThreadingAvailable() { + return this.args.channel.isCategoryChannel && this.args.channel.isOpen; + } + + get channelWideMentionsDescription() { + return I18n.t("chat.settings.channel_wide_mentions_description", { + channel: this.args.channel.title, + }); + } + + get isChannelMuted() { + return this.args.channel.currentUserMembership.muted; + } + + get shouldRenderChannelWideMentionsAvailable() { + return this.args.channel.isCategoryChannel; + } + + get shouldRenderDesktopNotificationsLevelSection() { + return !this.isChannelMuted; + } + + get shouldRenderMobileNotificationsLevelSection() { + return !this.isChannelMuted; + } + + get autoJoinAvailable() { + return ( + this.siteSettings.max_chat_auto_joined_users > 0 && + this.args.channel.isCategoryChannel && + this.args.channel.isOpen + ); + } + + get shouldRenderAdminSection() { + return ( + this.canEditChannel && + (this.toggleChannelWideMentionsAvailable || + this.args.channel.isCategoryChannel) + ); + } + + @action + async onToggleChannelWideMentions() { + const newValue = !this.args.channel.allowChannelWideMentions; + + if (this.args.channel.allowChannelWideMentions === newValue) { + return; + } + + try { + this.args.channel.allowChannelWideMentions = newValue; + + const result = await this._updateChannelProperty( + this.args.channel, + "allow_channel_wide_mentions", + newValue + ); + + this.args.channel.allowChannelWideMentions = + result.channel.allow_channel_wide_mentions; + } catch (error) { + popupAjaxError(error); + } + } + + @action + async onToggleAutoJoinUsers() { + if (this.args.channel.autoJoinUsers) { + return await this.onDisableAutoJoinUsers(); + } + + return await this.onEnableAutoJoinUsers(); + } + + @action + async onDisableAutoJoinUsers() { + if (this.args.channel.autoJoinUsers === false) { + return; + } + + try { + this.args.channel.autoJoinUsers = false; + + const result = await this._updateChannelProperty( + this.args.channel, + "auto_join_users", + false + ); + + this.args.channel.autoJoinUsers = result.channel.auto_join_users; + } catch (error) { + popupAjaxError(error); + } + } + + @action + onEnableAutoJoinUsers() { + if (this.args.channel.autoJoinUsers === true) { + return; + } + + return this.dialog.confirm({ + message: I18n.t("chat.settings.auto_join_users_warning", { + category: this.args.channel.chatable.name, + }), + didConfirm: async () => { + try { + const result = await this._updateChannelProperty( + this.args.channel, + "auto_join_users", + true + ); + + this.args.channel.autoJoinUsers = result.channel.auto_join_users; + } catch (error) { + popupAjaxError(error); + } + }, + }); + } + + @action + onToggleMuted() { + const newValue = !this.args.channel.currentUserMembership.muted; + this.saveNotificationSettings("muted", "muted", newValue); + } + + @action + async saveNotificationSettings(frontendKey, backendKey, newValue) { + if (this.args.channel.currentUserMembership[frontendKey] === newValue) { + return; + } + + this.args.channel.currentUserMembership[frontendKey] = newValue; + + const settings = {}; + settings[backendKey] = newValue; + + try { + const result = + await this.chatApi.updateCurrentUserChannelNotificationsSettings( + this.args.channel.id, + settings + ); + + this.args.channel.currentUserMembership[frontendKey] = + result.membership[backendKey]; + this.toasts.success({ data: { message: I18n.t("saved") } }); + } catch (error) { + popupAjaxError(error); + } + } + + @action + async _updateChannelProperty(channel, property, value) { + try { + const result = await this.chatApi.updateChannel(channel.id, { + [property]: value, + }); + this.toasts.success({ data: { message: I18n.t("saved") } }); + return result; + } catch (error) { + popupAjaxError(error); + } + } + + @action + async onToggleThreadingEnabled(value) { + try { + this.args.channel.threadingEnabled = !value; + const result = await this._updateChannelProperty( + this.args.channel, + "threading_enabled", + !value + ); + this.args.channel.threadingEnabled = result.channel.threading_enabled; + } catch (error) { + popupAjaxError(error); + } + } + + @action + onToggleChannelState() { + return this.modal.show(ChatModalToggleChannelStatus, { + model: this.args.channel, + }); + } + + @action + onArchiveChannel() { + return this.modal.show(ChatModalArchiveChannel, { + model: { channel: this.args.channel }, + }); + } + + @action + onDeleteChannel() { + return this.modal.show(ChatModalDeleteChannel, { + model: { channel: this.args.channel }, + }); + } + + @action + onEditChannelName() { + return this.modal.show(ChatModalEditChannelName, { + model: this.args.channel, + }); + } + + @action + onEditChannelDescription() { + return this.modal.show(ChatModalEditChannelDescription, { + model: this.args.channel, + }); + } +} diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-retention-reminder-text.js b/plugins/chat/assets/javascripts/discourse/components/chat-retention-reminder-text.gjs similarity index 90% rename from plugins/chat/assets/javascripts/discourse/components/chat-retention-reminder-text.js rename to plugins/chat/assets/javascripts/discourse/components/chat-retention-reminder-text.gjs index 5ad70302b9f..71fc34beca8 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-retention-reminder-text.js +++ b/plugins/chat/assets/javascripts/discourse/components/chat-retention-reminder-text.gjs @@ -3,6 +3,12 @@ import I18n from "I18n"; import { inject as service } from "@ember/service"; export default class ChatRetentionReminderText extends Component { + + @service siteSettings; get text() { diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-retention-reminder-text.hbs b/plugins/chat/assets/javascripts/discourse/components/chat-retention-reminder-text.hbs deleted file mode 100644 index 309d7f6c5fe..00000000000 --- a/plugins/chat/assets/javascripts/discourse/components/chat-retention-reminder-text.hbs +++ /dev/null @@ -1,3 +0,0 @@ - - {{this.text}} - \ No newline at end of file diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-user-info.gjs b/plugins/chat/assets/javascripts/discourse/components/chat-user-info.gjs new file mode 100644 index 00000000000..d4ae7ebc428 --- /dev/null +++ b/plugins/chat/assets/javascripts/discourse/components/chat-user-info.gjs @@ -0,0 +1,25 @@ +import Component from "@glimmer/component"; +import { userPath } from "discourse/lib/url"; +import ChatUserAvatar from "discourse/plugins/chat/discourse/components/chat/user-avatar"; +import ChatUserDisplayName from "discourse/plugins/chat/discourse/components/chat-user-display-name"; + +export default class ChatUserInfo extends Component { + + + get avatarSize() { + return this.args.avatarSize ?? "medium"; + } + + get userPath() { + return userPath(this.args.user.username); + } +} diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-user-info.hbs b/plugins/chat/assets/javascripts/discourse/components/chat-user-info.hbs deleted file mode 100644 index 1dd5843bf8e..00000000000 --- a/plugins/chat/assets/javascripts/discourse/components/chat-user-info.hbs +++ /dev/null @@ -1,8 +0,0 @@ -{{#if @user}} - - - - - - -{{/if}} \ No newline at end of file diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-user-info.js b/plugins/chat/assets/javascripts/discourse/components/chat-user-info.js deleted file mode 100644 index ccc7d150af6..00000000000 --- a/plugins/chat/assets/javascripts/discourse/components/chat-user-info.js +++ /dev/null @@ -1,8 +0,0 @@ -import Component from "@glimmer/component"; -import { userPath } from "discourse/lib/url"; - -export default class ChatUserInfo extends Component { - get userPath() { - return userPath(this.args.user.username); - } -} diff --git a/plugins/chat/assets/javascripts/discourse/components/chat/form.gjs b/plugins/chat/assets/javascripts/discourse/components/chat/form.gjs new file mode 100644 index 00000000000..d665004c2f2 --- /dev/null +++ b/plugins/chat/assets/javascripts/discourse/components/chat/form.gjs @@ -0,0 +1,14 @@ +import Component from "@glimmer/component"; +import ChatFormSection from "discourse/plugins/chat/discourse/components/chat/form/section"; + +export default class ChatForm extends Component { + + + get yieldableArgs() { + return { section: ChatFormSection }; + } +} diff --git a/plugins/chat/assets/javascripts/discourse/components/chat/form/row.gjs b/plugins/chat/assets/javascripts/discourse/components/chat/form/row.gjs new file mode 100644 index 00000000000..faef5e17944 --- /dev/null +++ b/plugins/chat/assets/javascripts/discourse/components/chat/form/row.gjs @@ -0,0 +1,48 @@ +import Component from "@glimmer/component"; +import { LinkTo } from "@ember/routing"; +import icon from "discourse-common/helpers/d-icon"; +import concatClass from "discourse/helpers/concat-class"; + +export default class ChatFormRow extends Component { + +} diff --git a/plugins/chat/assets/javascripts/discourse/components/chat/form/section.gjs b/plugins/chat/assets/javascripts/discourse/components/chat/form/section.gjs new file mode 100644 index 00000000000..47dcf2e8530 --- /dev/null +++ b/plugins/chat/assets/javascripts/discourse/components/chat/form/section.gjs @@ -0,0 +1,22 @@ +import Component from "@glimmer/component"; +import ChatFormRow from "discourse/plugins/chat/discourse/components/chat/form/row"; + +export default class ChatFormSection extends Component { + + + get yieldableArgs() { + return { row: ChatFormRow }; + } +} diff --git a/plugins/chat/assets/javascripts/discourse/components/chat/modal/edit-channel-name.hbs b/plugins/chat/assets/javascripts/discourse/components/chat/modal/edit-channel-name.hbs index 0256880fac7..2838b84ed5a 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat/modal/edit-channel-name.hbs +++ b/plugins/chat/assets/javascripts/discourse/components/chat/modal/edit-channel-name.hbs @@ -25,12 +25,10 @@
+ {{! template-lint-disable modifier-name-case }} +
+ {{#if @icons.left}} + {{icon @icons.left class="-left"}} + {{/if}} + + + + {{yield}} + + {{#if @icons.right}} + {{icon @icons.right class="-right"}} + {{/if}} +
+ + + @tracked isFocused = false; + + focusState = modifier((element) => { + const focusInHandler = () => { + this.isFocused = true; + }; + const focusOutHandler = () => { + this.isFocused = false; + }; + + element.addEventListener("focusin", focusInHandler); + element.addEventListener("focusout", focusOutHandler); + + return () => { + element.removeEventListener("focusin", focusInHandler); + element.removeEventListener("focusout", focusOutHandler); + }; + }); +} diff --git a/plugins/chat/assets/javascripts/discourse/components/dc-filter-input.hbs b/plugins/chat/assets/javascripts/discourse/components/dc-filter-input.hbs deleted file mode 100644 index a3d465fe24e..00000000000 --- a/plugins/chat/assets/javascripts/discourse/components/dc-filter-input.hbs +++ /dev/null @@ -1,26 +0,0 @@ -
- {{#if @icons.left}} - {{d-icon @icons.left class="-left"}} - {{/if}} - - - - {{yield}} - - {{#if @icons.right}} - {{d-icon @icons.right class="-right"}} - {{/if}} -
\ No newline at end of file diff --git a/plugins/chat/assets/javascripts/discourse/components/dc-filter-input.js b/plugins/chat/assets/javascripts/discourse/components/dc-filter-input.js deleted file mode 100644 index 60b7b82ea4f..00000000000 --- a/plugins/chat/assets/javascripts/discourse/components/dc-filter-input.js +++ /dev/null @@ -1,3 +0,0 @@ -import Component from "@glimmer/component"; - -export default class DcFilterInput extends Component {} diff --git a/plugins/chat/assets/javascripts/discourse/controllers/chat-channel-info-about.js b/plugins/chat/assets/javascripts/discourse/controllers/chat-channel-info-about.js deleted file mode 100644 index 8748478788f..00000000000 --- a/plugins/chat/assets/javascripts/discourse/controllers/chat-channel-info-about.js +++ /dev/null @@ -1,26 +0,0 @@ -import Controller from "@ember/controller"; -import { action } from "@ember/object"; -import ModalFunctionality from "discourse/mixins/modal-functionality"; -import { inject as service } from "@ember/service"; -import ChatModalEditChannelDescription from "discourse/plugins/chat/discourse/components/chat/modal/edit-channel-description"; -import ChatModalEditChannelName from "discourse/plugins/chat/discourse/components/chat/modal/edit-channel-name"; - -export default class ChatChannelInfoAboutController extends Controller.extend( - ModalFunctionality -) { - @service modal; - - @action - onEditChatChannelName() { - return this.modal.show(ChatModalEditChannelName, { - model: this.model, - }); - } - - @action - onEditChatChannelDescription() { - return this.modal.show(ChatModalEditChannelDescription, { - model: this.model, - }); - } -} diff --git a/plugins/chat/assets/javascripts/discourse/controllers/chat-channel-info-members.js b/plugins/chat/assets/javascripts/discourse/controllers/chat-channel-info-members.js deleted file mode 100644 index 48e3c615581..00000000000 --- a/plugins/chat/assets/javascripts/discourse/controllers/chat-channel-info-members.js +++ /dev/null @@ -1,3 +0,0 @@ -import Controller from "@ember/controller"; - -export default class ChatChannelInfoMembersController extends Controller {} diff --git a/plugins/chat/assets/javascripts/discourse/controllers/chat-channel-info-settings.js b/plugins/chat/assets/javascripts/discourse/controllers/chat-channel-info-settings.js deleted file mode 100644 index a70d62d1ea9..00000000000 --- a/plugins/chat/assets/javascripts/discourse/controllers/chat-channel-info-settings.js +++ /dev/null @@ -1,3 +0,0 @@ -import Controller from "@ember/controller"; - -export default class ChatChannelInfoSettingsController extends Controller {} diff --git a/plugins/chat/assets/javascripts/discourse/controllers/chat-channel-info.js b/plugins/chat/assets/javascripts/discourse/controllers/chat-channel-info.js deleted file mode 100644 index 65c132080d9..00000000000 --- a/plugins/chat/assets/javascripts/discourse/controllers/chat-channel-info.js +++ /dev/null @@ -1,34 +0,0 @@ -import Controller from "@ember/controller"; -import { inject as service } from "@ember/service"; -import { reads } from "@ember/object/computed"; -import { computed } from "@ember/object"; - -export default class ChatChannelInfoIndexController extends Controller { - @service router; - @service chat; - @service chatChannelInfoRouteOriginManager; - - @reads("router.currentRoute.localName") tab; - - @computed("model.{membershipsCount,status,currentUserMembership.following}") - get tabs() { - const tabs = []; - - if (!this.model.isDirectMessageChannel) { - tabs.push("about"); - } - - if (this.model.isOpen && this.model.membershipsCount >= 1) { - tabs.push("members"); - } - - if ( - this.currentUser?.staff || - this.model.currentUserMembership?.following - ) { - tabs.push("settings"); - } - - return tabs; - } -} diff --git a/plugins/chat/assets/javascripts/discourse/lib/collection.js b/plugins/chat/assets/javascripts/discourse/lib/collection.js index e472d45bffe..08014429e6b 100644 --- a/plugins/chat/assets/javascripts/discourse/lib/collection.js +++ b/plugins/chat/assets/javascripts/discourse/lib/collection.js @@ -12,9 +12,10 @@ export default class Collection { @tracked loading = false; @tracked fetchedOnce = false; - constructor(resourceURL, handler) { + constructor(resourceURL, handler, params = {}) { this._resourceURL = resourceURL; this._handler = handler; + this._params = params; this._fetchedAll = false; } @@ -94,6 +95,6 @@ export default class Collection { } #fetch(url) { - return ajax(url, { type: "GET" }); + return ajax(url, { type: "GET", data: this._params }); } } diff --git a/plugins/chat/assets/javascripts/discourse/routes/chat-channel-info-about.js b/plugins/chat/assets/javascripts/discourse/routes/chat-channel-info-about.js deleted file mode 100644 index 9bb3d7b81b0..00000000000 --- a/plugins/chat/assets/javascripts/discourse/routes/chat-channel-info-about.js +++ /dev/null @@ -1,12 +0,0 @@ -import DiscourseRoute from "discourse/routes/discourse"; -import { inject as service } from "@ember/service"; - -export default class ChatChannelInfoAboutRoute extends DiscourseRoute { - @service router; - - afterModel(model) { - if (model.isDirectMessageChannel) { - this.router.replaceWith("chat.channel.info.index"); - } - } -} diff --git a/plugins/chat/assets/javascripts/discourse/routes/chat-channel-info-index.js b/plugins/chat/assets/javascripts/discourse/routes/chat-channel-info-index.js index 03258bae148..34ffcf2343e 100644 --- a/plugins/chat/assets/javascripts/discourse/routes/chat-channel-info-index.js +++ b/plugins/chat/assets/javascripts/discourse/routes/chat-channel-info-index.js @@ -4,15 +4,7 @@ import { inject as service } from "@ember/service"; export default class ChatChannelInfoIndexRoute extends DiscourseRoute { @service router; - afterModel(model) { - if (model.isDirectMessageChannel) { - if (model.isOpen && model.membershipsCount >= 1) { - this.router.replaceWith("chat.channel.info.members"); - } else { - this.router.replaceWith("chat.channel.info.settings"); - } - } else { - this.router.replaceWith("chat.channel.info.about"); - } + afterModel() { + this.router.replaceWith("chat.channel.info.settings"); } } diff --git a/plugins/chat/assets/javascripts/discourse/routes/chat-channel-info-members.js b/plugins/chat/assets/javascripts/discourse/routes/chat-channel-info-members.js index 6260e3f1a65..e6912480514 100644 --- a/plugins/chat/assets/javascripts/discourse/routes/chat-channel-info-members.js +++ b/plugins/chat/assets/javascripts/discourse/routes/chat-channel-info-members.js @@ -5,12 +5,8 @@ export default class ChatChannelInfoMembersRoute extends DiscourseRoute { @service router; afterModel(model) { - if (!model.isOpen) { + if (!model.isOpen || model.membershipsCount < 1) { return this.router.replaceWith("chat.channel.info.settings"); } - - if (model.membershipsCount < 1) { - return this.router.replaceWith("chat.channel.info"); - } } } diff --git a/plugins/chat/assets/javascripts/discourse/routes/chat-channel-info-settings.js b/plugins/chat/assets/javascripts/discourse/routes/chat-channel-info-settings.js deleted file mode 100644 index 8468b04492d..00000000000 --- a/plugins/chat/assets/javascripts/discourse/routes/chat-channel-info-settings.js +++ /dev/null @@ -1,13 +0,0 @@ -import DiscourseRoute from "discourse/routes/discourse"; -import { inject as service } from "@ember/service"; - -export default class ChatChannelInfoSettingsRoute extends DiscourseRoute { - @service router; - @service currentUser; - - afterModel(model) { - if (!this.currentUser?.staff && !model.currentUserMembership?.following) { - this.router.replaceWith("chat.channel.info"); - } - } -} diff --git a/plugins/chat/assets/javascripts/discourse/services/chat-api.js b/plugins/chat/assets/javascripts/discourse/services/chat-api.js index daae4cc8b19..f4ab3c79068 100644 --- a/plugins/chat/assets/javascripts/discourse/services/chat-api.js +++ b/plugins/chat/assets/javascripts/discourse/services/chat-api.js @@ -233,14 +233,15 @@ export default class ChatApi extends Service { * @param {number} channelId - The ID of the channel. * @returns {Collection} */ - listChannelMemberships(channelId) { + listChannelMemberships(channelId, params = {}) { return new Collection( `${this.#basePath}/channels/${channelId}/memberships`, (response) => { return response.memberships.map((membership) => UserChatChannelMembership.create(membership) ); - } + }, + params ); } diff --git a/plugins/chat/assets/javascripts/discourse/services/chat-channels-manager.js b/plugins/chat/assets/javascripts/discourse/services/chat-channels-manager.js index 882353e6d45..7a6f9d8df80 100644 --- a/plugins/chat/assets/javascripts/discourse/services/chat-channels-manager.js +++ b/plugins/chat/assets/javascripts/discourse/services/chat-channels-manager.js @@ -18,6 +18,7 @@ export default class ChatChannelsManager extends Service { @service chatSubscriptionsManager; @service chatApi; @service currentUser; + @service router; @tracked _cached = new TrackedObject(); async find(id, options = { fetchIfNotFound: true }) { @@ -131,12 +132,12 @@ export default class ChatChannelsManager extends Service { } async #find(id) { - return this.chatApi - .channel(id) - .catch(popupAjaxError) - .then((result) => { - return this.store(result.channel); - }); + try { + const result = await this.chatApi.channel(id); + return this.store(result.channel); + } catch (error) { + popupAjaxError(error); + } } #cache(channel) { diff --git a/plugins/chat/assets/javascripts/discourse/services/chat-guardian.js b/plugins/chat/assets/javascripts/discourse/services/chat-guardian.js index 791b06938e6..7411643bdaf 100644 --- a/plugins/chat/assets/javascripts/discourse/services/chat-guardian.js +++ b/plugins/chat/assets/javascripts/discourse/services/chat-guardian.js @@ -1,6 +1,9 @@ -import Service from "@ember/service"; +import Service, { inject as service } from "@ember/service"; export default class ChatGuardian extends Service { + @service currentUser; + @service siteSettings; + canEditChatChannel() { return this.canUseChat() && this.currentUser.staff; } diff --git a/plugins/chat/assets/javascripts/discourse/templates/chat-channel-info-about.hbs b/plugins/chat/assets/javascripts/discourse/templates/chat-channel-info-about.hbs deleted file mode 100644 index 83de81608a8..00000000000 --- a/plugins/chat/assets/javascripts/discourse/templates/chat-channel-info-about.hbs +++ /dev/null @@ -1,5 +0,0 @@ - \ No newline at end of file diff --git a/plugins/chat/assets/javascripts/discourse/templates/chat-channel-info-members.hbs b/plugins/chat/assets/javascripts/discourse/templates/chat-channel-info-members.hbs index 50f2aa56629..3294012102c 100644 --- a/plugins/chat/assets/javascripts/discourse/templates/chat-channel-info-members.hbs +++ b/plugins/chat/assets/javascripts/discourse/templates/chat-channel-info-members.hbs @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/plugins/chat/assets/javascripts/discourse/templates/chat-channel-info-settings.hbs b/plugins/chat/assets/javascripts/discourse/templates/chat-channel-info-settings.hbs index 633f0350277..2aac01fe3da 100644 --- a/plugins/chat/assets/javascripts/discourse/templates/chat-channel-info-settings.hbs +++ b/plugins/chat/assets/javascripts/discourse/templates/chat-channel-info-settings.hbs @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/plugins/chat/assets/javascripts/discourse/templates/chat-channel-info.hbs b/plugins/chat/assets/javascripts/discourse/templates/chat-channel-info.hbs index e390f0a194c..660315b5925 100644 --- a/plugins/chat/assets/javascripts/discourse/templates/chat-channel-info.hbs +++ b/plugins/chat/assets/javascripts/discourse/templates/chat-channel-info.hbs @@ -1,65 +1 @@ -
-
-
-
- {{#if this.chatChannelInfoRouteOriginManager.isBrowse}} - - {{d-icon "chevron-left"}} - - {{else}} - - {{d-icon "chevron-left"}} - - {{/if}} -
- - -
-
- - - -
- - -
- {{outlet}} -
-
-
\ No newline at end of file + \ No newline at end of file diff --git a/plugins/chat/assets/stylesheets/common/chat-channel-info.scss b/plugins/chat/assets/stylesheets/common/chat-channel-info.scss index be5dee75b81..3993019c0d6 100644 --- a/plugins/chat/assets/stylesheets/common/chat-channel-info.scss +++ b/plugins/chat/assets/stylesheets/common/chat-channel-info.scss @@ -1,11 +1,19 @@ -.channel-info { +.chat-channel-info { display: flex; flex-direction: column; height: 100%; + padding: 1rem; + + &__nav { + .nav-pills { + margin: 0; + padding-bottom: 1rem; + } + } } // Info header -.channel-info-header { +.chat-channel-info-header { display: flex; justify-content: space-between; align-items: flex-start; @@ -13,129 +21,7 @@ box-sizing: border-box; } -.channel-info-header__title { +.chat-channel-info-header__title { font-size: var(--font-up-2); margin: 0; } - -// About view -.channel-info-about-view__title-input { - width: 100%; -} - -.channel-info-about-view__description-input { - height: 150px; - width: 100%; -} - -.channel-info-about-view__description__helper-text { - color: var(--primary-medium); -} - -.channel-info-about-view__slug { - color: var(--primary-medium); - font-size: var(--font-down-2); -} - -.channel-settings-view__selector { - width: 220px; -} - -.channel-settings-view__channel-threading-tooltip { - padding-left: 0.25rem; - color: var(--tertiary); - cursor: pointer; -} - -.channel-settings-view__muted-selector, -.chat-form__btn.delete-btn { - .d-icon { - color: var(--danger); - } -} - -// Members list -.chat-tabs__memberships-count { - margin-left: 0.25em; -} - -.channel-members-view-wrapper { - display: flex; - flex-direction: column; - height: 100%; - box-sizing: border-box; - padding: 0 1rem; -} - -.channel-members-view__search-input-container { - display: flex; - align-items: center; - border: 1px solid var(--primary-medium); - - &.is-focused { - border: 1px solid var(--tertiary); - } - - .d-icon { - padding: 0.5rem; - color: var(--primary-medium); - } -} - -input.channel-members-view__search-input { - border: 0; - margin: 0; - outline: 0; - width: 100%; - - &:focus { - border: 0; - outline: 0; - } -} - -.channel-members-view__status { - display: flex; - align-items: center; -} - -.channel-members-view__list-container { - display: flex; - flex-direction: column; - margin-top: 1em; - box-sizing: border-box; -} - -.channel-members-view__list-item { - display: flex; - align-items: center; - padding: 0.5rem 0 0.5rem 1px; - - &:not(:last-child) { - border-bottom: 1px solid var(--primary-low); - } - - .chat-user-avatar { - margin-right: 0.5rem; - } -} - -// Channel info edit name and slug modal -.chat-channel-edit-name-slug-modal { - .modal-inner-container { - width: 300px; - } - - &__name-input, - &__slug-input { - display: flex; - margin: 0; - width: 100%; - } -} - -.chat-channel-edit-name-slug-modal__description { - display: flex; - padding: 0.5rem 0; - color: var(--primary-medium); -} diff --git a/plugins/chat/assets/stylesheets/common/chat-channel-members.scss b/plugins/chat/assets/stylesheets/common/chat-channel-members.scss new file mode 100644 index 00000000000..40a07573bce --- /dev/null +++ b/plugins/chat/assets/stylesheets/common/chat-channel-members.scss @@ -0,0 +1,32 @@ +.chat-channel-members { + width: 50%; + min-width: 320px; + + &__filter { + margin-bottom: 1rem; + } + + &__list { + display: flex; + margin: 0; + flex-direction: column; + gap: 0.5rem; + + &-item { + display: flex; + gap: 0.5rem; + list-style: none; + border-bottom: 1px solid var(--primary-low); + height: 42px; + align-items: center; + + &.-no-results { + box-sizing: border-box; + } + + &:last-child { + border-bottom: none; + } + } + } +} diff --git a/plugins/chat/assets/stylesheets/common/chat-channel-settings-saved-indicator.scss b/plugins/chat/assets/stylesheets/common/chat-channel-settings-saved-indicator.scss deleted file mode 100644 index e28129f3e17..00000000000 --- a/plugins/chat/assets/stylesheets/common/chat-channel-settings-saved-indicator.scss +++ /dev/null @@ -1,9 +0,0 @@ -.chat-channel-settings-saved-indicator { - padding-left: 0.5rem; - color: var(--success); - font-weight: normal; - - .d-icon-check { - margin-right: 0.25rem; - } -} diff --git a/plugins/chat/assets/stylesheets/common/chat-channel-settings.scss b/plugins/chat/assets/stylesheets/common/chat-channel-settings.scss new file mode 100644 index 00000000000..86023946e05 --- /dev/null +++ b/plugins/chat/assets/stylesheets/common/chat-channel-settings.scss @@ -0,0 +1,18 @@ +.chat-channel-settings { + width: 50%; + min-width: 320px; + + .chat-channel-settings__slug { + max-width: 250px; + @include ellipsis; + } + + // category badge margin reset + .badge-wrapper.bullet { + margin-right: 0; + } + + .chat-retention-reminder-text { + color: var(--primary-medium); + } +} diff --git a/plugins/chat/assets/stylesheets/common/chat-form.scss b/plugins/chat/assets/stylesheets/common/chat-form.scss index 24e44dc5d84..4071798448a 100644 --- a/plugins/chat/assets/stylesheets/common/chat-form.scss +++ b/plugins/chat/assets/stylesheets/common/chat-form.scss @@ -1,62 +1,86 @@ -.chat-form__section { - margin: 1.5rem 1rem; - - &:first-child { - margin-top: 0; - } - - &:last-child { - margin-bottom: 0; - border-bottom: none; - } -} -.chat-form__section-admin-title { - margin-inline: 1rem; - padding-top: 1rem; - border-top: 1px solid var(--primary-low); -} - -.chat-form__field { - margin-bottom: 1rem; - - &:last-child { - margin-bottom: 0; - } -} - -.chat-form__description { - margin-top: 3px; - color: var(--primary-medium); - font-size: var(--font-down-1); -} - -.chat-form__btn { - border: 0; - background: none; - padding: 0.25rem 0; - margin: 0; -} - -.chat-form__label { - font-weight: 700; +.chat-form { display: flex; - align-items: center; + flex-direction: column; } -.chat-form__label-actions { - margin-left: auto; +.chat-form__row { + &.-separator { + border-bottom: 1px solid var(--primary-low); + } +} - .btn-text { - color: var(--tertiary); +.chat-form__section { + display: flex; + flex-direction: column; + width: 100%; + + & + .chat-form__section { + margin-top: 1rem; + } + + &-title { + font-weight: 700; + font-size: var(--font-down-1); + color: var(--primary-medium); + } + + &-title + &-content { + margin-top: 0.25rem; + } + + &-content { + background: var(--primary-very-low); + gap: 1rem; + display: flex; + padding: 1rem; + flex-direction: column; + } +} + +.chat-form__row { + display: flex; + width: 100%; + + // background: green; + flex-direction: column; + justify-content: center; + + label, + .d-toggle-switch__checkbox-slider { + margin: 0; + } + + &-action { + .chat-form__btn:first-child { + padding-left: 0; + } + } + + &-label + &-action { + margin-left: auto; + } + + &.-link { + color: var(--primary); + + .d-icon { + color: var(--primary-medium); + } + } + + &-content { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + min-height: 40px; + gap: 0.25rem; + } + + &-description { + display: flex; + padding-top: 3px; + color: var(--primary-medium); font-size: var(--font-down-1); } } - -.chat-retention-info { - margin-top: 2rem; - color: var(--primary-high); - - .d-icon { - margin-right: 0.5em; - } -} diff --git a/plugins/chat/assets/stylesheets/common/index.scss b/plugins/chat/assets/stylesheets/common/index.scss index d9ab114867a..220f88a4cbf 100644 --- a/plugins/chat/assets/stylesheets/common/index.scss +++ b/plugins/chat/assets/stylesheets/common/index.scss @@ -8,7 +8,6 @@ @import "chat-channel-card"; @import "chat-channel-info"; @import "chat-channel-preview-card"; -@import "chat-channel-settings-saved-indicator"; @import "chat-channel-title"; @import "chat-composer-dropdown"; @import "chat-composer-upload"; @@ -64,3 +63,5 @@ @import "chat-modal-move-message-to-channel"; @import "chat-scroll-to-bottom"; @import "chat-channel-row"; +@import "chat-channel-members"; +@import "chat-channel-settings"; diff --git a/plugins/chat/assets/stylesheets/mobile/chat-channel-info.scss b/plugins/chat/assets/stylesheets/mobile/chat-channel-info.scss deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/plugins/chat/assets/stylesheets/mobile/chat-channel-members.scss b/plugins/chat/assets/stylesheets/mobile/chat-channel-members.scss new file mode 100644 index 00000000000..2e9eaadc263 --- /dev/null +++ b/plugins/chat/assets/stylesheets/mobile/chat-channel-members.scss @@ -0,0 +1,3 @@ +.chat-channel-members { + width: 100%; +} diff --git a/plugins/chat/assets/stylesheets/mobile/chat-channel-settings.scss b/plugins/chat/assets/stylesheets/mobile/chat-channel-settings.scss new file mode 100644 index 00000000000..871658cf968 --- /dev/null +++ b/plugins/chat/assets/stylesheets/mobile/chat-channel-settings.scss @@ -0,0 +1,3 @@ +.chat-channel-settings { + width: 100%; +} diff --git a/plugins/chat/assets/stylesheets/mobile/index.scss b/plugins/chat/assets/stylesheets/mobile/index.scss index 833388c5a50..7e3065003e4 100644 --- a/plugins/chat/assets/stylesheets/mobile/index.scss +++ b/plugins/chat/assets/stylesheets/mobile/index.scss @@ -1,5 +1,4 @@ @import "base-mobile"; -@import "chat-channel-info"; @import "chat-channel"; @import "chat-composer"; @import "chat-index"; @@ -15,3 +14,5 @@ @import "chat-message-thread-indicator"; @import "chat-message-creator"; @import "chat-channel-row"; +@import "chat-channel-members"; +@import "chat-channel-settings"; diff --git a/plugins/chat/config/locales/client.en.yml b/plugins/chat/config/locales/client.en.yml index 7b3e6828692..72358feed53 100644 --- a/plugins/chat/config/locales/client.en.yml +++ b/plugins/chat/config/locales/client.en.yml @@ -320,7 +320,6 @@ en: back_to_all_channels: "All channels" back_to_channel: "Back" tabs: - about: About members: Members settings: Settings @@ -462,6 +461,11 @@ en: saved: "Saved" unfollow: "Leave" admin_title: "Admin" + settings_title: "Settings" + info_title: "Channel info" + category_label: "Category" + history_label: "History" + members_label: "Members" admin: title: "Chat" @@ -538,14 +542,14 @@ en: other: "%{commaSeparatedUsernames} and %{count} others are typing" retention_reminders: - public_none: "Channel history is retained indefinitely." + public_none: "indefinitely" public: - one: "Channel history is retained for %{count} day." - other: "Channel history is retained for %{count} days." - dm_none: "Personal chat history is retained indefinitely." + one: "%{count} day" + other: "%{count} days" + dm_none: "indefinitely" dm: - one: "Personal chat history is retained for %{count} day." - other: "Personal chat history is retained for %{count} days." + one: "%{count} day" + other: "%{count} days" flags: off_topic: "This message is not relevant to the current discussion as defined by the channel title, and should probably be moved elsewhere." diff --git a/plugins/chat/spec/system/channel_about_page_spec.rb b/plugins/chat/spec/system/channel_about_page_spec.rb deleted file mode 100644 index 550fa4c3ee4..00000000000 --- a/plugins/chat/spec/system/channel_about_page_spec.rb +++ /dev/null @@ -1,138 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe "Channel - Info - About page", type: :system do - fab!(:channel_1) { Fabricate(:category_channel) } - - let(:chat_page) { PageObjects::Pages::Chat.new } - let(:chat_channel_about_page) { PageObjects::Pages::ChatChannelAbout.new } - - before { chat_system_bootstrap } - - context "as regular user" do - fab!(:current_user) { Fabricate(:user) } - - before { sign_in(current_user) } - - it "shows channel info" do - chat_page.visit_channel_about(channel_1) - - expect(page.find(".category-name")).to have_content(channel_1.chatable.name) - expect(page.find(".channel-info-about-view__name")).to have_content(channel_1.title) - expect(page.find(".channel-info-about-view__slug")).to have_content(channel_1.slug) - end - - it "escapes channel title" do - channel_1.update!(name: "") - chat_page.visit_channel_about(channel_1) - - expect(page.find(".channel-info-about-view__name")["innerHTML"].strip).to eq( - "<script>alert('hello')</script>", - ) - expect(page.find(".chat-channel-title__name")["innerHTML"].strip).to eq( - "<script>alert('hello')</script>", - ) - end - - it "can’t edit name or slug" do - chat_page.visit_channel_about(channel_1) - - expect(page).to have_no_selector(".edit-name-slug-btn") - end - - it "can’t edit description" do - chat_page.visit_channel_about(channel_1) - - expect(page).to have_no_selector(".edit-description-btn") - end - - context "as a member" do - before { channel_1.add(current_user) } - - it "can leave channel" do - chat_page.visit_channel_about(channel_1) - membership = channel_1.membership_for(current_user) - - expect { - click_button(I18n.t("js.chat.channel_settings.leave_channel")) - expect(page).to have_content(I18n.t("js.chat.channel_settings.join_channel")) - }.to change { membership.reload.following }.from(true).to(false) - end - end - - context "as not a member" do - it "can join channel" do - chat_page.visit_channel_about(channel_1) - - expect { - click_button(I18n.t("js.chat.channel_settings.join_channel")) - expect(page).to have_content(I18n.t("js.chat.channel_settings.leave_channel")) - }.to change { - Chat::UserChatChannelMembership.where(user_id: current_user.id, following: true).count - }.by(1) - end - end - end - - context "as admin" do - fab!(:current_user) { Fabricate(:admin) } - - before { sign_in(current_user) } - - it "can edit name" do - chat_page.visit_channel_about(channel_1) - - edit_modal = chat_channel_about_page.open_edit_modal - - expect(edit_modal).to have_name_input(channel_1.title) - - name = "A new name" - - edit_modal.fill_and_save_name(name) - - expect(chat_channel_about_page).to have_name(name) - end - - it "can edit description" do - chat_page.visit_channel_about(channel_1) - find(".edit-description-btn").click - - expect(page).to have_selector( - ".chat-modal-edit-channel-description__description-input", - text: channel_1.description, - ) - - description = "A new description" - find(".chat-modal-edit-channel-description__description-input").fill_in(with: description) - find(".create").click - - expect(page).to have_content(description) - end - - it "can edit slug" do - chat_page.visit_channel_about(channel_1) - edit_modal = chat_channel_about_page.open_edit_modal - - slug = "gonzo-slug" - - expect(edit_modal).to have_slug_input(channel_1.slug) - - edit_modal.fill_and_save_slug(slug) - - expect(chat_channel_about_page).to have_slug(slug) - end - - it "can clear the slug to use the autogenerated version based on the name" do - channel_1.update!(name: "test channel") - chat_page.visit_channel_about(channel_1) - edit_modal = chat_channel_about_page.open_edit_modal - - expect(edit_modal).to have_slug_input(channel_1.slug) - - edit_modal.fill_in_slug_input("") - edit_modal.wait_for_auto_generated_slug - edit_modal.save_changes - - expect(chat_channel_about_page).to have_slug("test-channel") - end - end -end diff --git a/plugins/chat/spec/system/channel_info_pages_spec.rb b/plugins/chat/spec/system/channel_info_pages_spec.rb deleted file mode 100644 index 0b53d1e0f05..00000000000 --- a/plugins/chat/spec/system/channel_info_pages_spec.rb +++ /dev/null @@ -1,38 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe "Info pages", type: :system do - let(:chat_page) { PageObjects::Pages::Chat.new } - let(:channel) { PageObjects::Pages::ChatChannel.new } - fab!(:current_user) { Fabricate(:user) } - fab!(:channel_1) { Fabricate(:chat_channel) } - - before do - chat_system_bootstrap - channel_1.add(current_user) - sign_in(current_user) - end - - context "when visiting from browse page" do - context "when clicking back button" do - it "redirects to browse page" do - chat_page.visit_browse - find(".chat-channel-card__setting").click - find(".chat-full-page-header__back-btn").click - - expect(page).to have_current_path("/chat/browse/open") - end - end - end - - context "when visiting from channel page" do - context "when clicking back button" do - it "redirects to channel page" do - chat_page.visit_channel(channel_1) - find(".chat-channel-title-wrapper").click - find(".chat-full-page-header__back-btn").click - - expect(page).to have_current_path(chat.channel_path(channel_1.slug, channel_1.id)) - end - end - end -end diff --git a/plugins/chat/spec/system/channel_members_page_spec.rb b/plugins/chat/spec/system/channel_members_page_spec.rb index becfd2812f7..8c7b3c9eae0 100644 --- a/plugins/chat/spec/system/channel_members_page_spec.rb +++ b/plugins/chat/spec/system/channel_members_page_spec.rb @@ -14,7 +14,7 @@ RSpec.describe "Channel - Info - Members page", type: :system do context "as unauthorized user" do before { SiteSetting.chat_allowed_groups = Fabricate(:group).id } - it "can’t see channel members" do + it "can't see channel members" do chat_page.visit_channel_members(channel_1) expect(page).to have_current_path("/latest") @@ -23,10 +23,10 @@ RSpec.describe "Channel - Info - Members page", type: :system do context "as authorized user" do context "with no members" do - it "redirects to about page" do + it "redirects to settings page" do chat_page.visit_channel_members(channel_1) - expect(page).to have_current_path("/chat/c/#{channel_1.slug}/#{channel_1.id}/info/about") + expect(page).to have_current_path("/chat/c/#{channel_1.slug}/#{channel_1.id}/info/settings") end end @@ -44,15 +44,15 @@ RSpec.describe "Channel - Info - Members page", type: :system do chat_page.visit_channel_members(channel_1) - expect(page).to have_selector(".channel-members-view__list-item", count: 50, wait: 15) + expect(page).to have_selector(".chat-channel-members__list-item", count: 60) - scroll_to(find(".channel-members-view__list-item:nth-child(50)")) + scroll_to(find(".chat-channel-members__list-item:nth-child(50)")) - expect(page).to have_selector(".channel-members-view__list-item", count: 100, wait: 15) + expect(page).to have_selector(".chat-channel-members__list-item", count: 100) - scroll_to(find(".channel-members-view__list-item:nth-child(100)")) + scroll_to(find(".chat-channel-members__list-item:nth-child(100)")) - expect(page).to have_selector(".channel-members-view__list-item", count: 100, wait: 15) + expect(page).to have_selector(".chat-channel-members__list-item", count: 100) end context "with filter" do @@ -62,9 +62,9 @@ RSpec.describe "Channel - Info - Members page", type: :system do Jobs::Chat::UpdateChannelUserCount.new.execute(chat_channel_id: channel_1.id) chat_page.visit_channel_members(channel_1) - find(".channel-members-view__search-input").fill_in(with: "cat") + find(".chat-channel-members__filter").fill_in(with: "cat") - expect(page).to have_selector(".channel-members-view__list-item", count: 1, text: "cat") + expect(page).to have_selector(".chat-channel-members__list-item", count: 1, text: "cat") end end end diff --git a/plugins/chat/spec/system/channel_settings_page_spec.rb b/plugins/chat/spec/system/channel_settings_page_spec.rb index 5196a3e8856..1e6631d69b9 100644 --- a/plugins/chat/spec/system/channel_settings_page_spec.rb +++ b/plugins/chat/spec/system/channel_settings_page_spec.rb @@ -1,15 +1,42 @@ # frozen_string_literal: true RSpec.describe "Channel - Info - Settings page", type: :system do - let(:chat_page) { PageObjects::Pages::Chat.new } fab!(:current_user) { Fabricate(:user) } fab!(:channel_1) { Fabricate(:category_channel) } + let(:chat_page) { PageObjects::Pages::Chat.new } + let(:toasts) { PageObjects::Components::Toasts.new } + let(:channel_settings_page) { PageObjects::Pages::ChatChannelSettings.new } + before do chat_system_bootstrap sign_in(current_user) end + context "when visiting from browse page" do + context "when clicking back button" do + it "redirects to browse page" do + chat_page.visit_browse + find(".chat-channel-card__setting").click + find(".chat-full-page-header__back-btn").click + + expect(page).to have_current_path("/chat/browse/open") + end + end + end + + context "when visiting from channel page" do + context "when clicking back button" do + it "redirects to channel page" do + chat_page.visit_channel(channel_1) + find(".chat-channel-title-wrapper").click + find(".chat-full-page-header__back-btn").click + + expect(page).to have_current_path(chat.channel_path(channel_1.slug, channel_1.id)) + end + end + end + context "as unauthorized user" do before { SiteSetting.chat_allowed_groups = Fabricate(:group).id } @@ -20,194 +47,244 @@ RSpec.describe "Channel - Info - Settings page", type: :system do end end - context "as authorized user" do - context "as not member" do - it "redirects to about tab" do - chat_page.visit_channel_settings(channel_1) + context "as not allowed to see the channel" do + fab!(:channel_1) { Fabricate(:private_category_channel) } - expect(page).to have_current_path("/chat/c/#{channel_1.slug}/#{channel_1.id}/info/about") - end + it "redirects to browse page" do + chat_page.visit_channel_settings(channel_1) - it "doesn’t have settings tab" do - chat_page.visit_channel_settings(channel_1) + expect(page).to have_current_path("/chat/browse/open") + end + end - expect(page).to have_no_selector(".chat-tabs-list__item[aria-controls='settings-tab']") - end + context "as not member of channel" do + it "shows settings page" do + chat_page.visit_channel_settings(channel_1) - context "as an admin" do - before { sign_in(Fabricate(:admin)) } + expect(page).to have_current_path("/chat/c/#{channel_1.slug}/#{channel_1.id}/info/settings") + end + end - it "shows settings tab" do - chat_page.visit_channel_settings(channel_1) + context "as regular user of channel" do + before { channel_1.add(current_user) } - expect(page).to have_selector(".chat-tabs-list__item[aria-controls='settings-tab']") - end + it "shows settings page" do + chat_page.visit_channel_settings(channel_1) - it "can navigate to settings tab" do - chat_page.visit_channel_settings(channel_1) - - expect(page).to have_current_path( - "/chat/c/#{channel_1.slug}/#{channel_1.id}/info/settings", - ) - end - end + expect(page).to have_current_path("/chat/c/#{channel_1.slug}/#{channel_1.id}/info/settings") end - context "as a member" do - before { channel_1.add(current_user) } + it "shows channel info" do + chat_page.visit_channel_settings(channel_1) - context "when visiting the settings of a recently joined channel" do - fab!(:channel_2) { Fabricate(:category_channel) } + expect(page.find(".category-name")).to have_content(channel_1.chatable.name) + expect(page.find(".chat-channel-settings__name")).to have_content(channel_1.title) + expect(page.find(".chat-channel-settings__slug")).to have_content(channel_1.slug) + end - it "is correctly populated" do - chat_page.visit_browse - find( - ".chat-channel-card[data-channel-id='#{channel_2.id}'] .toggle-channel-membership-button", - ).click + it "can’t edit name or slug" do + chat_page.visit_channel_settings(channel_1) - expect( - page.find(".chat-channel-card[data-channel-id='#{channel_2.id}']"), - ).to have_content(I18n.t("js.chat.joined").upcase) + expect(page).to have_no_selector(".edit-name-slug-btn") + end - find( - ".chat-channel-card[data-channel-id='#{channel_2.id}'] .chat-channel-card__setting", - ).click + it "can’t edit description" do + chat_page.visit_channel_settings(channel_1) - expect(page).to have_content(I18n.t("js.chat.notification_levels.mention")) - end - end + expect(page).to have_no_selector(".edit-description-btn") + end - it "can mute channel" do - chat_page.visit_channel_settings(channel_1) - membership = channel_1.membership_for(current_user) + it "escapes channel title" do + channel_1.update!(name: "") + chat_page.visit_channel_settings(channel_1) - expect { - select_kit = - PageObjects::Components::SelectKit.new(".-mute .channel-settings-view__selector") - select_kit.expand - select_kit.select_row_by_name("On") + expect(page.find(".chat-channel-settings__name")["innerHTML"].strip).to eq( + "<script>alert('hello')</script>", + ) + expect(page.find(".chat-channel-title__name")["innerHTML"].strip).to eq( + "<script>alert('hello')</script>", + ) + end - expect(page).to have_content(I18n.t("js.chat.settings.saved")) - }.to change { membership.reload.muted }.from(false).to(true) - end + it "is not showing admin section" do + chat_page.visit_channel_settings(channel_1) - it "can change desktop notification level" do - chat_page.visit_channel_settings(channel_1) - membership = channel_1.membership_for(current_user) + expect(page).to have_no_css("[data-section='admin']") + end - expect { - select_kit = - PageObjects::Components::SelectKit.new( - ".-desktop-notification-level .channel-settings-view__selector", - ) - select_kit.expand - select_kit.select_row_by_name("Never") + it "can mute channel" do + chat_page.visit_channel_settings(channel_1) + membership = channel_1.membership_for(current_user) - expect(page).to have_content(I18n.t("js.chat.settings.saved")) - }.to change { membership.reload.desktop_notification_level }.from("mention").to("never") - end + expect { + PageObjects::Components::DToggleSwitch.new(".chat-channel-settings__mute-switch").toggle - it "can change mobile notification level" do - chat_page.visit_channel_settings(channel_1) - membership = channel_1.membership_for(current_user) + expect(toasts).to have_success(I18n.t("js.saved")) + }.to change { membership.reload.muted }.from(false).to(true) + end - expect { - select_kit = - PageObjects::Components::SelectKit.new( - ".-mobile-notification-level .channel-settings-view__selector", - ) - select_kit.expand - select_kit.select_row_by_name("Never") + it "can change desktop notification level" do + chat_page.visit_channel_settings(channel_1) + membership = channel_1.membership_for(current_user) - expect(page).to have_content(I18n.t("js.chat.settings.saved")) - }.to change { membership.reload.mobile_notification_level }.from("mention").to("never") - end + expect { + select_kit = + PageObjects::Components::SelectKit.new( + ".chat-channel-settings__desktop-notifications-selector", + ) + select_kit.expand + select_kit.select_row_by_name("Never") - it "doesn’t show admin section" do - chat_page.visit_channel_settings(channel_1) + expect(toasts).to have_success(I18n.t("js.saved")) + }.to change { membership.reload.desktop_notification_level }.from("mention").to("never") + end - expect(page).to have_no_content(I18n.t("js.chat.settings.admin_title")) - end + it "can change mobile notification level" do + chat_page.visit_channel_settings(channel_1) + membership = channel_1.membership_for(current_user) - context "as an admin" do - before { sign_in(Fabricate(:admin)) } + expect { + select_kit = + PageObjects::Components::SelectKit.new( + ".chat-channel-settings__mobile-notifications-selector", + ) + select_kit.expand + select_kit.select_row_by_name("Never") - it "shows admin section" do - chat_page.visit_channel_settings(channel_1) + expect(toasts).to have_success(I18n.t("js.saved")) + }.to change { membership.reload.mobile_notification_level }.from("mention").to("never") + end + end - expect(page).to have_content(I18n.t("js.chat.settings.admin_title")) - end + context "as staff" do + fab!(:current_user) { Fabricate(:admin) } - it "can change auto join setting" do - chat_page.visit_channel_settings(channel_1) + it "can edit name" do + chat_page.visit_channel_settings(channel_1) - expect { - select_kit = - PageObjects::Components::SelectKit.new(".-autojoin .channel-settings-view__selector") - select_kit.expand - select_kit.select_row_by_name("Yes") - find("#dialog-holder .btn-primary").click + edit_modal = channel_settings_page.open_edit_modal - expect(page).to have_content(I18n.t("js.chat.settings.saved")) - }.to change { channel_1.reload.auto_join_users }.from(false).to(true) - end + expect(edit_modal).to have_name_input(channel_1.title) - it "can change allow channel wide mentions" do - chat_page.visit_channel_settings(channel_1) + name = "A new name" - expect { - select_kit = - PageObjects::Components::SelectKit.new( - ".-channel-wide-mentions .channel-settings-view__selector", - ) - select_kit.expand - select_kit.select_row_by_name("No") + edit_modal.fill_and_save_name(name) - expect(page).to have_content(I18n.t("js.chat.settings.saved")) - }.to change { channel_1.reload.allow_channel_wide_mentions }.from(true).to(false) - end + expect(channel_settings_page).to have_name(name) + end - it "can close channel" do - chat_page.visit_channel_settings(channel_1) + it "can edit description" do + chat_page.visit_channel_settings(channel_1) + find(".edit-description-btn").click - expect { - click_button(I18n.t("js.chat.channel_settings.close_channel")) - find("#chat-channel-toggle-btn").click - expect(page).to have_content(I18n.t("js.chat.channel_status.closed_header")) - }.to change { channel_1.reload.status }.from("open").to("closed") - end + expect(page).to have_selector( + ".chat-modal-edit-channel-description__description-input", + text: channel_1.description, + ) - it "can enable threading" do - chat_page.visit_channel_settings(channel_1) + description = "A new description" + find(".chat-modal-edit-channel-description__description-input").fill_in(with: description) + find(".create").click - expect { - select_kit = - PageObjects::Components::SelectKit.new(".-threading .channel-settings-view__selector") - select_kit.expand - select_kit.select_row_by_name("Enabled") - expect(page).to have_content(I18n.t("js.chat.settings.saved")) - }.to change { channel_1.reload.threading_enabled }.from(false).to(true) - end + expect(page).to have_content(description) + end - it "can delete channel" do - chat_page.visit_channel_settings(channel_1) + it "can edit slug" do + chat_page.visit_channel_settings(channel_1) + edit_modal = channel_settings_page.open_edit_modal - click_button(I18n.t("js.chat.channel_settings.delete_channel")) - fill_in("channel-delete-confirm-name", with: channel_1.title) - find_button("chat-confirm-delete-channel", disabled: false).click - expect(page).to have_content(I18n.t("js.chat.channel_delete.process_started")) - end + slug = "gonzo-slug" - context "when confirmation name is wrong" do - it "doesn’t delete submission" do - chat_page.visit_channel_settings(channel_1) - find(".delete-btn").click - fill_in("channel-delete-confirm-name", with: channel_1.title + "wrong") + expect(edit_modal).to have_slug_input(channel_1.slug) - expect(page).to have_button("chat-confirm-delete-channel", disabled: true) - end - end - end + edit_modal.fill_and_save_slug(slug) + + expect(channel_settings_page).to have_slug(slug) + end + + it "can clear the slug to use the autogenerated version based on the name" do + channel_1.update!(name: "test channel") + chat_page.visit_channel_settings(channel_1) + edit_modal = channel_settings_page.open_edit_modal + + expect(edit_modal).to have_slug_input(channel_1.slug) + + edit_modal.fill_in_slug_input("") + edit_modal.wait_for_auto_generated_slug + edit_modal.save_changes + + expect(channel_settings_page).to have_slug("test-channel") + end + + it "shows settings page" do + chat_page.visit_channel_settings(channel_1) + + expect(page).to have_current_path("/chat/c/#{channel_1.slug}/#{channel_1.id}/info/settings") + end + + it "can change auto join setting" do + chat_page.visit_channel_settings(channel_1) + + expect { + PageObjects::Components::DToggleSwitch.new( + ".chat-channel-settings__auto-join-switch", + ).toggle + find("#dialog-holder .btn-primary").click + + expect(toasts).to have_success(I18n.t("js.saved")) + }.to change { channel_1.reload.auto_join_users }.from(false).to(true) + end + + it "can change allow channel wide mentions" do + chat_page.visit_channel_settings(channel_1) + + expect { + PageObjects::Components::DToggleSwitch.new( + ".chat-channel-settings__channel-wide-mentions", + ).toggle + + expect(toasts).to have_success(I18n.t("js.saved")) + }.to change { channel_1.reload.allow_channel_wide_mentions }.from(true).to(false) + end + + it "can close channel" do + chat_page.visit_channel_settings(channel_1) + + expect { + click_button(I18n.t("js.chat.channel_settings.close_channel")) + find("#chat-channel-toggle-btn").click + + expect(page).to have_content(I18n.t("js.chat.channel_status.closed_header")) + }.to change { channel_1.reload.status }.from("open").to("closed") + end + + it "can enable threading" do + chat_page.visit_channel_settings(channel_1) + + expect { + PageObjects::Components::DToggleSwitch.new( + ".chat-channel-settings__threading-switch", + ).toggle + + expect(toasts).to have_success(I18n.t("js.saved")) + }.to change { channel_1.reload.threading_enabled }.from(false).to(true) + end + + it "can delete channel" do + chat_page.visit_channel_settings(channel_1) + + click_button(I18n.t("js.chat.channel_settings.delete_channel")) + fill_in("channel-delete-confirm-name", with: channel_1.title) + find_button("chat-confirm-delete-channel", disabled: false).click + expect(page).to have_content(I18n.t("js.chat.channel_delete.process_started")) + end + + it "doesn’t delete when confirmation is wrong" do + chat_page.visit_channel_settings(channel_1) + find(".delete-btn").click + fill_in("channel-delete-confirm-name", with: channel_1.title + "wrong") + + expect(page).to have_button("chat-confirm-delete-channel", disabled: true) end end end diff --git a/plugins/chat/spec/system/drawer_spec.rb b/plugins/chat/spec/system/drawer_spec.rb index 4def6cbd4e3..6e6c072094a 100644 --- a/plugins/chat/spec/system/drawer_spec.rb +++ b/plugins/chat/spec/system/drawer_spec.rb @@ -24,7 +24,7 @@ RSpec.describe "Drawer", type: :system do drawer_page.open_channel(channel) page.find(".chat-channel-title").click - expect(page).to have_current_path("/chat/c/#{channel.slug}/#{channel.id}/info/about") + expect(page).to have_current_path("/chat/c/#{channel.slug}/#{channel.id}/info/settings") end end end diff --git a/plugins/chat/spec/system/page_objects/chat/chat.rb b/plugins/chat/spec/system/page_objects/chat/chat.rb index d2fa99777c8..2b9a3117fc5 100644 --- a/plugins/chat/spec/system/page_objects/chat/chat.rb +++ b/plugins/chat/spec/system/page_objects/chat/chat.rb @@ -63,10 +63,6 @@ module PageObjects visit(channel.url + "/info/settings") end - def visit_channel_about(channel) - visit(channel.url + "/info/about") - end - def visit_channel_members(channel) visit(channel.url + "/info/members") end diff --git a/plugins/chat/spec/system/page_objects/chat/chat_channel_about.rb b/plugins/chat/spec/system/page_objects/chat/chat_channel_settings.rb similarity index 68% rename from plugins/chat/spec/system/page_objects/chat/chat_channel_about.rb rename to plugins/chat/spec/system/page_objects/chat/chat_channel_settings.rb index a28836bb59f..34d9ee1b8b1 100644 --- a/plugins/chat/spec/system/page_objects/chat/chat_channel_about.rb +++ b/plugins/chat/spec/system/page_objects/chat/chat_channel_settings.rb @@ -2,7 +2,7 @@ module PageObjects module Pages - class ChatChannelAbout < PageObjects::Pages::Base + class ChatChannelSettings < PageObjects::Pages::Base EDIT_MODAL_SELECTOR = ".chat-modal-edit-channel-name" def open_edit_modal @@ -12,11 +12,11 @@ module PageObjects end def has_slug?(slug) - page.has_css?(".channel-info-about-view__slug", text: slug) + page.has_css?(".chat-channel-settings__slug", text: slug) end def has_name?(name) - page.has_css?(".channel-info-about-view__name", text: name) + page.has_css?(".chat-channel-settings__name", text: name) end end end diff --git a/plugins/chat/spec/system/page_objects/modals/chat_edit_modal.rb b/plugins/chat/spec/system/page_objects/modals/chat_edit_modal.rb index d678db64485..0930ccb0970 100644 --- a/plugins/chat/spec/system/page_objects/modals/chat_edit_modal.rb +++ b/plugins/chat/spec/system/page_objects/modals/chat_edit_modal.rb @@ -5,7 +5,7 @@ module PageObjects class ChatChannelEdit < PageObjects::Modals::Base include SystemHelpers - EDIT_MODAL_SELECTOR = PageObjects::Pages::ChatChannelAbout::EDIT_MODAL_SELECTOR + EDIT_MODAL_SELECTOR = PageObjects::Pages::ChatChannelSettings::EDIT_MODAL_SELECTOR SLUG_INPUT_SELECTOR = ".chat-channel-edit-name-slug-modal__slug-input" NAME_INPUT_SELECTOR = ".chat-channel-edit-name-slug-modal__name-input" diff --git a/plugins/chat/test/javascripts/components/chat-channel-settings-saved-indicator-test.js b/plugins/chat/test/javascripts/components/chat-channel-settings-saved-indicator-test.js deleted file mode 100644 index 6f03050cb05..00000000000 --- a/plugins/chat/test/javascripts/components/chat-channel-settings-saved-indicator-test.js +++ /dev/null @@ -1,31 +0,0 @@ -import { module, test } from "qunit"; -import { setupRenderingTest } from "discourse/tests/helpers/component-test"; -import { render, settled } from "@ember/test-helpers"; -import { hbs } from "ember-cli-htmlbars"; - -module( - "Discourse Chat | Component | chat-channel-settings-saved-indicator", - function (hooks) { - setupRenderingTest(hooks); - - test("when property changes", async function (assert) { - await render( - hbs`` - ); - - assert - .dom(".chat-channel-settings-saved-indicator.is-active") - .doesNotExist(); - - this.set("property", 1); - - assert.dom(".chat-channel-settings-saved-indicator.is-active").exists(); - - await settled(); - - assert - .dom(".chat-channel-settings-saved-indicator.is-active") - .doesNotExist(); - }); - } -); diff --git a/plugins/chat/test/javascripts/components/chat-channel-settings-view-test.js b/plugins/chat/test/javascripts/components/chat-channel-settings-view-test.js deleted file mode 100644 index 3bc294b528f..00000000000 --- a/plugins/chat/test/javascripts/components/chat-channel-settings-view-test.js +++ /dev/null @@ -1,25 +0,0 @@ -import ChatChannel from "discourse/plugins/chat/discourse/models/chat-channel"; -import { setupRenderingTest } from "discourse/tests/helpers/component-test"; -import hbs from "htmlbars-inline-precompile"; -import I18n from "I18n"; -import { module, test } from "qunit"; -import { render } from "@ember/test-helpers"; - -module( - "Discourse Chat | Component | chat-channel-settings-view", - function (hooks) { - setupRenderingTest(hooks); - - test("display retention info", async function (assert) { - this.set("channel", ChatChannel.create({ chatable_type: "Category" })); - - await render(hbs``); - - assert.dom(".chat-retention-info").hasText( - I18n.t("chat.retention_reminders.public", { - count: this.siteSettings.chat_channel_retention_days, - }) - ); - }); - } -); diff --git a/spec/system/page_objects/components/d_toggle_switch.rb b/spec/system/page_objects/components/d_toggle_switch.rb new file mode 100644 index 00000000000..5569a83a678 --- /dev/null +++ b/spec/system/page_objects/components/d_toggle_switch.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module PageObjects + module Components + class DToggleSwitch < PageObjects::Components::Base + attr_reader :context + + def initialize(context) + @context = context + end + + def component + find(@context, visible: :all).native + end + + def toggle + actionbuilder = page.driver.browser.action # workaround zero height button + actionbuilder.click(component).perform + end + end + end +end