mirror of
https://github.com/discourse/discourse.git
synced 2025-06-04 06:44:43 +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:
@ -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}} />
|
Reference in New Issue
Block a user