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}} - <div class="chat-form__section"> - <div class="chat-form__field"> - <label class="chat-form__label"> - {{i18n "chat.about_view.associated_category"}} - </label> - <div class="chat-form__control"> - {{category-badge - this.channel.chatable - link=true - allowUncategorized=true - }} - </div> - </div> - </div> -{{/if}} - -<div class="chat-form__section"> - <div class="chat-form__field"> - <label class="chat-form__label"> - <span>{{i18n "chat.about_view.name"}}</span> - {{#if (chat-guardian "can-edit-chat-channel")}} - <div class="chat-form__label-actions"> - <DButton - @label="chat.channel_settings.edit" - @action={{if this.onEditChatChannelName this.onEditChatChannelName}} - class="edit-name-slug-btn btn-flat" - /> - </div> - {{/if}} - </label> - <div class="chat-form__control"> - <div class="channel-info-about-view__name"> - {{replace-emoji this.channel.title}} - </div> - <div class="channel-info-about-view__slug"> - {{this.channel.slug}} - </div> - </div> - </div> -</div> - -{{#if - (or (chat-guardian "can-edit-chat-channel") this.channel.description.length) -}} - <div class="chat-form__section"> - <div class="chat-form__field"> - <label class="chat-form__label"> - <span>{{i18n "chat.about_view.description"}}</span> - {{#if (chat-guardian "can-edit-chat-channel")}} - <div class="chat-form__label-actions"> - <DButton - @label={{if - this.channel.description.length - "chat.channel_settings.edit" - "chat.channel_settings.add" - }} - @action={{if - this.onEditChatChannelDescription - this.onEditChatChannelDescription - }} - class="edit-description-btn btn-flat" - /> - </div> - {{/if}} - </label> - - <div class="chat-form__control"> - <div class="channel-info-about-view__description"> - {{#if this.channel.description.length}} - {{this.channel.description}} - {{else}} - <div class="channel-info-about-view__description__helper-text"> - {{i18n "chat.channel_edit_description_modal.description"}} - </div> - {{/if}} - </div> - </div> - </div> - </div> -{{/if}} - -<div class="chat-form__section"> - <ToggleChannelMembershipButton - @channel={{this.channel}} - @options={{hash - joinClass="btn-primary" - leaveClass="btn-flat" - joinIcon="sign-in-alt" - leaveIcon="sign-out-alt" - }} - /> -</div> \ 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 { + <template> + <div class="chat-full-page-header"> + <div class="chat-channel-header-details"> + <div class="chat-full-page-header__left-actions"> + {{#if this.chatChannelInfoRouteOriginManager.isBrowse}} + <LinkTo + @route="chat.browse" + class="chat-full-page-header__back-btn no-text btn-flat btn" + title={{this.backToAllChannelsLabel}} + > + {{icon "chevron-left"}} + </LinkTo> + {{else}} + <LinkTo + @route="chat.channel" + @models={{@channel.routeModels}} + class="chat-full-page-header__back-btn no-text btn-flat btn" + title={{this.backToChannelLabel}} + > + {{icon "chevron-left"}} + </LinkTo> + {{/if}} + </div> + + <ChatChannelTitle @channel={{@channel}} /> + </div> + </div> + + <ChatChannelStatus @channel={{@channel}} /> + + <div class="chat-channel-info"> + {{#if this.showTabs}} + <nav class="chat-channel-info__nav"> + <ul class="nav nav-pills"> + <li> + <LinkTo + @route="chat.channel.info.settings" + @model={{@channel}} + @replace={{true}} + > + {{this.settingsLabel}} + </LinkTo> + </li> + <li> + <LinkTo + @route="chat.channel.info.members" + @model={{@channel}} + @replace={{true}} + > + {{this.membersLabel}} + </LinkTo> + </li> + </ul> + </nav> + {{/if}} + + {{outlet}} + </div> + </template> + + @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)}} - <LoadMore @selector=".channel-members-view__list-item" @action={{this.load}}> - <div class="channel-members-view-wrapper"> - <div - class={{concat - "channel-members-view__search-input-container" - (if this.isSearchFocused " is-focused") - }} - > - <Input - class={{this.inputSelector}} - placeholder={{i18n "chat.members_view.filter_placeholder"}} - {{on "input" (action "onFilterMembers" value="target.value")}} - {{on "focusin" (action (mut this.isSearchFocused) true)}} - {{on "focusout" (action (mut this.isSearchFocused) false)}} - /> - {{d-icon "search"}} - </div> - - <div class="channel-members-view__list-container"> - <div role="list" class="channel-members-view__list"> - {{#each this.members as |membership|}} - <div class="channel-members-view__list-item"> - <ChatUserInfo @user={{membership.user}} /> - </div> - {{else}} - {{#if this.members.fetchedOnce}} - <div class="chat-thread-list__no-threads"> - {{i18n "chat.channel.no_memberships_found"}} - </div> - {{/if}} - {{/each}} - </div> - </div> - </div> - <ConditionalLoadingSpinner @condition={{this.members.loading}} /> - </LoadMore> -{{else}} - <div class="channel-members-view-wrapper"> - {{i18n "chat.channel.no_memberships"}} - </div> -{{/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 { + <template> + {{! template-lint-disable modifier-name-case }} + <div class="chat-channel-members"> + <DcFilterInput + @class="chat-channel-members__filter" + @filterAction={{this.mutFilter}} + @icons={{hash right="search"}} + placeholder={{this.filterPlaceholder}} + {{this.focusInput}} + /> + + {{#if (gt @channel.membershipsCount 0)}} + <ul class="chat-channel-members__list" {{this.fill}}> + {{#each this.members as |membership|}} + <li class="chat-channel-members__list-item"> + <ChatUserInfo @user={{membership.user}} @avatarSize="tiny" /> + </li> + {{else}} + {{#if this.noResults}} + <li + class="chat-channel-members__list-item -no-results alert alert-info" + > + {{this.noMembershipsFoundLabel}} + </li> + {{/if}} + {{/each}} + </ul> + + <div {{this.loadMore}}> + <br /> + </div> + {{else}} + <p class="alert alert-info"> + {{this.noMembershipsLabel}} + </p> + {{/if}} + </div> + </template> + + @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 @@ -<span - {{did-update this.activate @property}} - {{will-destroy this.teardown}} - class={{concat-class - "chat-channel-settings-saved-indicator" - (if this.isActive "is-active") - }} - role="status" -> - {{#if this.isActive}} - {{d-icon "check"}} - <span>{{i18n "saved"}}</span> - {{/if}} -</span> \ 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 @@ -<div class="chat-form__section"> - <div class="chat-form__field -mute"> - <label class="chat-form__label"> - <span>{{i18n "chat.settings.mute"}}</span> - <ChatChannelSettingsSavedIndicator - @property={{@channel.currentUserMembership.muted}} - /> - </label> - <div class="chat-form__control"> - <ComboBox - @content={{this.mutedOptions}} - @value={{@channel.currentUserMembership.muted}} - @valueProperty="value" - @class="channel-settings-view__selector" - @onChange={{fn this.saveNotificationSettings "muted" "muted"}} - /> - </div> - </div> - - {{#unless @channel.currentUserMembership.muted}} - <div class="chat-form__field -desktop-notification-level"> - <label class="chat-form__label"> - <span>{{i18n "chat.settings.desktop_notification_level"}}</span> - <ChatChannelSettingsSavedIndicator - @property={{@channel.currentUserMembership.desktopNotificationLevel}} - /> - </label> - <div class="chat-form__control"> - <ComboBox - @content={{this.notificationLevels}} - @value={{@channel.currentUserMembership.desktopNotificationLevel}} - @valueProperty="value" - @class="channel-settings-view__selector" - @onChange={{fn - this.saveNotificationSettings - "desktopNotificationLevel" - "desktop_notification_level" - }} - /> - </div> - </div> - - <div class="chat-form__field -mobile-notification-level"> - <label class="chat-form__label"> - <span>{{i18n "chat.settings.mobile_notification_level"}}</span> - <ChatChannelSettingsSavedIndicator - @property={{@channel.currentUserMembership.mobileNotificationLevel}} - /> - </label> - <div class="chat-form__control"> - <ComboBox - @content={{this.notificationLevels}} - @value={{@channel.currentUserMembership.mobileNotificationLevel}} - @valueProperty="value" - @class="channel-settings-view__selector" - @onChange={{fn - this.saveNotificationSettings - "mobileNotificationLevel" - "mobile_notification_level" - }} - /> - </div> - </div> - {{/unless}} - <div class="chat-retention-info"> - {{d-icon "info-circle"}} - <ChatRetentionReminderText @channel={{@channel}} /> - </div> -</div> - -{{#if this.adminSectionAvailable}} - <h3 class="chat-form__section-admin-title"> - {{i18n "chat.settings.admin_title"}} - </h3> - - {{#if this.autoJoinAvailable}} - <div class="chat-form__section -autojoin"> - <div class="chat-form__field"> - <label class="chat-form__label"> - <span>{{i18n "chat.settings.auto_join_users_label"}}</span> - <ChatChannelSettingsSavedIndicator - @property={{@channel.autoJoinUsers}} - /> - </label> - <ComboBox - @content={{this.autoAddUsersOptions}} - @value={{@channel.autoJoinUsers}} - @valueProperty="value" - @class="channel-settings-view__selector" - @onChange={{action - (fn this.onToggleAutoJoinUsers @channel.autoJoinUsers) - }} - /> - <p class="chat-form__description"> - {{i18n - "chat.settings.auto_join_users_info" - category=@channel.chatable.name - }} - </p> - </div> - </div> - {{/if}} - - {{#if this.togglingChannelWideMentionsAvailable}} - <div class="chat-form__section -channel-wide-mentions"> - <div class="chat-form__field"> - <label class="chat-form__label"> - <span>{{i18n "chat.settings.channel_wide_mentions_label"}}</span> - <ChatChannelSettingsSavedIndicator - @property={{@channel.allowChannelWideMentions}} - /> - </label> - <ComboBox - @content={{this.channelWideMentionsOptions}} - @value={{@channel.allowChannelWideMentions}} - @valueProperty="value" - @class="channel-settings-view__selector" - @onChange={{this.onToggleChannelWideMentions}} - /> - <p class="chat-form__description"> - {{i18n - "chat.settings.channel_wide_mentions_description" - channel=@channel.title - }} - </p> - </div> - </div> - {{/if}} - - <div class="chat-form__section -threading"> - <div class="chat-form__field"> - <label class="chat-form__label"> - <span>{{i18n "chat.settings.channel_threading_label"}}</span> - <span class="channel-settings-view__channel-threading-tooltip"> - {{d-icon "info-circle"}} - <DTooltip> - {{i18n "chat.settings.channel_threading_description"}} - </DTooltip> - </span> - <ChatChannelSettingsSavedIndicator - @property={{@channel.threadingEnabled}} - /> - </label> - <ComboBox - @content={{this.threadingEnabledOptions}} - @value={{@channel.threadingEnabled}} - @valueProperty="value" - @class="channel-settings-view__selector" - @onChange={{this.onToggleThreadingEnabled}} - /> - </div> - </div> -{{/if}} - -{{#unless @channel.isDirectMessageChannel}} - <div class="chat-form__section"> - {{#if (chat-guardian "can-edit-chat-channel")}} - {{#if (chat-guardian "can-archive-channel" @channel)}} - <div class="chat-form__field"> - <DButton - @action={{this.onArchiveChannel}} - @label="chat.channel_settings.archive_channel" - @icon="archive" - class="archive-btn chat-form__btn btn-flat" - /> - </div> - {{/if}} - - {{#if @channel.isClosed}} - <div class="chat-form__field"> - <DButton - @action={{this.onToggleChannelState}} - @label="chat.channel_settings.open_channel" - @icon="unlock" - class="open-btn chat-form__btn btn-flat" - /> - </div> - {{else}} - <div class="chat-form__field"> - <DButton - @action={{this.onToggleChannelState}} - @label="chat.channel_settings.close_channel" - @icon="lock" - class="close-btn chat-form__btn btn-flat" - /> - </div> - {{/if}} - - <div class="chat-form__field"> - <DButton - @action={{this.onDeleteChannel}} - @label="chat.channel_settings.delete_channel" - @icon="trash-alt" - class="delete-btn chat-form__btn btn-flat" - /> - </div> - {{/if}} - </div> -{{/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 { + <template> + <div class="chat-channel-settings"> + <ChatForm as |form|> + {{#if this.shouldRenderTitleSection}} + <form.section @title={{this.titleSectionTitle}} as |section|> + <section.row> + <:default> + <div class="chat-channel-settings__name"> + {{replaceEmoji @channel.title}} + </div> + + {{#if @channel.isCategoryChannel}} + <div class="chat-channel-settings__slug"> + <LinkTo + @route="chat.channel" + @models={{@channel.routeModels}} + > + /chat/c/{{@channel.slug}}/{{@channel.id}} + </LinkTo> + </div> + {{/if}} + </:default> + + <:action> + {{#if this.canEditChannel}} + <DButton + @label="chat.channel_settings.edit" + @action={{this.onEditChannelName}} + class="edit-name-slug-btn btn-flat" + /> + {{/if}} + </:action> + + </section.row> + </form.section> + {{/if}} + + {{#if this.shouldRenderDescriptionSection}} + <form.section @title={{this.descriptionSectionTitle}} as |section|> + <section.row> + <:default> + {{#if @channel.description.length}} + {{@channel.description}} + {{else}} + {{this.descriptionPlaceholder}} + {{/if}} + </:default> + + <:action> + {{#if this.canEditChannel}} + <DButton + @label={{if + @channel.description.length + "chat.channel_settings.edit" + "chat.channel_settings.add" + }} + @action={{this.onEditChannelDescription}} + class="edit-description-btn btn-flat" + /> + {{/if}} + </:action> + </section.row> + </form.section> + {{/if}} + + {{#if this.site.mobileView}} + <form.section as |section|> + <section.row + @label={{this.membersLabel}} + @route="chat.channel.info.members" + @routeModels={{@channel.routeModels}} + /> + </form.section> + {{/if}} + + {{#if @channel.isOpen}} + <form.section @title={{this.settingsSectionTitle}} as |section|> + <section.row @label={{this.muteSectionLabel}}> + <:action> + <DToggleSwitch + @state={{@channel.currentUserMembership.muted}} + class="chat-channel-settings__mute-switch" + {{on "click" this.onToggleMuted}} + /> + </:action> + </section.row> + + {{#if this.shouldRenderDesktopNotificationsLevelSection}} + <section.row @label={{this.desktopNotificationsLevelLabel}}> + <:action> + <ComboBox + @content={{this.notificationLevels}} + @value={{@channel.currentUserMembership.desktopNotificationLevel}} + @valueProperty="value" + @class="chat-channel-settings__selector chat-channel-settings__desktop-notifications-selector" + @onChange={{fn + this.saveNotificationSettings + "desktopNotificationLevel" + "desktop_notification_level" + }} + /> + </:action> + </section.row> + {{/if}} + + {{#if this.shouldRenderMobileNotificationsLevelSection}} + <section.row @label={{this.mobileNotificationsLevelLabel}}> + <:action> + <ComboBox + @content={{this.notificationLevels}} + @value={{@channel.currentUserMembership.mobileNotificationLevel}} + @valueProperty="value" + @class="chat-channel-settings__selector chat-channel-settings__mobile-notifications-selector" + @onChange={{fn + this.saveNotificationSettings + "mobileNotificationLevel" + "mobile_notification_level" + }} + /> + </:action> + </section.row> + {{/if}} + </form.section> + {{/if}} + + <form.section @title={{this.channelInfoSectionTitle}} as |section|> + {{#if @channel.isCategoryChannel}} + <section.row @label={{this.categoryLabel}}> + {{categoryBadge + @channel.chatable + link=true + allowUncategorized=true + }} + </section.row> + {{/if}} + + <section.row @label={{this.historyLabel}}> + <ChatRetentionReminderText @channel={{@channel}} /> + </section.row> + </form.section> + + {{#if this.shouldRenderAdminSection}} + <form.section + @title={{this.adminSectionTitle}} + data-section="admin" + as |section| + > + {{#if this.autoJoinAvailable}} + <section.row @label={{this.autoJoinLabel}}> + <:action> + <DToggleSwitch + @state={{@channel.autoJoinUsers}} + class="chat-channel-settings__auto-join-switch" + {{on + "click" + (fn this.onToggleAutoJoinUsers @channel.autoJoinUsers) + }} + /> + </:action> + </section.row> + {{/if}} + + {{#if this.toggleChannelWideMentionsAvailable}} + <section.row @label={{this.channelWideMentionsLabel}}> + <:action> + <DToggleSwitch + class="chat-channel-settings__channel-wide-mentions" + @state={{@channel.allowChannelWideMentions}} + {{on + "click" + (fn + this.onToggleChannelWideMentions + @channel.allowChannelWideMentions + ) + }} + /> + </:action> + + <:description> + {{this.channelWideMentionsDescription}} + </:description> + </section.row> + {{/if}} + + {{#if this.toggleThreadingAvailable}} + <section.row @label={{this.toggleThreadingLabel}}> + <:action> + <DToggleSwitch + @state={{@channel.threadingEnabled}} + class="chat-channel-settings__threading-switch" + {{on + "click" + (fn + this.onToggleThreadingEnabled @channel.threadingEnabled + ) + }} + /> + </:action> + + <:description> + {{this.toggleThreadingDescription}} + </:description> + </section.row> + {{/if}} + + {{#if this.shouldRenderStatusSection}} + {{#if this.shouldRenderArchiveRow}} + <section.row> + <:action> + <DButton + @action={{this.onArchiveChannel}} + @label="chat.channel_settings.archive_channel" + @icon="archive" + class="archive-btn chat-form__btn btn-flat" + /> + </:action> + </section.row> + {{/if}} + + <section.row> + <:action> + {{#if @channel.isOpen}} + <DButton + @action={{this.onToggleChannelState}} + @label="chat.channel_settings.close_channel" + @icon="lock" + class="close-btn chat-form__btn btn-flat" + /> + {{else}} + <DButton + @action={{this.onToggleChannelState}} + @label="chat.channel_settings.open_channel" + @icon="unlock" + class="open-btn chat-form__btn btn-flat" + /> + {{/if}} + </:action> + </section.row> + + <section.row> + <:action> + <DButton + @action={{this.onDeleteChannel}} + @label="chat.channel_settings.delete_channel" + @icon="trash-alt" + class="delete-btn chat-form__btn btn-flat" + /> + </:action> + </section.row> + {{/if}} + + </form.section> + {{/if}} + + <form.section as |section|> + <section.row> + <:action> + <ToggleChannelMembershipButton + @channel={{@channel}} + @options={{hash + joinClass="btn-primary" + leaveClass="btn-flat" + joinIcon="sign-in-alt" + leaveIcon="sign-out-alt" + }} + /> + </:action> + </section.row> + </form.section> + </ChatForm> + </div> + </template> + + @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 { + <template> + <span class="chat-retention-reminder-text"> + {{this.text}} + </span> + </template> + @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 @@ -<span class="chat-retention-reminder-text"> - {{this.text}} -</span> \ 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 { + <template> + {{#if @user}} + <a href={{this.userPath}} data-user-card={{@user.username}}> + <ChatUserAvatar @user={{@user}} @avatarSize={{this.avatarSize}} /> + </a> + <a href={{this.userPath}} data-user-card={{@user.username}}> + <ChatUserDisplayName @user={{@user}} /> + </a> + {{/if}} + </template> + + 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}} - <a href={{this.userPath}} data-user-card={{@user.username}}> - <Chat::UserAvatar @user={{@user}} @avatarSize="medium" /> - </a> - <a href={{this.userPath}} data-user-card={{@user.username}}> - <ChatUserDisplayName @user={{@user}} /> - </a> -{{/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 { + <template> + <div class="chat-form"> + {{yield this.yieldableArgs}} + </div> + </template> + + 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 { + <template> + {{#if @route}} + <LinkTo + @route={{@route}} + @models={{@routeModels}} + class={{concatClass + "chat-form__row -link" + (if @separator "-separator") + }} + > + <div class="chat-form__row-content"> + {{@label}} + {{icon "chevron-right" class="chat-form__row-icon"}} + </div> + </LinkTo> + {{else}} + <div class={{concatClass "chat-form__row" (if @separator "-separator")}}> + <div class="chat-form__row-content"> + {{#if @label}} + <span class="chat-form__row-label">{{@label}}</span> + {{/if}} + + {{#if (has-block)}} + <span class="chat-form__row-label"> + {{yield}} + </span> + {{/if}} + + {{#if (has-block "action")}} + <div class="chat-form__row-action">{{yield to="action"}}</div> + {{/if}} + </div> + + {{#if (has-block "description")}} + <div class="chat-form__row-description"> + {{yield to="description"}} + </div> + {{/if}} + </div> + {{/if}} + </template> +} 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 { + <template> + <div class="chat-form__section" ...attributes> + {{#if @title}} + <div class="chat-form__section-title"> + {{@title}} + </div> + {{/if}} + + <div class="chat-form__section-content"> + {{yield this.yieldableArgs}} + </div> + </div> + </template> + + 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 @@ <div class="edit-channel-control"> <label for="channel-slug" class="edit-channel-label"> {{i18n "chat.channel_edit_name_slug_modal.slug"}} - <span> - {{d-icon "info-circle"}} - <DTooltip>{{i18n - "chat.channel_edit_name_slug_modal.slug_description" - }}</DTooltip> - </span> + <DTooltip + @icon="info-circle" + @content={{i18n "chat.channel_edit_name_slug_modal.slug_description"}} + /> </label> <Input name="channel-slug" diff --git a/plugins/chat/assets/javascripts/discourse/components/dc-filter-input.gjs b/plugins/chat/assets/javascripts/discourse/components/dc-filter-input.gjs new file mode 100644 index 00000000000..5d924d93822 --- /dev/null +++ b/plugins/chat/assets/javascripts/discourse/components/dc-filter-input.gjs @@ -0,0 +1,58 @@ +import Component from "@glimmer/component"; +import { Input } from "@ember/component"; +import { on } from "@ember/modifier"; +import noop from "discourse/helpers/noop"; +import concatClass from "discourse/helpers/concat-class"; +import icon from "discourse-common/helpers/d-icon"; +import { modifier } from "ember-modifier"; +import { tracked } from "@glimmer/tracking"; + +export default class DcFilterInput extends Component { + <template> + {{! template-lint-disable modifier-name-case }} + <div + class={{concatClass + @class + "dc-filter-input-container" + (if this.isFocused " is-focused") + }} + > + {{#if @icons.left}} + {{icon @icons.left class="-left"}} + {{/if}} + + <Input + class="dc-filter-input" + @value={{@value}} + {{on "input" (if @filterAction @filterAction (noop))}} + {{this.focusState}} + ...attributes + /> + + {{yield}} + + {{#if @icons.right}} + {{icon @icons.right class="-right"}} + {{/if}} + </div> + </template> + + @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 @@ -<div - class={{concat - @class - " dc-filter-input-container" - (if this.isFocused " is-focused") - }} -> - {{#if @icons.left}} - {{d-icon @icons.left class="-left"}} - {{/if}} - - <Input - class="dc-filter-input" - @value={{@value}} - {{on "input" (if @filterAction @filterAction (noop))}} - {{on "focusin" (action (mut this.isFocused) true)}} - {{on "focusout" (action (mut this.isFocused) false)}} - ...attributes - /> - - {{yield}} - - {{#if @icons.right}} - {{d-icon @icons.right class="-right"}} - {{/if}} -</div> \ 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 @@ -<ChatChannelAboutView - @channel={{this.model}} - @onEditChatChannelName={{action "onEditChatChannelName"}} - @onEditChatChannelDescription={{action "onEditChatChannelDescription"}} -/> \ 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 @@ -<ChatChannelMembersView @channel={{this.model}} /> \ No newline at end of file +<ChatChannelMembers @channel={{this.model}} /> \ 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 @@ -<ChatChannelSettingsView @channel={{this.model}} /> \ No newline at end of file +<ChatChannelSettings @channel={{this.model}} /> \ 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 @@ -<div class="channel-info"> - <div class="chat-full-page-header"> - <div class="chat-channel-header-details"> - <div class="chat-full-page-header__left-actions"> - {{#if this.chatChannelInfoRouteOriginManager.isBrowse}} - <LinkTo - @route="chat.browse" - class="chat-full-page-header__back-btn no-text btn-flat btn" - title={{i18n "chat.channel_info.back_to_all_channel"}} - > - {{d-icon "chevron-left"}} - </LinkTo> - {{else}} - <LinkTo - @route="chat.channel" - @models={{this.model.routeModels}} - class="chat-full-page-header__back-btn no-text btn-flat btn" - title={{i18n "chat.channel_info.back_to_channel"}} - > - {{d-icon "chevron-left"}} - </LinkTo> - {{/if}} - </div> - - <ChatChannelTitle @channel={{this.model}} /> - </div> - </div> - - <ChatChannelStatus @channel={{this.model}} /> - - <div class="chat-tabs chat-info-tabs"> - <ul class="chat-tabs-list nav-pills" role="tablist"> - {{#each this.tabs as |tab|}} - <li - class="chat-tabs-list__item" - role="tab" - aria-controls={{concat tab "-tab"}} - > - <LinkTo - @route={{concat "chat.channel.info." tab}} - @models={{this.model.routeModels}} - class="chat-tabs-list__link" - > - <span>{{i18n (concat "chat.channel_info.tabs." tab)}}</span> - {{#if (eq tab "members")}} - <span class="chat-tabs__memberships-count"> - ({{this.model.membershipsCount}}) - </span> - {{/if}} - </LinkTo> - </li> - {{/each}} - </ul> - - <div - id={{this.tab}} - class="chat-tabs__tabpanel" - aria-hidden={{notEq this.tab this.activeTab}} - role="tabpanel" - aria-labelledby={{concat this.tab "-tab"}} - > - {{outlet}} - </div> - </div> -</div> \ No newline at end of file +<ChatChannelInfo @channel={{this.model}} /> \ 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: "<script>alert('hello')</script>") - 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: "<script>alert('hello')</script>") + 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`<ChatChannelSettingsSavedIndicator @property={{this.property}} />` - ); - - 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`<ChatChannelSettingsView @channel={{this.channel}} />`); - - 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