mirror of
https://github.com/discourse/discourse.git
synced 2025-03-20 03:35:30 +08:00
UI: redesigned settings/members (#23804)
This PR is a first step towards private groups. It redesigns settings/members area of a channel and also drops the "about" page which is now mixed into settings. This commit is also: - introducing chat-form, a small DSL to create forms, ideally I would want something in core for this - introducing a DToggleSwitch page object component to simplify testing toggles - migrating various components to gjs
This commit is contained in:
parent
93c96cf6fa
commit
42801c950f
@ -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,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -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));
|
||||
});
|
||||
}
|
||||
|
@ -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,
|
||||
},
|
||||
};
|
||||
|
@ -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" });
|
||||
}
|
||||
|
@ -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>
|
@ -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;
|
||||
}
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
@ -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}}
|
@ -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();
|
||||
});
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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>
|
@ -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);
|
||||
}
|
||||
}
|
@ -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}}
|
@ -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");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
@ -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() {
|
@ -1,3 +0,0 @@
|
||||
<span class="chat-retention-reminder-text">
|
||||
{{this.text}}
|
||||
</span>
|
@ -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);
|
||||
}
|
||||
}
|
@ -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}}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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 };
|
||||
}
|
||||
}
|
@ -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>
|
||||
}
|
@ -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 };
|
||||
}
|
||||
}
|
@ -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"
|
||||
|
@ -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);
|
||||
};
|
||||
});
|
||||
}
|
@ -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>
|
@ -1,3 +0,0 @@
|
||||
import Component from "@glimmer/component";
|
||||
|
||||
export default class DcFilterInput extends Component {}
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
import Controller from "@ember/controller";
|
||||
|
||||
export default class ChatChannelInfoMembersController extends Controller {}
|
@ -1,3 +0,0 @@
|
||||
import Controller from "@ember/controller";
|
||||
|
||||
export default class ChatChannelInfoSettingsController extends Controller {}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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 });
|
||||
}
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -1,5 +0,0 @@
|
||||
<ChatChannelAboutView
|
||||
@channel={{this.model}}
|
||||
@onEditChatChannelName={{action "onEditChatChannelName"}}
|
||||
@onEditChatChannelDescription={{action "onEditChatChannelDescription"}}
|
||||
/>
|
@ -1 +1 @@
|
||||
<ChatChannelMembersView @channel={{this.model}} />
|
||||
<ChatChannelMembers @channel={{this.model}} />
|
@ -1 +1 @@
|
||||
<ChatChannelSettingsView @channel={{this.model}} />
|
||||
<ChatChannelSettings @channel={{this.model}} />
|
@ -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>
|
||||
<ChatChannelInfo @channel={{this.model}} />
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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";
|
||||
|
@ -0,0 +1,3 @@
|
||||
.chat-channel-members {
|
||||
width: 100%;
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
.chat-channel-settings {
|
||||
width: 100%;
|
||||
}
|
@ -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";
|
||||
|
@ -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."
|
||||
|
@ -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
|
@ -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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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"
|
||||
|
||||
|
@ -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();
|
||||
});
|
||||
}
|
||||
);
|
@ -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,
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
22
spec/system/page_objects/components/d_toggle_switch.rb
Normal file
22
spec/system/page_objects/components/d_toggle_switch.rb
Normal file
@ -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
|
Loading…
x
Reference in New Issue
Block a user