DEV: implements <Chat::Navbar /> (#24917)

This new navbar component is used for every navbar in chat, full page or drawer, and any screen.

This commit also uses this opportunity to correctly decouple drawer-routes from full page routes. This will avoid having this kind of properties in components: `@includeHeader={{false}}`. The header is now defined in the parent template using a navbar. Each route has now its own template wrapped in a div of the name of the route, eg: `<div class="c-routes-threads">..</div>`.

The navbar API:

```gjs
<Navbar as |navbar|>
 <navbar.BackButton />
 <navbar.Title @title="Foo" />
 <navbar.ChannelTitle @channel={{@channel}} />
 <navbar.Actions as |action|>
   <action.CloseThreadButton />
 </navbar.Actions>
</navbar>
```

The full list of components is listed in `plugins/chat/assets/javascripts/discourse/components/navbar/index.gjs` and `plugins/chat/assets/javascripts/discourse/components/navbar/actions.gjs`.

Visually the header is not changing much, only in drawer mode the background has been removed.

This commit also introduces a `<List />` component to facilitate rendering lists in chat plugin.
This commit is contained in:
Joffrey JAFFEUX 2023-12-18 17:49:58 +01:00 committed by GitHub
parent a08691a599
commit 53b96638c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
110 changed files with 1370 additions and 1716 deletions

View File

@ -412,11 +412,6 @@ html {
.reviewable .status span.approved {
color: var(--success-hover);
}
// Chat
.chat-channel .open-drawer-btn {
color: var(--primary-medium);
}
}
// chat

View File

@ -1,88 +0,0 @@
<div class="chat-browse-view__header chat-full-page-header">
{{#if this.site.mobileView}}
<LinkTo
@route="chat.index"
class="chat-full-page-header__back-btn no-text btn-flat btn"
title={{i18n "chat.browse.back"}}
>
{{d-icon "chevron-left"}}
</LinkTo>
{{/if}}
<span class="chat-browse-view__title">{{i18n "chat.browse.title"}}</span>
{{#if this.currentUser.staff}}
<DButton
@action={{this.createChannel}}
@icon="plus"
@label={{if this.site.desktopView "chat.create_channel.title"}}
class={{concat-class
"new-channel-btn"
(if this.site.mobileView "btn-flat")
}}
/>
{{/if}}
</div>
<div class="chat-browse-view">
<div class="chat-browse-view__actions">
<nav>
<ul class="nav-pills chat-browse-view__filters">
{{#each this.tabs as |tab|}}
<li class={{concat "chat-browse-view__filter -" tab}}>
<LinkTo
@route={{concat "chat.browse." tab}}
class={{concat "chat-browse-view__filter-link -" tab}}
>
{{i18n (concat "chat.browse.filter_" tab)}}
</LinkTo>
</li>
{{/each}}
</ul>
</nav>
<DcFilterInput
{{did-insert (action this.focusFilterInput)}}
@filterAction={{this.debouncedFiltering}}
@icons={{hash right="search"}}
@containerClass="filter-input"
placeholder={{i18n "chat.browse.filter_input_placeholder"}}
/>
</div>
{{#if
(and
this.channelsCollection.fetchedOnce (not this.channelsCollection.length)
)
}}
<div class="empty-state">
<span class="empty-state-title">{{i18n "chat.empty_state.title"}}</span>
<div class="empty-state-body">
<p>{{i18n "chat.empty_state.direct_message"}}</p>
<DButton
@action={{this.showChatNewMessageModal}}
@label="chat.empty_state.direct_message_cta"
/>
</div>
</div>
{{else if this.channelsCollection.length}}
<LoadMore
@selector=".chat-channel-card"
@action={{this.channelsCollection.load}}
>
<div class="chat-browse-view__content_wrapper">
<div class="chat-browse-view__content">
<div class="chat-browse-view__cards">
{{#each this.channelsCollection as |channel|}}
<ChatChannelCard @channel={{channel}} />
{{/each}}
</div>
</div>
</div>
<ConditionalLoadingSpinner
@condition={{this.channelsCollection.loading}}
/>
</LoadMore>
{{/if}}
</div>

View File

@ -1,76 +0,0 @@
import Component from "@ember/component";
import { action, computed } from "@ember/object";
import { schedule } from "@ember/runloop";
import { inject as service } from "@ember/service";
import { INPUT_DELAY } from "discourse-common/config/environment";
import discourseDebounce from "discourse-common/lib/debounce";
import ChatModalCreateChannel from "discourse/plugins/chat/discourse/components/chat/modal/create-channel";
import ChatModalNewMessage from "discourse/plugins/chat/discourse/components/chat/modal/new-message";
const TABS = ["all", "open", "closed", "archived"];
export default class ChatBrowseView extends Component {
@service chatApi;
@service modal;
tagName = "";
didReceiveAttrs() {
super.didReceiveAttrs(...arguments);
if (!this.channelsCollection) {
this.set("channelsCollection", this.chatApi.channels());
}
this.channelsCollection.load({
filter: this.filter,
status: this.status,
});
}
@computed("siteSettings.chat_allow_archiving_channels")
get tabs() {
if (this.siteSettings.chat_allow_archiving_channels) {
return TABS;
} else {
return [...TABS].removeObject("archived");
}
}
@action
showChatNewMessageModal() {
this.modal.show(ChatModalNewMessage);
}
@action
onScroll() {
discourseDebounce(
this,
this.channelsCollection.load,
{ filter: this.filter, status: this.status },
INPUT_DELAY
);
}
@action
debouncedFiltering(event) {
this.set("channelsCollection", this.chatApi.channels());
discourseDebounce(
this,
this.channelsCollection.load,
{ filter: event.target.value, status: this.status },
INPUT_DELAY
);
}
@action
createChannel() {
this.modal.show(ChatModalCreateChannel);
}
@action
focusFilterInput(input) {
schedule("afterRender", () => input?.focus());
}
}

View File

@ -1,110 +0,0 @@
import Component from "@glimmer/component";
import { action } from "@ember/object";
import { LinkTo } from "@ember/routing";
import { inject as service } from "@ember/service";
import DButton from "discourse/components/d-button";
import icon from "discourse-common/helpers/d-icon";
import I18n from "discourse-i18n";
import ChannelTitle from "discourse/plugins/chat/discourse/components/channel-title";
import ChatModalEditChannelName from "discourse/plugins/chat/discourse/components/chat/modal/edit-channel-name";
import ChatChannelStatus from "discourse/plugins/chat/discourse/components/chat-channel-status";
export default class ChatChannelMessageEmojiPicker extends Component {
@service chatChannelInfoRouteOriginManager;
@service site;
@service modal;
@service chatGuardian;
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.isOpen;
}
get canEditChannel() {
return (
this.chatGuardian.canEditChatChannel() &&
(this.args.channel.isCategoryChannel ||
(this.args.channel.isDirectMessageChannel &&
this.args.channel.chatable.group))
);
}
@action
editChannelTitle() {
return this.modal.show(ChatModalEditChannelName, {
model: this.args.channel,
});
}
<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>
<ChannelTitle @channel={{@channel}} />
{{#if this.canEditChannel}}
<DButton
@icon="pencil-alt"
class="btn-flat"
@action={{this.editChannelTitle}}
/>
{{/if}}
</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>
}

View File

@ -21,6 +21,7 @@ import discourseDebounce from "discourse-common/lib/debounce";
import { bind } from "discourse-common/utils/decorators";
import and from "truth-helpers/helpers/and";
import not from "truth-helpers/helpers/not";
import ChatChannelStatus from "discourse/plugins/chat/discourse/components/chat-channel-status";
import ChatChannelSubscriptionManager from "discourse/plugins/chat/discourse/lib/chat-channel-subscription-manager";
import {
FUTURE,
@ -45,7 +46,6 @@ import ChatComposerChannel from "./chat/composer/channel";
import ChatScrollToBottomArrow from "./chat/scroll-to-bottom-arrow";
import ChatSelectionManager from "./chat/selection-manager";
import ChatChannelPreviewCard from "./chat-channel-preview-card";
import ChatFullPageHeader from "./chat-full-page-header";
import ChatMentionWarnings from "./chat-mention-warnings";
import Message from "./chat-message";
import ChatNotices from "./chat-notices";
@ -719,14 +719,9 @@ export default class ChatChannel extends Component {
{{didUpdate this.loadMessages @targetMessageId}}
data-id={{@channel.id}}
>
<ChatFullPageHeader
@channel={{@channel}}
@onCloseFullScreen={{this.onCloseFullScreen}}
@displayed={{this.includeHeader}}
/>
<ChatChannelStatus @channel={{@channel}} />
<ChatNotices @channel={{@channel}} />
<ChatMentionWarnings />
<div

View File

@ -1,70 +0,0 @@
import Component from "@glimmer/component";
import { action } from "@ember/object";
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import { inject as service } from "@ember/service";
import I18n from "discourse-i18n";
import and from "truth-helpers/helpers/and";
import ChatDrawerHeader from "discourse/plugins/chat/discourse/components/chat-drawer/header";
import ChatDrawerHeaderBackLink from "discourse/plugins/chat/discourse/components/chat-drawer/header/back-link";
import ChatDrawerHeaderRightActions from "discourse/plugins/chat/discourse/components/chat-drawer/header/right-actions";
import ChatDrawerHeaderTitle from "discourse/plugins/chat/discourse/components/chat-drawer/header/title";
import ChatThreadList from "discourse/plugins/chat/discourse/components/chat-thread-list";
export default class ChatDrawerChannelThreads extends Component {
@service appEvents;
@service chat;
@service chatStateManager;
@service chatChannelsManager;
backLinkTitle = I18n.t("chat.return_to_list");
@action
fetchChannel() {
if (!this.args.params?.channelId) {
return;
}
return this.chatChannelsManager
.find(this.args.params.channelId)
.then((channel) => {
this.chat.activeChannel = channel;
});
}
<template>
<ChatDrawerHeader @toggleExpand={{@drawerActions.toggleExpand}}>
{{#if
(and this.chatStateManager.isDrawerExpanded this.chat.activeChannel)
}}
<div class="chat-drawer-header__left-actions">
<div class="chat-drawer-header__top-line">
<ChatDrawerHeaderBackLink
@route="chat.channel"
@title={{this.backLinkTitle}}
@routeModels={{this.chat.activeChannel.routeModels}}
/>
</div>
</div>
{{/if}}
<ChatDrawerHeaderTitle
@title="chat.threads.list"
@icon="discourse-threads"
@channelName={{this.chat.activeChannel.title}}
/>
<ChatDrawerHeaderRightActions @drawerActions={{@drawerActions}} />
</ChatDrawerHeader>
{{#if this.chatStateManager.isDrawerExpanded}}
<div class="chat-drawer-content" {{didInsert this.fetchChannel}}>
{{#if this.chat.activeChannel}}
<ChatThreadList
@channel={{this.chat.activeChannel}}
@includeHeader={{false}}
/>
{{/if}}
</div>
{{/if}}
</template>
}

View File

@ -1,25 +0,0 @@
import Component from "@glimmer/component";
import { on } from "@ember/modifier";
import { inject as service } from "@ember/service";
import i18n from "discourse-common/helpers/i18n";
export default class ChatDrawerHeader extends Component {
@service chatStateManager;
<template>
{{! template-lint-disable no-invalid-interactive }}
<div
role="region"
aria-label={{i18n "chat.aria_roles.header"}}
class="chat-drawer-header"
{{on "click" @toggleExpand}}
title={{if
this.chatStateManager.isDrawerExpanded
(i18n "chat.collapse")
(i18n "chat.expand")
}}
>
{{yield}}
</div>
</template>
}

View File

@ -1,21 +0,0 @@
import Component from "@glimmer/component";
import { array } from "@ember/helper";
import { LinkTo } from "@ember/routing";
import { inject as service } from "@ember/service";
import dIcon from "discourse-common/helpers/d-icon";
import or from "truth-helpers/helpers/or";
export default class ChatDrawerHeaderBackLink extends Component {
@service chatStateManager;
<template>
<LinkTo
title={{@title}}
class="chat-drawer-header__back-btn"
@route={{@route}}
@models={{or @routeModels (array)}}
>
{{dIcon "chevron-left"}}
</LinkTo>
</template>
}

View File

@ -1,45 +0,0 @@
import Component from "@glimmer/component";
import { on } from "@ember/modifier";
import { LinkTo } from "@ember/routing";
import { inject as service } from "@ember/service";
import ChannelTitle from "../../channel-title";
export default class ChatDrawerChannelHeaderTitle extends Component {
@service chatStateManager;
<template>
{{#if @channel}}
{{#if this.chatStateManager.isDrawerExpanded}}
<LinkTo
@route={{if
@channel.isDirectMessageChannel
"chat.channel.info.settings"
"chat.channel.info.members"
}}
@models={{@channel.routeModels}}
class="chat-drawer-header__title"
>
<div class="chat-drawer-header__top-line">
<ChannelTitle @channel={{@channel}} />
</div>
</LinkTo>
{{else}}
<div
role="button"
{{on "click" @drawerActions.toggleExpand}}
class="chat-drawer-header__title"
>
<div class="chat-drawer-header__top-line">
<ChannelTitle @channel={{@channel}}>
{{#if @channel.tracking.unreadCount}}
<span class="chat-unread-count">
{{@channel.tracking.unreadCount}}
</span>
{{/if}}
</ChannelTitle>
</div>
</div>
{{/if}}
{{/if}}
</template>
}

View File

@ -1,12 +0,0 @@
import DButton from "discourse/components/d-button";
const CloseButton = <template>
<DButton
@icon="times"
@action={{@close}}
@title="chat.close"
class="btn-flat btn-link chat-drawer-header__close-btn"
/>
</template>;
export default CloseButton;

View File

@ -1,18 +0,0 @@
import Component from "@glimmer/component";
import { inject as service } from "@ember/service";
import DButton from "discourse/components/d-button";
export default class ChatDrawerHeaderFullPageButton extends Component {
@service chatStateManager;
<template>
{{#if this.chatStateManager.isDrawerExpanded}}
<DButton
@icon="discourse-expand"
class="btn-flat btn-link chat-drawer-header__full-screen-btn"
@title="chat.open_full_page"
@action={{@openInFullPage}}
/>
{{/if}}
</template>
}

View File

@ -1,18 +0,0 @@
import Component from "@glimmer/component";
import { inject as service } from "@ember/service";
import i18n from "discourse-common/helpers/i18n";
import BackLink from "./back-link";
export default class ChatDrawerHeaderLeftActions extends Component {
@service chatStateManager;
<template>
{{#if this.chatStateManager.isDrawerExpanded}}
<div class="chat-drawer-header__left-actions">
<div class="chat-drawer-header__top-line">
<BackLink @route="chat" @title={{i18n "chat.return_to_list"}} />
</div>
</div>
{{/if}}
</template>
}

View File

@ -1,30 +0,0 @@
import Component from "@glimmer/component";
import { inject as service } from "@ember/service";
import ThreadsListButton from "../../chat/thread/threads-list-button";
import CloseButton from "./close-button";
import FullPageButton from "./full-page-button";
import ToggleExpandButton from "./toggle-expand-button";
export default class ChatDrawerHeaderRightActions extends Component {
@service chat;
get showThreadsListButton() {
return this.chat.activeChannel?.threadingEnabled;
}
<template>
<div class="chat-drawer-header__right-actions">
<div class="chat-drawer-header__top-line">
{{#if this.showThreadsListButton}}
<ThreadsListButton @channel={{this.chat.activeChannel}} />
{{/if}}
<ToggleExpandButton @toggleExpand={{@drawerActions.toggleExpand}} />
<FullPageButton @openInFullPage={{@drawerActions.openInFullPage}} />
<CloseButton @close={{@drawerActions.close}} />
</div>
</div>
</template>
}

View File

@ -1,40 +0,0 @@
import Component from "@glimmer/component";
import replaceEmoji from "discourse/helpers/replace-emoji";
import icon from "discourse-common/helpers/d-icon";
import I18n from "discourse-i18n";
export default class ChatDrawerHeaderTitle extends Component {
get headerTitle() {
if (this.args.title) {
return I18n.t(this.args.title);
}
return replaceEmoji(this.args.translatedTitle);
}
get showChannel() {
return this.args.channelName ?? false;
}
get showIcon() {
return this.args.icon ?? false;
}
<template>
<span class="chat-drawer-header__title">
<div class="chat-drawer-header__top-line">
<span class="chat-drawer-header__icon">
{{#if this.showIcon}}
{{icon @icon}}
{{/if}}
</span>
<span class="chat-drawer-header__title-text">{{this.headerTitle}}</span>
{{#if this.showChannel}}
<span class="chat-drawer-header__divider">-</span>
<span class="chat-drawer-header__channel-name">{{@channelName}}</span>
{{/if}}
</div>
</span>
</template>
}

View File

@ -1,28 +0,0 @@
import Component from "@glimmer/component";
import { inject as service } from "@ember/service";
import i18n from "discourse-common/helpers/i18n";
import ChannelsList from "../channels-list";
import Header from "./header";
import RightActions from "./header/right-actions";
export default class ChatDrawerIndex extends Component {
@service chatStateManager;
<template>
<Header @toggleExpand={{@drawerActions.toggleExpand}}>
<div class="chat-drawer-header__title">
<div class="chat-drawer-header__top-line">
{{i18n "chat.heading"}}
</div>
</div>
<RightActions @drawerActions={{@drawerActions}} />
</Header>
{{#if this.chatStateManager.isDrawerExpanded}}
<div class="chat-drawer-content">
<ChannelsList />
</div>
{{/if}}
</template>
}

View File

@ -1,47 +0,0 @@
import Component from "@glimmer/component";
import { inject as service } from "@ember/service";
import I18n from "discourse-i18n";
import ChatDrawerHeader from "discourse/plugins/chat/discourse/components/chat-drawer/header";
import ChatDrawerHeaderBackLink from "discourse/plugins/chat/discourse/components/chat-drawer/header/back-link";
import ChatDrawerHeaderRightActions from "discourse/plugins/chat/discourse/components/chat-drawer/header/right-actions";
import ChatDrawerHeaderTitle from "discourse/plugins/chat/discourse/components/chat-drawer/header/title";
import UserThreads from "discourse/plugins/chat/discourse/components/user-threads";
export default class ChatDrawerThreads extends Component {
@service appEvents;
@service chat;
@service chatStateManager;
@service chatChannelsManager;
backLinkTitle = I18n.t("chat.return_to_list");
<template>
<ChatDrawerHeader @toggleExpand={{@drawerActions.toggleExpand}}>
{{#if this.chatStateManager.isDrawerExpanded}}
<div class="chat-drawer-header__left-actions">
<div class="chat-drawer-header__top-line">
<ChatDrawerHeaderBackLink
@route="chat"
@title={{this.backLink.title}}
/>
</div>
</div>
{{/if}}
<ChatDrawerHeaderTitle
@title="chat.threads.list"
@icon="discourse-threads"
@channelName={{this.chat.activeChannel.title}}
/>
<ChatDrawerHeaderRightActions @drawerActions={{@drawerActions}} />
</ChatDrawerHeader>
{{#if this.chatStateManager.isDrawerExpanded}}
<div class="chat-drawer-content">
<UserThreads />
</div>
{{/if}}
</template>
}

View File

@ -1,99 +0,0 @@
import Component from "@glimmer/component";
import { on } from "@ember/modifier";
import { action } from "@ember/object";
import { LinkTo } from "@ember/routing";
import { inject as service } from "@ember/service";
import DButton from "discourse/components/d-button";
import concatClass from "discourse/helpers/concat-class";
import icon from "discourse-common/helpers/d-icon";
import and from "truth-helpers/helpers/and";
import or from "truth-helpers/helpers/or";
import ChannelTitle from "discourse/plugins/chat/discourse/components/channel-title";
import ChatModalEditChannelName from "discourse/plugins/chat/discourse/components/chat/modal/edit-channel-name";
import ThreadsListButton from "discourse/plugins/chat/discourse/components/chat/thread/threads-list-button";
import ChatChannelStatus from "discourse/plugins/chat/discourse/components/chat-channel-status";
export default class ChatFullPageHeader extends Component {
@service chatStateManager;
@service modal;
@service router;
@service site;
get displayed() {
return this.args.displayed ?? true;
}
get showThreadsListButton() {
return (
this.args.channel.threadingEnabled &&
this.router.currentRoute.name !== "chat.channel.threads" &&
this.router.currentRoute.name !== "chat.channel.thread.index" &&
this.router.currentRoute.name !== "chat.channel.thread"
);
}
@action
editChannelTitle() {
return this.modal.show(ChatModalEditChannelName, {
model: this.args.channel,
});
}
@action
trapMouse(event) {
event.stopPropagation();
}
<template>
{{! template-lint-disable no-invalid-interactive }}
{{#if (and this.chatStateManager.isFullPageActive this.displayed)}}
<div
class={{concatClass
"chat-full-page-header"
(unless @channel.isFollowing "-not-following")
}}
{{on "mousemove" this.trapMouse}}
>
<div class="chat-channel-header-details">
{{#if this.site.mobileView}}
<div class="chat-full-page-header__left-actions">
<LinkTo
@route="chat"
class="chat-full-page-header__back-btn no-text btn-flat"
>
{{icon "chevron-left"}}
</LinkTo>
</div>
{{/if}}
<LinkTo
@route="chat.channel.info"
@models={{@channel.routeModels}}
class="chat-channel-title-wrapper"
>
<ChannelTitle @channel={{@channel}} />
</LinkTo>
{{#if (or @channel.threadingEnabled this.site.desktopView)}}
<div class="chat-full-page-header__right-actions">
{{#if this.site.desktopView}}
<DButton
@icon="discourse-compress"
@title="chat.close_full_page"
class="open-drawer-btn btn-flat"
@action={{@onCloseFullScreen}}
/>
{{/if}}
{{#if this.showThreadsListButton}}
<ThreadsListButton @channel={{@channel}} />
{{/if}}
</div>
{{/if}}
</div>
</div>
<ChatChannelStatus @channel={{@channel}} />
{{/if}}
</template>
}

View File

@ -98,12 +98,15 @@ export default class ChatMessageThreadIndicator extends Component {
@bind
openThread(event) {
if (event.type === "keydown" && event.key !== "Enter") {
if (event?.type === "keydown" && event?.key !== "Enter") {
return;
}
// handle middle mouse
if (event.type === "mousedown" && (event.which === 2 || event.shiftKey)) {
if (
event?.type === "mousedown" &&
(event?.which === 2 || event?.shiftKey)
) {
window.open(
getURL(
this.router.urlFor(

View File

@ -41,7 +41,7 @@ export default class ChatRetentionReminder extends Component {
<DButton
@action={{this.dismiss}}
@icon="times"
class="btn-flat dismiss-btn"
class="btn no-text btn-icon btn-flat no-text dismiss-btn"
/>
</div>
{{/if}}

View File

@ -7,7 +7,6 @@ import isElementInViewport from "discourse/lib/is-element-in-viewport";
import { bind } from "discourse-common/utils/decorators";
import I18n from "discourse-i18n";
import eq from "truth-helpers/helpers/eq";
import ChatThreadListHeader from "discourse/plugins/chat/discourse/components/chat/thread-list/header";
import ChatThreadListItem from "discourse/plugins/chat/discourse/components/chat/thread-list/item";
import ChatTrackMessage from "discourse/plugins/chat/discourse/modifiers/chat/track-message";
@ -179,10 +178,6 @@ export default class ChatThreadList extends Component {
<template>
{{#if this.shouldRender}}
<div class="chat-thread-list" {{this.subscribe @channel}}>
{{#if @includeHeader}}
<ChatThreadListHeader @channel={{@channel}} />
{{/if}}
<div class="chat-thread-list__items" {{this.fill}}>
{{#each this.sortedThreads key="id" as |thread|}}

View File

@ -0,0 +1,14 @@
import NotificationsButtonComponent from "select-kit/components/notifications-button";
import { threadNotificationButtonLevels } from "discourse/plugins/chat/discourse/lib/chat-notification-levels";
export default NotificationsButtonComponent.extend({
pluginApiIdentifiers: ["thread-notifications-button"],
classNames: ["thread-notifications-button"],
content: threadNotificationButtonLevels,
selectKitOptions: {
i18nPrefix: "chat.thread.notifications",
showFullTitle: false,
btnCustomClasses: "btn-flat",
},
});

View File

@ -36,7 +36,6 @@ import ChatScrollableList from "../modifiers/chat/scrollable-list";
import ChatComposerThread from "./chat/composer/thread";
import ChatScrollToBottomArrow from "./chat/scroll-to-bottom-arrow";
import ChatSelectionManager from "./chat/selection-manager";
import ChatThreadHeader from "./chat/thread/header";
import Message from "./chat-message";
import ChatSkeleton from "./chat-skeleton";
import ChatUploadDropZone from "./chat-upload-drop-zone";
@ -493,10 +492,6 @@ export default class ChatThread extends Component {
{{didInsert this.setup}}
{{willDestroy this.teardown}}
>
{{#if @includeHeader}}
<ChatThreadHeader @channel={{@thread.channel}} @thread={{@thread}} />
{{/if}}
<div
class="chat-thread__body popper-viewport chat-messages-scroll"
{{didInsert this.setScrollable}}

View File

@ -4,24 +4,20 @@ import { action } from "@ember/object";
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import didUpdate from "@ember/render-modifiers/modifiers/did-update";
import { inject as service } from "@ember/service";
import { popupAjaxError } from "discourse/lib/ajax-error";
import I18n from "discourse-i18n";
import and from "truth-helpers/helpers/and";
import ChatDrawerHeader from "discourse/plugins/chat/discourse/components/chat-drawer/header";
import ChatDrawerHeaderBackLink from "discourse/plugins/chat/discourse/components/chat-drawer/header/back-link";
import ChatDrawerHeaderRightActions from "discourse/plugins/chat/discourse/components/chat-drawer/header/right-actions";
import ChatDrawerHeaderTitle from "discourse/plugins/chat/discourse/components/chat-drawer/header/title";
import Navbar from "discourse/plugins/chat/discourse/components/chat/navbar";
import ChatThread from "discourse/plugins/chat/discourse/components/chat-thread";
export default class ChatDrawerThread extends Component {
@service appEvents;
export default class ChatDrawerRoutesChannelThread extends Component {
@service chat;
@service chatStateManager;
@service chatChannelsManager;
@service chatHistory;
get backLink() {
get backButton() {
const link = {
models: this.chat.activeChannel.routeModels,
models: this.chat.activeChannel?.routeModels,
};
if (this.chatHistory.previousRoute?.name === "chat.channel.threads") {
@ -47,14 +43,16 @@ export default class ChatDrawerThread extends Component {
}
@action
fetchChannelAndThread() {
async fetchChannelAndThread() {
if (!this.args.params?.channelId || !this.args.params?.threadId) {
return;
}
return this.chatChannelsManager
.find(this.args.params.channelId)
.then((channel) => {
try {
const channel = await this.chatChannelsManager.find(
this.args.params.channelId
);
this.chat.activeChannel = channel;
channel.threadsManager
@ -62,29 +60,25 @@ export default class ChatDrawerThread extends Component {
.then((thread) => {
this.chat.activeChannel.activeThread = thread;
});
});
} catch (error) {
popupAjaxError(error);
}
}
<template>
<ChatDrawerHeader @toggleExpand={{@drawerActions.toggleExpand}}>
{{#if
(and this.chatStateManager.isDrawerExpanded this.chat.activeChannel)
}}
<div class="chat-drawer-header__left-actions">
<div class="chat-drawer-header__top-line">
<ChatDrawerHeaderBackLink
@route={{this.backLink.route}}
@title={{this.backLink.title}}
@routeModels={{this.backLink.models}}
<Navbar @onClick={{this.chat.toggleDrawer}} as |navbar|>
<navbar.BackButton
@title={{this.backButton.title}}
@route={{this.backButton.route}}
@routeModels={{this.backButton.models}}
/>
</div>
</div>
{{/if}}
<ChatDrawerHeaderTitle @translatedTitle={{this.threadTitle}} />
<ChatDrawerHeaderRightActions @drawerActions={{@drawerActions}} />
</ChatDrawerHeader>
<navbar.Title @title={{this.threadTitle}} @icon="discourse-threads" />
<navbar.Actions as |action|>
<action.ToggleDrawerButton />
<action.FullPageButton />
<action.CloseDrawerButton />
</navbar.Actions>
</Navbar>
{{#if this.chatStateManager.isDrawerExpanded}}
<div

View File

@ -0,0 +1,68 @@
import Component from "@glimmer/component";
import { action } from "@ember/object";
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import { inject as service } from "@ember/service";
import replaceEmoji from "discourse/helpers/replace-emoji";
import { popupAjaxError } from "discourse/lib/ajax-error";
import htmlSafe from "discourse-common/helpers/html-safe";
import I18n from "discourse-i18n";
import Navbar from "discourse/plugins/chat/discourse/components/chat/navbar";
import ChatThreadList from "discourse/plugins/chat/discourse/components/chat-thread-list";
export default class ChatDrawerRoutesChannelThreads extends Component {
@service chat;
@service chatChannelsManager;
backLinkTitle = I18n.t("chat.return_to_list");
get title() {
return htmlSafe(
I18n.t("chat.threads.list") +
" - " +
replaceEmoji(this.chat.activeChannel.title)
);
}
@action
async fetchChannel() {
if (!this.args.params?.channelId) {
return;
}
try {
const channel = await this.chatChannelsManager.find(
this.args.params.channelId
);
this.chat.activeChannel = channel;
} catch (error) {
popupAjaxError(error);
}
}
<template>
{{#if this.chat.activeChannel}}
<Navbar @onClick={{this.chat.toggleDrawer}} as |navbar|>
<navbar.BackButton
@title={{this.backLinkTitle}}
@route="chat.channel"
@routeModels={{this.chat.activeChannel?.routeModels}}
/>
<navbar.Title @title={{this.title}} @icon="discourse-threads" />
<navbar.Actions as |action|>
<action.ToggleDrawerButton />
<action.FullPageButton />
<action.CloseDrawerButton />
</navbar.Actions>
</Navbar>
{{/if}}
<div class="chat-drawer-content" {{didInsert this.fetchChannel}}>
{{#if this.chat.activeChannel}}
<ChatThreadList
@channel={{this.chat.activeChannel}}
@includeHeader={{false}}
/>
{{/if}}
</div>
</template>
}

View File

@ -4,14 +4,10 @@ import { action } from "@ember/object";
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import didUpdate from "@ember/render-modifiers/modifiers/did-update";
import { inject as service } from "@ember/service";
import ChatChannel from "../chat-channel";
import Header from "./header";
import ChannelTitle from "./header/channel-title";
import LeftActions from "./header/left-actions";
import RightActions from "./header/right-actions";
import Navbar from "discourse/plugins/chat/discourse/components/chat/navbar";
import ChatChannel from "discourse/plugins/chat/discourse/components/chat-channel";
export default class ChatDrawerChannel extends Component {
@service appEvents;
export default class ChatDrawerRoutesChannel extends Component {
@service chat;
@service chatStateManager;
@service chatChannelsManager;
@ -30,16 +26,16 @@ export default class ChatDrawerChannel extends Component {
}
<template>
<Header @toggleExpand={{@drawerActions.toggleExpand}}>
<LeftActions />
<ChannelTitle
@channel={{this.chat.activeChannel}}
@drawerActions={{@drawerActions}}
/>
<RightActions @drawerActions={{@drawerActions}} />
</Header>
<Navbar @onClick={{this.chat.toggleDrawer}} as |navbar|>
<navbar.BackButton />
<navbar.ChannelTitle @channel={{this.chat.activeChannel}} />
<navbar.Actions as |action|>
<action.ThreadsListButton @channel={{this.chat.activeChannel}} />
<action.ToggleDrawerButton />
<action.FullPageButton />
<action.CloseDrawerButton />
</navbar.Actions>
</Navbar>
{{#if this.chatStateManager.isDrawerExpanded}}
<div

View File

@ -0,0 +1,27 @@
import Component from "@glimmer/component";
import { inject as service } from "@ember/service";
import i18n from "discourse-common/helpers/i18n";
import ChannelsList from "discourse/plugins/chat/discourse/components/channels-list";
import Navbar from "discourse/plugins/chat/discourse/components/chat/navbar";
export default class ChatDrawerRoutesChannels extends Component {
@service chat;
@service chatStateManager;
<template>
<Navbar @onClick={{this.chat.toggleDrawer}} as |navbar|>
<navbar.Title @title={{i18n "chat.heading"}} />
<navbar.Actions as |action|>
<action.ToggleDrawerButton />
<action.FullPageButton />
<action.CloseDrawerButton />
</navbar.Actions>
</Navbar>
{{#if this.chatStateManager.isDrawerExpanded}}
<div class="chat-drawer-content">
<ChannelsList />
</div>
{{/if}}
</template>
}

View File

@ -0,0 +1,38 @@
import Component from "@glimmer/component";
import { inject as service } from "@ember/service";
import i18n from "discourse-common/helpers/i18n";
import I18n from "discourse-i18n";
import Navbar from "discourse/plugins/chat/discourse/components/chat/navbar";
import UserThreads from "discourse/plugins/chat/discourse/components/user-threads";
export default class ChatDrawerRoutesThreads extends Component {
@service chat;
@service chatStateManager;
backButtonTitle = I18n.t("chat.return_to_list");
<template>
<Navbar @onClick={{this.chat.toggleDrawer}} as |navbar|>
<navbar.BackButton @title={{this.backButtonTitle}} />
<navbar.Title
@title={{i18n "chat.threads.list"}}
@icon="discourse-threads"
as |title|
>
<title.SubTitle @title={{this.chat.activeChannel.title}} />
</navbar.Title>
<navbar.Actions as |action|>
<action.ThreadsListButton />
<action.ToggleDrawerButton />
<action.FullPageButton />
<action.CloseDrawerButton />
</navbar.Actions>
</Navbar>
{{#if this.chatStateManager.isDrawerExpanded}}
<div class="chat-drawer-content">
<UserThreads />
</div>
{{/if}}
</template>
}

View File

@ -0,0 +1,9 @@
import Component from "@glimmer/component";
export default class EmptyState extends Component {
<template>
<div class="c-list-empty-state" ...attributes>
{{yield}}
</div>
</template>
}

View File

@ -0,0 +1,72 @@
import Component from "@glimmer/component";
import { hash } from "@ember/helper";
import { action } from "@ember/object";
import { modifier } from "ember-modifier";
import ConditionalLoadingSpinner from "discourse/components/conditional-loading-spinner";
import isElementInViewport from "discourse/lib/is-element-in-viewport";
import { INPUT_DELAY } from "discourse-common/config/environment";
import discourseDebounce from "discourse-common/lib/debounce";
import EmptyState from "./empty-state";
import Item from "./item";
export default class List extends Component {
loadMore = modifier((element) => {
this.intersectionObserver = new IntersectionObserver(this.loadCollection);
this.intersectionObserver.observe(element);
return () => {
this.intersectionObserver.disconnect();
};
});
fill = modifier((element) => {
this.resizeObserver = new ResizeObserver(() => {
if (isElementInViewport(element)) {
this.loadCollection();
}
});
this.resizeObserver.observe(element);
return () => {
this.resizeObserver.disconnect();
};
});
get itemComponent() {
return this.args.itemComponent ?? Item;
}
get emptyStateComponent() {
return EmptyState;
}
@action
loadCollection() {
discourseDebounce(this, this.debouncedLoadCollection, INPUT_DELAY);
}
async debouncedLoadCollection() {
await this.args.collection.load({ limit: 10 });
}
<template>
<div class="c-list">
<div {{this.fill}} ...attributes>
{{#each @collection.items as |item|}}
{{yield (hash Item=(component this.itemComponent item=item))}}
{{else}}
{{#if @collection.fetchedOnce}}
{{yield (hash EmptyState=this.emptyStateComponent)}}
{{/if}}
{{/each}}
</div>
<div {{this.loadMore}}>
<br />
</div>
<ConditionalLoadingSpinner @condition={{@collection.loading}} />
</div>
</template>
}

View File

@ -0,0 +1,7 @@
import Component from "@glimmer/component";
export default class Item extends Component {
<template>
{{yield @item}}
</template>
}

View File

@ -0,0 +1,73 @@
import Component from "@glimmer/component";
import { hash } from "@ember/helper";
import CloseDrawerButton from "./close-drawer-button";
import CloseThreadButton from "./close-thread-button";
import CloseThreadsButton from "./close-threads-button";
import FullPageButton from "./full-page-button";
import NewChannelButton from "./new-channel-button";
import OpenDrawerButton from "./open-drawer-button";
import ThreadSettingsButton from "./thread-settings-button";
import ThreadTrackingDropdown from "./thread-tracking-dropdown";
import ThreadsListButton from "./threads-list-button";
import ToggleDrawerButton from "./toggle-drawer-button";
export default class ChatNavbarActions extends Component {
get openDrawerButtonComponent() {
return OpenDrawerButton;
}
get newChannelButtonComponent() {
return NewChannelButton;
}
get threadTrackingDropdownComponent() {
return ThreadTrackingDropdown;
}
get closeThreadButtonComponent() {
return CloseThreadButton;
}
get closeThreadsButtonComponent() {
return CloseThreadsButton;
}
get threadSettingsButtonComponent() {
return ThreadSettingsButton;
}
get threadsListButtonComponent() {
return ThreadsListButton;
}
get closeDrawerButtonComponent() {
return CloseDrawerButton;
}
get toggleDrawerButtonComponent() {
return ToggleDrawerButton;
}
get chatNavbarFullPageButtonComponent() {
return FullPageButton;
}
<template>
<nav class="c-navbar__actions">
{{yield
(hash
OpenDrawerButton=this.openDrawerButtonComponent
NewChannelButton=this.newChannelButtonComponent
ThreadTrackingDropdown=this.threadTrackingDropdownComponent
CloseThreadButton=this.closeThreadButtonComponent
CloseThreadsButton=this.closeThreadsButtonComponent
ThreadSettingsButton=this.threadSettingsButtonComponent
ThreadsListButton=this.threadsListButtonComponent
CloseDrawerButton=this.closeDrawerButtonComponent
ToggleDrawerButton=this.toggleDrawerButtonComponent
FullPageButton=this.chatNavbarFullPageButtonComponent
)
}}
</nav>
</template>
}

View File

@ -0,0 +1,43 @@
import Component from "@glimmer/component";
import { LinkTo } from "@ember/routing";
import icon from "discourse-common/helpers/d-icon";
import I18n from "I18n";
export default class ChatNavbarBackButton extends Component {
get icon() {
return this.args.icon ?? "chevron-left";
}
get title() {
return this.args.title ?? I18n.t("chat.browse.back");
}
<template>
{{#if @routeModels}}
<LinkTo
@route={{@route}}
@models={{@routeModels}}
class="c-navbar__back-button no-text btn-flat btn"
title={{this.title}}
>
{{#if (has-block)}}
{{yield}}
{{else}}
{{icon this.icon}}
{{/if}}
</LinkTo>
{{else}}
<LinkTo
@route="chat"
class="c-navbar__back-button no-text btn-flat btn"
title={{this.title}}
>
{{#if (has-block)}}
{{yield}}
{{else}}
{{icon this.icon}}
{{/if}}
</LinkTo>
{{/if}}
</template>
}

View File

@ -0,0 +1,17 @@
import Component from "@glimmer/component";
import { LinkTo } from "@ember/routing";
import ChannelTitle from "discourse/plugins/chat/discourse/components/channel-title";
export default class ChatNavbarChannelTitle extends Component {
<template>
{{#if @channel}}
<LinkTo
@route="chat.channel.info.members"
@models={{@channel.routeModels}}
class="c-navbar__channel-title"
>
<ChannelTitle @channel={{@channel}} />
</LinkTo>
{{/if}}
</template>
}

View File

@ -0,0 +1,24 @@
import Component from "@glimmer/component";
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import DButton from "discourse/components/d-button";
export default class ChatNavbarCloseDrawerButton extends Component {
@service chat;
@service chatStateManager;
@action
closeDrawer() {
this.chatStateManager.didCloseDrawer();
this.chat.activeChannel = null;
}
<template>
<DButton
@icon="times"
@action={{this.closeDrawer}}
@title="chat.close"
class="btn-flat no-text c-navbar__close-drawer-button"
/>
</template>
}

View File

@ -0,0 +1,22 @@
import Component from "@glimmer/component";
import { LinkTo } from "@ember/routing";
import { inject as service } from "@ember/service";
import icon from "discourse-common/helpers/d-icon";
import i18n from "discourse-common/helpers/i18n";
export default class ChatNavbarCloseThreadButton extends Component {
@service site;
<template>
{{#if this.site.desktopView}}
<LinkTo
class="c-navbar__close-thread-button btn-flat btn btn-icon no-text"
@route="chat.channel"
@models={{@thread.channel.routeModels}}
title={{i18n "chat.thread.close"}}
>
{{icon "times"}}
</LinkTo>
{{/if}}
</template>
}

View File

@ -0,0 +1,24 @@
import Component from "@glimmer/component";
import { LinkTo } from "@ember/routing";
import { inject as service } from "@ember/service";
import icon from "discourse-common/helpers/d-icon";
import I18n from "I18n";
export default class ChatNavbarCloseThreadsButton extends Component {
@service site;
closeButtonTitle = I18n.t("chat.thread.close");
<template>
{{#if this.site.desktopView}}
<LinkTo
class="c-navbar__close-threads-button btn-flat btn btn-icon no-text"
@route="chat.channel"
@models={{@channel.routeModels}}
title={{this.closeButtonTitle}}
>
{{icon "times"}}
</LinkTo>
{{/if}}
</template>
}

View File

@ -0,0 +1,33 @@
import Component from "@glimmer/component";
import { action } from "@ember/object";
import { next } from "@ember/runloop";
import { inject as service } from "@ember/service";
import DButton from "discourse/components/d-button";
import DiscourseURL from "discourse/lib/url";
export default class ChatNavbarFullPageButton extends Component {
@service chat;
@service chatStateManager;
@action
async openInFullPage() {
this.chatStateManager.storeAppURL();
this.chatStateManager.prefersFullPage();
this.chat.activeChannel = null;
await new Promise((resolve) => next(resolve));
DiscourseURL.routeTo(this.chatStateManager.lastKnownChatURL);
}
<template>
{{#if this.chatStateManager.isDrawerExpanded}}
<DButton
@icon="discourse-expand"
class="btn-flat no-text c-navbar__full-page-button"
@title="chat.open_full_page"
@action={{this.openInFullPage}}
/>
{{/if}}
</template>
}

View File

@ -0,0 +1,46 @@
import Component from "@glimmer/component";
import { hash } from "@ember/helper";
import { on } from "@ember/modifier";
import concatClass from "discourse/helpers/concat-class";
import noop from "discourse/helpers/noop";
import Actions from "./actions";
import BackButton from "./back-button";
import ChannelTitle from "./channel-title";
import Title from "./title";
export default class ChatNavbar extends Component {
get buttonComponent() {
return BackButton;
}
get titleComponent() {
return Title;
}
get actionsComponent() {
return Actions;
}
get channelTitleComponent() {
return ChannelTitle;
}
<template>
{{! template-lint-disable no-invalid-interactive }}
<div
class={{concatClass "c-navbar-container" (if @onClick "-clickable")}}
{{on "click" (if @onClick @onClick (noop))}}
>
<nav class="c-navbar">
{{yield
(hash
BackButton=this.buttonComponent
ChannelTitle=this.channelTitleComponent
Title=this.titleComponent
Actions=this.actionsComponent
)
}}
</nav>
</div>
</template>
}

View File

@ -0,0 +1,32 @@
import Component from "@glimmer/component";
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import DButton from "discourse/components/d-button";
import concatClass from "discourse/helpers/concat-class";
import CreateChannelModal from "discourse/plugins/chat/discourse/components/chat/modal/create-channel";
export default class ChatNavbarNewChannelButton extends Component {
@service chatStateManager;
@service currentUser;
@service modal;
@service site;
@action
createChannel() {
this.modal.show(CreateChannelModal);
}
<template>
{{#if this.currentUser.staff}}
<DButton
@action={{this.createChannel}}
@icon="plus"
@label={{if this.site.desktopView "chat.create_channel.title"}}
class={{concatClass
"c-navbar__new-channel-button"
(if this.site.mobileView "btn-flat")
}}
/>
{{/if}}
</template>
}

View File

@ -0,0 +1,30 @@
import Component from "@glimmer/component";
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import DButton from "discourse/components/d-button";
import DiscourseURL from "discourse/lib/url";
export default class ChatNavbarOpenDrawerButton extends Component {
@service chatStateManager;
@service site;
@action
async openDrawer() {
this.chatStateManager.prefersDrawer();
DiscourseURL.routeTo(this.chatStateManager.lastKnownAppURL).then(() => {
DiscourseURL.routeTo(this.chatStateManager.lastKnownChatURL);
});
}
<template>
{{#if this.site.desktopView}}
<DButton
@icon="discourse-compress"
@title="chat.close_full_page"
class="c-navbar__open-drawer-button btn-flat"
@action={{this.openDrawer}}
/>
{{/if}}
</template>
}

View File

@ -0,0 +1,18 @@
import Component from "@glimmer/component";
import SubTitle from "./sub-title";
export default class ChatNavbarSubTitle extends Component {
get subTitleComponent() {
return SubTitle;
}
<template>
<div class="c-navbar__sub-title">
{{#if (has-block)}}
{{yield}}
{{else}}
{{@title}}
{{/if}}
</div>
</template>
}

View File

@ -0,0 +1,37 @@
import Component from "@glimmer/component";
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import DButton from "discourse/components/d-button";
import ThreadSettingsModal from "discourse/plugins/chat/discourse/components/chat/modal/thread-settings";
export default class ChatNavbarThreadSettingsButton extends Component {
@service currentUser;
@service modal;
get canChangeThreadSettings() {
if (!this.args.thread) {
return false;
}
return (
this.currentUser.staff ||
this.currentUser.id === this.args.thread.originalMessage.user.id
);
}
@action
openThreadSettings() {
this.modal.show(ThreadSettingsModal, { model: this.args.thread });
}
<template>
{{#if this.canChangeThreadSettings}}
<DButton
@action={{this.openThreadSettings}}
@icon="cog"
@title="chat.thread.settings"
class="btn-flat c-navbar__thread-settings-button"
/>
{{/if}}
</template>
}

View File

@ -0,0 +1,66 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import concatClass from "discourse/helpers/concat-class";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { NotificationLevels } from "discourse/lib/notification-levels";
import ThreadTrackingDropdown from "discourse/plugins/chat/discourse/components/chat-thread-tracking-dropdown";
import UserChatThreadMembership from "discourse/plugins/chat/discourse/models/user-chat-thread-membership";
export default class ChatNavbarThreadTrackingDropdown extends Component {
@service chatApi;
@tracked persistedNotificationLevel = true;
get threadNotificationLevel() {
return this.membership?.notificationLevel || NotificationLevels.REGULAR;
}
get membership() {
return this.args.thread.currentUserMembership;
}
@action
async updateThreadNotificationLevel(newNotificationLevel) {
this.persistedNotificationLevel = false;
let currentNotificationLevel;
if (this.membership) {
currentNotificationLevel = this.membership.notificationLevel;
this.membership.notificationLevel = newNotificationLevel;
} else {
this.args.thread.currentUserMembership = UserChatThreadMembership.create({
notification_level: newNotificationLevel,
last_read_message_id: null,
});
}
try {
const response =
await this.chatApi.updateCurrentUserThreadNotificationsSettings(
this.args.thread.channel.id,
this.args.thread.id,
{ notificationLevel: newNotificationLevel }
);
this.membership.last_read_message_id =
response.membership.last_read_message_id;
this.persistedNotificationLevel = true;
} catch (error) {
this.membership.notificationLevel = currentNotificationLevel;
popupAjaxError(error);
}
}
<template>
<ThreadTrackingDropdown
@value={{this.threadNotificationLevel}}
@onChange={{this.updateThreadNotificationLevel}}
@class={{concatClass
"c-navbar__thread-tracking-dropdown"
(if this.persistedNotificationLevel "-persisted")
}}
/>
</template>
}

View File

@ -0,0 +1,42 @@
import Component from "@glimmer/component";
import { LinkTo } from "@ember/routing";
import { inject as service } from "@ember/service";
import concatClass from "discourse/helpers/concat-class";
import icon from "discourse-common/helpers/d-icon";
import I18n from "I18n";
import ThreadHeaderUnreadIndicator from "discourse/plugins/chat/discourse/components/chat/thread/header-unread-indicator";
export default class ChatNavbarThreadsListButton extends Component {
@service router;
threadsListLabel = I18n.t("chat.threads.list");
get showThreadsListButton() {
return (
this.args.channel?.threadingEnabled &&
this.router.currentRoute.name !== "chat.channel.threads" &&
this.router.currentRoute.name !== "chat.channel.thread" &&
this.router.currentRoute.name !== "chat.channel.thread.index"
);
}
<template>
{{#if this.showThreadsListButton}}
<LinkTo
@route="chat.channel.threads"
@models={{@channel.routeModels}}
title={{this.threadsListLabel}}
class={{concatClass
"c-navbar__threads-list-button"
"btn"
"no-text"
"btn-flat"
(if @channel.threadsManager.unreadThreadCount "has-unreads")
}}
>
{{icon "discourse-threads"}}
<ThreadHeaderUnreadIndicator @channel={{@channel}} />
</LinkTo>
{{/if}}
</template>
}

View File

@ -0,0 +1,27 @@
import Component from "@glimmer/component";
import { hash } from "@ember/helper";
import icon from "discourse-common/helpers/d-icon";
import SubTitle from "./sub-title";
export default class ChatNavbarTitle extends Component {
get subTitleComponent() {
return SubTitle;
}
<template>
<div class="c-navbar__title">
{{#if (has-block)}}
{{#if @icon}}
{{icon @icon}}
{{/if}}
{{@title}}
{{yield (hash SubTitle=this.subTitleComponent)}}
{{else}}
{{#if @icon}}
{{icon @icon}}
{{/if}}
{{@title}}
{{/if}}
</div>
</template>
}

View File

@ -2,7 +2,8 @@ import Component from "@glimmer/component";
import { inject as service } from "@ember/service";
import DButton from "discourse/components/d-button";
export default class ChatDrawerHeaderToggleExpandButton extends Component {
export default class ChatNavbarToggleDrawerButton extends Component {
@service chat;
@service chatStateManager;
<template>
@ -12,13 +13,13 @@ export default class ChatDrawerHeaderToggleExpandButton extends Component {
"angle-double-down"
"angle-double-up"
}}
@action={{@toggleExpand}}
@action={{this.chat.toggleDrawer}}
@title={{if
this.chatStateManager.isDrawerExpanded
"chat.collapse"
"chat.expand"
}}
class="btn-flat btn-link chat-drawer-header__expand-btn"
class="btn-flat no-text c-navbar__toggle-drawer-button"
/>
</template>
}

View File

@ -0,0 +1,131 @@
import { cached, tracked } from "@glimmer/tracking";
import Component from "@ember/component";
import { concat, hash } from "@ember/helper";
import { action, computed } from "@ember/object";
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import { LinkTo } from "@ember/routing";
import { schedule } from "@ember/runloop";
import { inject as service } from "@ember/service";
import DButton from "discourse/components/d-button";
import { INPUT_DELAY } from "discourse-common/config/environment";
import i18n from "discourse-common/helpers/i18n";
import discourseDebounce from "discourse-common/lib/debounce";
import List from "discourse/plugins/chat/discourse/components/chat/list";
import ChatModalNewMessage from "discourse/plugins/chat/discourse/components/chat/modal/new-message";
import Navbar from "discourse/plugins/chat/discourse/components/chat/navbar";
import ChatChannelCard from "discourse/plugins/chat/discourse/components/chat-channel-card";
import DcFilterInput from "discourse/plugins/chat/discourse/components/dc-filter-input";
const TABS = ["all", "open", "closed", "archived"];
export default class ChatRoutesBrowse extends Component {
@service chatApi;
@service modal;
@tracked filter = "";
@cached
get channelsCollection() {
return this.chatApi.channels({
filter: this.filter,
status: this.attrs.status,
});
}
@computed("siteSettings.chat_allow_archiving_channels")
get tabs() {
if (this.siteSettings.chat_allow_archiving_channels) {
return TABS;
} else {
return [...TABS].removeObject("archived");
}
}
@action
showChatNewMessageModal() {
this.modal.show(ChatModalNewMessage);
}
@action
setFilter(event) {
this.filter = event.target.value;
discourseDebounce(this.debouncedLoad, INPUT_DELAY);
}
@action
debouncedLoad() {
this.channelsCollection.load({ limit: 10 });
}
@action
focusFilterInput(input) {
schedule("afterRender", () => input?.focus());
}
<template>
<div class="c-routes-browse">
<Navbar as |navbar|>
<navbar.BackButton />
<navbar.Title @title={{i18n "chat.browse.title"}} />
<navbar.Actions as |action|>
<action.NewChannelButton />
</navbar.Actions>
</Navbar>
<div class="chat-browse-view">
<div class="chat-browse-view__actions">
<nav>
<ul class="nav-pills chat-browse-view__filters">
{{#each this.tabs as |tab|}}
<li class={{concat "chat-browse-view__filter -" tab}}>
<LinkTo
@route={{concat "chat.browse." tab}}
class={{concat "chat-browse-view__filter-link -" tab}}
>
{{i18n (concat "chat.browse.filter_" tab)}}
</LinkTo>
</li>
{{/each}}
</ul>
</nav>
<DcFilterInput
{{didInsert this.focusFilterInput}}
@filterAction={{this.setFilter}}
@icons={{hash right="search"}}
@containerClass="filter-input"
placeholder={{i18n "chat.browse.filter_input_placeholder"}}
/>
</div>
<div class="chat-browse-view__content_wrapper">
<div class="chat-browse-view__content">
<List
@collection={{this.channelsCollection}}
class="chat-browse-view__cards"
as |list|
>
<list.Item as |channel|>
<ChatChannelCard @channel={{channel}} />
</list.Item>
<list.EmptyState>
<span class="empty-state-title">
{{i18n "chat.empty_state.title"}}
</span>
<div class="empty-state-body">
<p>{{i18n "chat.empty_state.direct_message"}}</p>
<DButton
@action={{this.showChatNewMessageModal}}
@label="chat.empty_state.direct_message_cta"
/>
</div>
</list.EmptyState>
</List>
</div>
</div>
</div>
</div>
</template>
}

View File

@ -13,11 +13,11 @@ import icon from "discourse-common/helpers/d-icon";
import discourseDebounce from "discourse-common/lib/debounce";
import I18n from "discourse-i18n";
import MessageCreator from "discourse/plugins/chat/discourse/components/chat/message-creator";
import { MODES } from "discourse/plugins/chat/discourse/components/chat/message-creator/constants";
import ChatUserInfo from "discourse/plugins/chat/discourse/components/chat-user-info";
import DcFilterInput from "discourse/plugins/chat/discourse/components/dc-filter-input";
import { MODES } from "./chat/message-creator/constants";
export default class ChatChannelMembers extends Component {
export default class ChatRouteChannelInfoMembers extends Component {
@service appEvents;
@service chatApi;
@service modal;

View File

@ -28,7 +28,7 @@ const NOTIFICATION_LEVELS = [
{ name: I18n.t("chat.notification_levels.always"), value: "always" },
];
export default class ChatAboutScreen extends Component {
export default class ChatRouteChannelInfoSettings extends Component {
@service chatApi;
@service chatGuardian;
@service chatChannelsManager;

View File

@ -0,0 +1,91 @@
import Component from "@glimmer/component";
import { action } from "@ember/object";
import { LinkTo } from "@ember/routing";
import { inject as service } from "@ember/service";
import I18n from "discourse-i18n";
import ChatModalEditChannelName from "discourse/plugins/chat/discourse/components/chat/modal/edit-channel-name";
import Navbar from "discourse/plugins/chat/discourse/components/chat/navbar";
import ChatChannelStatus from "discourse/plugins/chat/discourse/components/chat-channel-status";
export default class ChatRoutesChannelInfo extends Component {
@service chatChannelInfoRouteOriginManager;
@service site;
@service modal;
@service chatGuardian;
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.isOpen;
}
get canEditChannel() {
return (
this.chatGuardian.canEditChatChannel() &&
(this.args.channel.isCategoryChannel ||
(this.args.channel.isDirectMessageChannel &&
this.args.channel.chatable.group))
);
}
@action
editChannelTitle() {
return this.modal.show(ChatModalEditChannelName, {
model: this.args.channel,
});
}
<template>
<div class="c-routes-channel-info">
<Navbar as |navbar|>
{{#if this.chatChannelInfoRouteOriginManager.isBrowse}}
<navbar.BackButton
@route="chat.browse"
@title={{this.backToAllChannelsLabel}}
/>
{{else}}
<navbar.BackButton
@route="chat.channel"
@routeModels={{@channel.routeModels}}
@title={{this.backToChannelLabel}}
/>
{{/if}}
<navbar.ChannelTitle @channel={{@channel}} />
</Navbar>
<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>
</div>
</template>
}

View File

@ -0,0 +1,20 @@
import Component from "@glimmer/component";
import { array } from "@ember/helper";
import ThreadHeader from "discourse/plugins/chat/discourse/components/chat/thread/header";
import Thread from "discourse/plugins/chat/discourse/components/chat-thread";
export default class ChatRoutesChannelThread extends Component {
<template>
<div class="c-routes-channel-thread">
{{#each (array @thread) as |thread|}}
<ThreadHeader @thread={{thread}} />
<Thread
@thread={{thread}}
@targetMessageId={{@targetMessageId}}
@includeHeader={{true}}
/>
{{/each}}
</div>
</template>
}

View File

@ -0,0 +1,12 @@
import Component from "@glimmer/component";
import ChatThreadListHeader from "discourse/plugins/chat/discourse/components/chat/thread-list/header";
import ChatThreadList from "discourse/plugins/chat/discourse/components/chat-thread-list";
export default class ChatRoutesChannelThreads extends Component {
<template>
<div class="c-routes-channel-threads">
<ChatThreadListHeader @channel={{@channel}} />
<ChatThreadList @channel={{@channel}} @includeHeader={{true}} />
</div>
</template>
}

View File

@ -0,0 +1,33 @@
import Component from "@glimmer/component";
import { inject as service } from "@ember/service";
import Navbar from "discourse/plugins/chat/discourse/components/chat/navbar";
import SidePanel from "discourse/plugins/chat/discourse/components/chat-side-panel";
import FullPageChat from "discourse/plugins/chat/discourse/components/full-page-chat";
export default class ChatRoutesChannel extends Component {
@service site;
<template>
<div class="c-routes-channel">
<Navbar as |navbar|>
{{#if this.site.mobileView}}
<navbar.BackButton />
{{/if}}
<navbar.ChannelTitle @channel={{@channel}} />
<navbar.Actions as |action|>
<action.OpenDrawerButton />
<action.ThreadsListButton @channel={{@channel}} />
</navbar.Actions>
</Navbar>
<FullPageChat
@channel={{@channel}}
@targetMessageId={{@targetMessageId}}
/>
</div>
<SidePanel>
{{outlet}}
</SidePanel>
</template>
}

View File

@ -0,0 +1,24 @@
import Component from "@glimmer/component";
import i18n from "discourse-common/helpers/i18n";
import Navbar from "discourse/plugins/chat/discourse/components/chat/navbar";
import UserThreads from "discourse/plugins/chat/discourse/components/user-threads";
export default class ChatRoutesThreads extends Component {
<template>
<div class="c-routes-threads">
<Navbar as |navbar|>
<navbar.BackButton />
<navbar.Title
@title={{i18n "chat.my_threads.title"}}
@icon="discourse-threads"
/>
<navbar.Actions as |action|>
<action.OpenDrawerButton />
</navbar.Actions>
</Navbar>
<UserThreads />
</div>
</template>
}

View File

@ -1,70 +1,37 @@
import Component from "@glimmer/component";
import { LinkTo } from "@ember/routing";
import { inject as service } from "@ember/service";
import replaceEmoji from "discourse/helpers/replace-emoji";
import icon from "discourse-common/helpers/d-icon";
import i18n from "discourse-common/helpers/i18n";
import I18n from "discourse-i18n";
import Navbar from "discourse/plugins/chat/discourse/components/chat/navbar";
export default class ChatThreadListHeader extends Component {
@service router;
@service site;
threadListTitle = I18n.t("chat.threads.list");
closeButtonTitle = I18n.t("chat.thread.close");
showCloseButton = !this.site.mobileView;
get showBackButton() {
return this.args.channel && this.site.mobileView;
}
get backButton() {
return {
route: "chat.channel.index",
models: this.args.channel.routeModels,
title: I18n.t("chat.return_to_channel"),
};
}
<template>
<div class="chat-thread-list-header">
<div class="chat-thread-header__left-buttons">
{{#if this.showBackButton}}
<LinkTo
class="chat-thread__back-to-previous-route btn-flat btn btn-icon no-text"
@route={{this.backButton.route}}
@models={{this.backButton.models}}
title={{this.backButton.title}}
>
{{icon "chevron-left"}}
</LinkTo>
{{/if}}
</div>
<div class="chat-thread-list-header__label">
<span>
{{icon "discourse-threads"}}
{{replaceEmoji this.threadListTitle}}
</span>
{{#if this.site.mobileView}}
<div class="chat-thread-list-header__label-channel">
{{replaceEmoji @channel.title}}
</div>
{{/if}}
</div>
{{#if this.showCloseButton}}
<div class="chat-thread-header__buttons">
<LinkTo
class="chat-thread__close btn-flat btn btn-icon no-text"
<Navbar as |navbar|>
<navbar.BackButton
@route="chat.channel"
@models={{@channel.routeModels}}
title={{this.closeButtonTitle}}
@routeModels={{@channel.routeModels}}
@title={{i18n "chat.return_to_channel"}}
/>
<navbar.Title
@title={{replaceEmoji this.threadListTitle}}
@icon="discourse-threads"
as |title|
>
{{icon "times"}}
</LinkTo>
</div>
{{#if this.site.mobileView}}
<title.SubTitle @title={{replaceEmoji @channel.title}} />
{{/if}}
</div>
</navbar.Title>
<navbar.Actions as |action|>
<action.CloseThreadsButton @channel={{@channel}} />
</navbar.Actions>
</Navbar>
</template>
}

View File

@ -50,7 +50,6 @@ export default class ChatThreadListItem extends Component {
</div>
<div class="chat-thread-list-item__metadata">
<div class="chat-thread-list-item__members">
<ChatUserAvatar
@user={{@thread.originalMessage.user}}
@ -72,7 +71,6 @@ export default class ChatThreadListItem extends Component {
}}
{{/if}}
</div>
</div>
</div>
</div>

View File

@ -1,32 +1,15 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";
import { LinkTo } from "@ember/routing";
import { inject as service } from "@ember/service";
import DButton from "discourse/components/d-button";
import concatClass from "discourse/helpers/concat-class";
import replaceEmoji from "discourse/helpers/replace-emoji";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { NotificationLevels } from "discourse/lib/notification-levels";
import icon from "discourse-common/helpers/d-icon";
import I18n from "discourse-i18n";
import ChatModalThreadSettings from "discourse/plugins/chat/discourse/components/chat/modal/thread-settings";
import Navbar from "discourse/plugins/chat/discourse/components/chat/navbar";
import ChatThreadHeaderUnreadIndicator from "discourse/plugins/chat/discourse/components/chat/thread/header-unread-indicator";
import UserChatThreadMembership from "discourse/plugins/chat/discourse/models/user-chat-thread-membership";
import ThreadNotificationsButton from "discourse/plugins/chat/select-kit/addons/components/thread-notifications-button";
export default class ChatThreadHeader extends Component {
@service currentUser;
@service chatApi;
@service router;
@service chatStateManager;
@service chatHistory;
@service site;
@service modal;
@tracked persistedNotificationLevel = true;
closeThreadTitle = I18n.t("chat.thread.close");
get backLink() {
const prevPage = this.chatHistory.previousRoute?.name;
@ -35,15 +18,15 @@ export default class ChatThreadHeader extends Component {
if (prevPage === "chat.channel.threads") {
route = "chat.channel.threads";
title = I18n.t("chat.return_to_threads_list");
models = this.args.channel.routeModels;
models = this.channel?.routeModels;
} else if (prevPage === "chat.channel.index" && !this.site.mobileView) {
route = "chat.channel.threads";
title = I18n.t("chat.return_to_threads_list");
models = this.args.channel.routeModels;
models = this.channel?.routeModels;
} else if (!this.currentUser.isInDoNotDisturb() && this.unreadCount > 0) {
route = "chat.channel.threads";
title = I18n.t("chat.return_to_threads_list");
models = this.args.channel.routeModels;
models = this.channel?.routeModels;
} else if (prevPage === "chat.threads") {
route = "chat.threads";
title = I18n.t("chat.my_threads.title");
@ -51,29 +34,14 @@ export default class ChatThreadHeader extends Component {
} else {
route = "chat.channel.index";
title = I18n.t("chat.return_to_channel");
models = this.args.channel.routeModels;
models = this.channel?.routeModels;
}
return { route, models, title };
}
get canChangeThreadSettings() {
if (!this.args.thread) {
return false;
}
return (
this.currentUser.staff ||
this.currentUser.id === this.args.thread.originalMessage.user.id
);
}
get threadNotificationLevel() {
return this.membership?.notificationLevel || NotificationLevels.REGULAR;
}
get membership() {
return this.args.thread.currentUserMembership;
get channel() {
return this.args.thread?.channel;
}
get headerTitle() {
@ -81,97 +49,28 @@ export default class ChatThreadHeader extends Component {
}
get unreadCount() {
return this.args.channel.threadsManager.unreadThreadCount;
}
@action
openThreadSettings() {
this.modal.show(ChatModalThreadSettings, { model: this.args.thread });
}
@action
updateThreadNotificationLevel(newNotificationLevel) {
this.persistedNotificationLevel = false;
let currentNotificationLevel;
if (this.membership) {
currentNotificationLevel = this.membership.notificationLevel;
this.membership.notificationLevel = newNotificationLevel;
} else {
this.args.thread.currentUserMembership = UserChatThreadMembership.create({
notification_level: newNotificationLevel,
last_read_message_id: null,
});
}
return this.chatApi
.updateCurrentUserThreadNotificationsSettings(
this.args.thread.channel.id,
this.args.thread.id,
{ notificationLevel: newNotificationLevel }
)
.then((response) => {
this.membership.last_read_message_id =
response.membership.last_read_message_id;
this.persistedNotificationLevel = true;
})
.catch((err) => {
this.membership.notificationLevel = currentNotificationLevel;
popupAjaxError(err);
});
return this.channel?.threadsManager?.unreadThreadCount;
}
<template>
<div class="chat-thread-header">
<div class="chat-thread-header__left-buttons">
<Navbar as |navbar|>
{{#if @thread}}
<LinkTo
class="chat-thread__back-to-previous-route btn-flat btn btn-icon no-text"
<navbar.BackButton
@route={{this.backLink.route}}
@models={{this.backLink.models}}
title={{this.backLink.title}}
@routeModels={{this.backLink.models}}
@title={{this.backLink.title}}
>
<ChatThreadHeaderUnreadIndicator @channel={{@thread.channel}} />
<ChatThreadHeaderUnreadIndicator @channel={{this.channel}} />
{{icon "chevron-left"}}
</LinkTo>
</navbar.BackButton>
{{/if}}
</div>
<span class="chat-thread-header__label overflow-ellipsis">
{{replaceEmoji this.headerTitle}}
</span>
<div
class={{concatClass
"chat-thread-header__buttons"
(if this.persistedNotificationLevel "-persisted")
}}
>
<ThreadNotificationsButton
@value={{this.threadNotificationLevel}}
@onChange={{this.updateThreadNotificationLevel}}
/>
{{#if this.canChangeThreadSettings}}
<DButton
@action={{this.openThreadSettings}}
@icon="cog"
@title="chat.thread.settings"
class="btn-flat chat-thread-header__settings"
/>
{{/if}}
{{#unless this.site.mobileView}}
<LinkTo
class="chat-thread__close btn-flat btn btn-icon no-text"
@route="chat.channel"
@models={{@thread.channel.routeModels}}
title={{this.closeThreadTitle}}
>
{{icon "times"}}
</LinkTo>
{{/unless}}
</div>
</div>
<navbar.Title @title={{replaceEmoji this.headerTitle}} />
<navbar.Actions as |action|>
<action.ThreadTrackingDropdown @thread={{@thread}} />
<action.ThreadSettingsButton @thread={{@thread}} />
<action.CloseThreadButton @thread={{@thread}} />
</navbar.Actions>
</Navbar>
</template>
}

View File

@ -1,28 +0,0 @@
import Component from "@glimmer/component";
import { LinkTo } from "@ember/routing";
import concatClass from "discourse/helpers/concat-class";
import icon from "discourse-common/helpers/d-icon";
import I18n from "I18n";
import ThreadHeaderUnreadIndicator from "discourse/plugins/chat/discourse/components/chat/thread/header-unread-indicator";
export default class ThreadsListButton extends Component {
threadsListLabel = I18n.t("chat.threads.list");
<template>
<LinkTo
@route="chat.channel.threads"
@models={{@channel.routeModels}}
title={{this.threadsListLabel}}
class={{concatClass
"chat-threads-list-button"
"btn"
"btn-flat"
(if @channel.threadsManager.unreadThreadCount "has-unreads")
}}
>
{{icon "discourse-threads"}}
<ThreadHeaderUnreadIndicator @channel={{@channel}} />
</LinkTo>
</template>
}

View File

@ -1,20 +0,0 @@
import Component from "@glimmer/component";
import icon from "discourse-common/helpers/d-icon";
import i18n from "discourse-common/helpers/i18n";
import Navbar from "discourse/plugins/chat/discourse/components/navbar";
import UserThreads from "discourse/plugins/chat/discourse/components/user-threads";
export default class ChatThreads extends Component {
<template>
<div class="chat-threads">
<Navbar>
<:current>
{{icon "discourse-threads"}}
{{i18n "chat.my_threads.title"}}
</:current>
</Navbar>
<UserThreads />
</div>
</template>
}

View File

@ -1,44 +0,0 @@
import Component from "@glimmer/component";
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import DButton from "discourse/components/d-button";
import DiscourseURL from "discourse/lib/url";
export default class ChatNavbar extends Component {
@service chatStateManager;
@action
async closeFullScreen() {
this.chatStateManager.prefersDrawer();
try {
await DiscourseURL.routeTo(this.chatStateManager.lastKnownAppURL);
await DiscourseURL.routeTo(this.chatStateManager.lastKnownChatURL);
} catch (error) {
await DiscourseURL.routeTo("/");
}
}
<template>
<div class="chat-navbar-container">
<nav class="chat-navbar">
{{#if (has-block "current")}}
<span class="chat-navbar__current">
{{yield to="current"}}
</span>
{{/if}}
<ul class="chat-navbar__right-actions">
<li class="chat-navbar__right-action">
<DButton
@icon="discourse-compress"
@title="chat.close_full_page"
class="open-drawer-btn btn-flat"
@action={{this.closeFullScreen}}
/>
</li>
</ul>
</nav>
</div>
</template>
}

View File

@ -1,14 +1,9 @@
import Component from "@glimmer/component";
import { cached } from "@glimmer/tracking";
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import { modifier } from "ember-modifier";
import ConditionalLoadingSpinner from "discourse/components/conditional-loading-spinner";
import isElementInViewport from "discourse/lib/is-element-in-viewport";
import { INPUT_DELAY } from "discourse-common/config/environment";
import discourseDebounce from "discourse-common/lib/debounce";
import { bind } from "discourse-common/utils/decorators";
import ChannelTitle from "discourse/plugins/chat/discourse/components/channel-title";
import List from "discourse/plugins/chat/discourse/components/chat/list";
import ThreadIndicator from "discourse/plugins/chat/discourse/components/chat-message-thread-indicator";
import ThreadTitle from "discourse/plugins/chat/discourse/components/thread-title";
import ChatChannel from "discourse/plugins/chat/discourse/models/chat-channel";
@ -17,45 +12,12 @@ import ChatThread from "discourse/plugins/chat/discourse/models/chat-thread";
export default class UserThreads extends Component {
@service chat;
@service chatApi;
@service router;
loadMore = modifier((element) => {
this.intersectionObserver = new IntersectionObserver(this.loadThreads);
this.intersectionObserver.observe(element);
return () => {
this.intersectionObserver.disconnect();
};
});
fill = modifier((element) => {
this.resizeObserver = new ResizeObserver(() => {
if (isElementInViewport(element)) {
this.loadThreads();
}
});
this.resizeObserver.observe(element);
return () => {
this.resizeObserver.disconnect();
};
});
@cached
get threadsCollection() {
return this.chatApi.userThreads(this.handleLoadedThreads);
}
@action
loadThreads() {
discourseDebounce(this, this.debouncedLoadThreads, INPUT_DELAY);
}
async debouncedLoadThreads() {
await this.threadsCollection.load({ limit: 10 });
}
@bind
handleLoadedThreads(result) {
return result.threads.map((threadObject) => {
@ -71,12 +33,15 @@ export default class UserThreads extends Component {
}
<template>
<div class="c-user-threads" {{this.fill}}>
{{#each this.threadsCollection.items as |thread|}}
<List
@collection={{this.threadsCollection}}
class="c-user-threads"
as |list|
>
<list.Item as |thread|>
<div class="c-user-thread" data-id={{thread.id}}>
<ThreadTitle @thread={{thread}} />
<ChannelTitle @channel={{thread.channel}} />
<ThreadIndicator
@message={{thread.originalMessage}}
@interactiveUser={{false}}
@ -84,16 +49,7 @@ export default class UserThreads extends Component {
tabindex="-1"
/>
</div>
{{/each}}
<div {{this.loadMore}}>
<br />
</div>
<ConditionalLoadingSpinner
@condition={{this.threadsCollection.loading}}
/>
</div>
</list.Item>
</List>
</template>
}

View File

@ -94,12 +94,16 @@ export default class ChatApi extends Service {
*
* this.chatApi.channels.then(channels => { ... })
*/
channels() {
return new Collection(`${this.#basePath}/channels`, (response) => {
channels(params = {}) {
return new Collection(
`${this.#basePath}/channels`,
(response) => {
return response.channels.map((channel) =>
this.chatChannelsManager.store(channel)
);
});
},
params
);
}
/**

View File

@ -1,15 +1,15 @@
import { tracked } from "@glimmer/tracking";
import Service, { inject as service } from "@ember/service";
import ChatDrawerChannel from "discourse/plugins/chat/discourse/components/chat-drawer/channel";
import ChatDrawerChannelThreads from "discourse/plugins/chat/discourse/components/chat-drawer/channel-threads";
import ChatDrawerIndex from "discourse/plugins/chat/discourse/components/chat-drawer/index";
import ChatDrawerThread from "discourse/plugins/chat/discourse/components/chat-drawer/thread";
import ChatDrawerThreads from "discourse/plugins/chat/discourse/components/chat-drawer/threads";
import ChatDrawerRoutesChannel from "discourse/plugins/chat/discourse/components/chat/drawer-routes/channel";
import ChatDrawerRoutesChannelThread from "discourse/plugins/chat/discourse/components/chat/drawer-routes/channel-thread";
import ChatDrawerRoutesChannelThreads from "discourse/plugins/chat/discourse/components/chat/drawer-routes/channel-threads";
import ChatDrawerRoutesChannels from "discourse/plugins/chat/discourse/components/chat/drawer-routes/channels";
import ChatDrawerRoutesThreads from "discourse/plugins/chat/discourse/components/chat/drawer-routes/threads";
const ROUTES = {
"chat.channel": { name: ChatDrawerChannel },
"chat.channel": { name: ChatDrawerRoutesChannel },
"chat.channel.thread": {
name: ChatDrawerThread,
name: ChatDrawerRoutesChannelThread,
extractParams: (route) => {
return {
channelId: route.parent.params.channelId,
@ -18,7 +18,7 @@ const ROUTES = {
},
},
"chat.channel.thread.index": {
name: ChatDrawerThread,
name: ChatDrawerRoutesChannelThread,
extractParams: (route) => {
return {
channelId: route.parent.params.channelId,
@ -27,7 +27,7 @@ const ROUTES = {
},
},
"chat.channel.thread.near-message": {
name: ChatDrawerThread,
name: ChatDrawerRoutesChannelThread,
extractParams: (route) => {
return {
channelId: route.parent.parent.params.channelId,
@ -37,7 +37,7 @@ const ROUTES = {
},
},
"chat.channel.threads": {
name: ChatDrawerChannelThreads,
name: ChatDrawerRoutesChannelThreads,
extractParams: (route) => {
return {
channelId: route.parent.params.channelId,
@ -45,11 +45,11 @@ const ROUTES = {
},
},
"chat.threads": {
name: ChatDrawerThreads,
name: ChatDrawerRoutesThreads,
},
chat: { name: ChatDrawerIndex },
chat: { name: ChatDrawerRoutesChannels },
"chat.channel.near-message": {
name: ChatDrawerChannel,
name: ChatDrawerRoutesChannel,
extractParams: (route) => {
return {
channelId: route.parent.params.channelId,
@ -58,7 +58,7 @@ const ROUTES = {
},
},
"chat.channel-legacy": {
name: ChatDrawerChannel,
name: ChatDrawerRoutesChannel,
extractParams: (route) => {
return {
channelId: route.params.channelId,
@ -83,7 +83,7 @@ export default class ChatDrawerRouter extends Service {
this.drawerRoute = ROUTES[route.name];
this.params = this.drawerRoute?.extractParams?.(route) || route.params;
this.component = this.drawerRoute?.name || ChatDrawerIndex;
this.component = this.drawerRoute?.name || ChatDrawerRoutesChannels;
this.drawerRoute.activate?.(route);
}

View File

@ -1,5 +1,5 @@
import { tracked } from "@glimmer/tracking";
import { computed } from "@ember/object";
import { action, computed } from "@ember/object";
import { and } from "@ember/object/computed";
import { cancel, next } from "@ember/runloop";
import Service, { inject as service } from "@ember/service";
@ -414,4 +414,13 @@ export default class Chat extends Service {
"Use the new chat API `api.registerChatComposerButton` instead of `chat.addToolbarButton`"
);
}
@action
toggleDrawer() {
this.chatStateManager.didToggleDrawer();
this.appEvents.trigger(
"chat:toggle-expand",
this.chatStateManager.isDrawerExpanded
);
}
}

View File

@ -1 +1 @@
<ChatBrowseView @status="all" />
<Chat::Routes::Browse @status="all" />

View File

@ -1 +1 @@
<ChatBrowseView @status="archived" />
<Chat::Routes::Browse @status="archived" />

View File

@ -1 +1 @@
<ChatBrowseView @status="closed" />
<Chat::Routes::Browse @status="closed" />

View File

@ -1 +1 @@
<ChatBrowseView @status="open" />
<Chat::Routes::Browse @status="open" />

View File

@ -1 +1 @@
<ChatChannelMembers @channel={{this.model}} />
<Chat::Routes::ChannelInfoMembers @channel={{this.model}} />

View File

@ -1 +1 @@
<ChatChannelSettings @channel={{this.model}} />
<Chat::Routes::ChannelInfoSettings @channel={{this.model}} />

View File

@ -1 +1 @@
<ChatChannelInfo @channel={{this.model}} />
<Chat::Routes::ChannelInfo @channel={{this.model}} />

View File

@ -1,7 +1,4 @@
{{#each (array this.model) as |thread|}}
<ChatThread
@thread={{thread}}
<Chat::Routes::ChannelThread
@thread={{this.model}}
@targetMessageId={{this.targetMessageId}}
@includeHeader={{true}}
/>
{{/each}}
/>

View File

@ -1 +1 @@
<ChatThreadList @channel={{this.model}} @includeHeader={{true}} />
<Chat::Routes::ChannelThreads @channel={{this.model}} />

View File

@ -1,8 +1,4 @@
<FullPageChat
<Chat::Routes::Channel
@channel={{this.model}}
@targetMessageId={{this.targetMessageId}}
/>
<ChatSidePanel>
{{outlet}}
</ChatSidePanel>

View File

@ -1 +0,0 @@
<ChatDraftChannelScreen />

View File

@ -1 +1 @@
<Chat::Threads />
<Chat::Routes::Threads />

View File

@ -3,7 +3,7 @@
--full-page-border-radius: 12px;
--full-page-sidebar-width: 275px;
--channel-list-avatar-size: 30px;
--chat-header-offset: 50px;
--chat-header-offset: 46px;
}
// Very specific hack to ensure the contextual menu (copy/paste/...) is
@ -198,40 +198,9 @@ body.has-full-page-chat {
grid-template-columns: var(--full-page-sidebar-width) 1fr;
background: var(--d-content-background);
.chat-full-page-header {
border-bottom: 1px solid var(--primary-low);
background: var(--secondary);
z-index: 3;
display: flex;
align-items: center;
&__back-btn {
width: 40px;
min-width: 40px;
display: flex;
align-items: center;
justify-content: center;
}
.chat-channel-title {
.category-chat-name,
.chat-name,
.dm-usernames {
color: var(--primary);
display: inline;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.-not-following {
.chat-channel-title {
max-width: calc(100% - 50px);
}
.join-channel-btn {
margin-left: auto;
}
}
.c-navbar-container {
position: sticky;
top: var(--header-offset);
}
.chat-messages-scroll {
@ -240,67 +209,6 @@ body.has-full-page-chat {
}
}
.chat-full-page-header__left-actions {
display: flex;
align-items: stretch;
}
.chat-full-page-header__title {
display: flex;
align-items: stretch;
}
.chat-full-page-header__right-actions {
align-items: stretch;
display: flex;
flex-grow: 1;
gap: 0.5rem;
font-size: var(--font-up-1);
justify-content: flex-end;
}
.chat-full-page-header {
box-sizing: border-box;
.chat-channel-header-details {
display: flex;
align-items: stretch;
flex: 1;
max-width: 100%;
.chat-channel-archive-status {
text-align: right;
padding-right: 1em;
}
}
.chat-channel-title {
margin: 0;
max-width: 100%;
.d-icon:not(.d-icon-lock) {
height: 1.25em;
width: 1.25em;
}
.category-chat-name,
.dm-username {
font-weight: 700;
font-size: var(--font-up-1);
line-height: var(--font-up-1);
}
.dm-usernames {
overflow: hidden;
text-overflow: ellipsis;
}
}
.chat-channel-retry-archive {
display: flex;
margin-top: 1em;
}
}
.user-preferences .chat-setting .controls {
margin-bottom: 0;
}

View File

@ -7,10 +7,6 @@
display: flex;
align-items: center;
justify-content: flex-start;
.new-channel-btn {
margin-left: auto;
}
}
&__title {

View File

@ -1,6 +1,5 @@
.chat-channel-info {
display: flex;
height: 100%;
padding: 1rem;
flex-direction: column;

View File

@ -1,10 +1,3 @@
//appears in: header of chat pane, channel info, preview card
.chat-channel-title-wrapper {
display: flex;
align-items: center;
overflow: hidden;
}
.chat-channel-title {
display: flex;
align-items: center;

View File

@ -7,6 +7,7 @@ body.composer-open .chat-drawer-outlet-container {
top: -5px;
width: 15px;
height: 15px;
z-index: z("composer", "content");
}
html:not(.rtl) {
@ -30,36 +31,25 @@ html.rtl {
right: var(--composer-right, 20px);
left: 0;
max-height: calc(100% - var(--header-offset) - 15px);
.rtl & {
left: var(--composer-right, 20px);
right: 0;
}
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
pointer-events: none !important;
bottom: 0;
> * {
pointer-events: auto;
}
.no-channel-title {
font-weight: bold;
margin-left: 0.5rem;
}
&.composer-draft-collapsed {
bottom: 40px;
}
box-sizing: border-box;
padding-bottom: var(--composer-height, 0);
transition: all 100ms ease-in;
transition-property: bottom, padding-bottom;
.rtl & {
left: var(--composer-right, 20px);
right: 0;
}
> * {
pointer-events: auto;
}
}
.chat-drawer {
@ -101,170 +91,11 @@ html.rtl {
}
}
.chat-drawer-header__left-actions {
display: flex;
height: 2rem;
}
.chat-drawer-header__right-actions {
display: flex;
height: 2rem;
margin-left: auto;
}
.chat-drawer-header__top-line {
display: flex;
align-items: center;
}
.chat-drawer-header__bottom-line {
height: 1.5rem;
display: flex;
align-items: start;
}
.chat-drawer-header__title {
@include ellipsis;
display: flex;
width: auto;
font-weight: 700;
padding: 0 0.5rem 0 0;
cursor: pointer;
height: 2rem;
align-items: center;
.chat-drawer-header__top-line {
padding: 0.25rem;
width: 100%;
}
}
a.chat-drawer-header__title {
&:hover {
.chat-drawer-header__top-line {
background: var(--primary-low);
border-radius: var(--d-border-radius);
}
}
}
.chat-drawer-header__icon {
margin-right: 0.25rem;
}
.chat-drawer-header__divider {
margin: 0 0.25rem;
}
.chat-drawer-header {
box-sizing: border-box;
border-bottom: solid 1px var(--primary-low);
border-radius: var(--d-border-radius-large) var(--d-border-radius-large) 0 0;
background: var(--primary-very-low);
width: 100%;
display: flex;
align-items: flex-start;
cursor: pointer;
padding: 0.25rem;
.btn {
height: 100%;
}
.chat-channel-title {
font-weight: 700;
width: 100%;
&__user-info {
overflow: hidden;
}
.chat-name,
.chat-drawer-name,
.category-chat-name,
.dm-usernames {
color: var(--primary);
}
.category-chat-badge,
.chat-drawer-badge {
display: flex;
justify-content: center;
align-content: center;
.d-icon:not(.d-icon-lock) {
width: 1.25em;
height: 1.25em;
}
}
.dm-usernames {
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
}
.d-icon:not(.d-icon-d-chat) {
color: var(--primary-high);
}
.category-hashtag {
padding: 2px 4px;
}
}
&__close-btn,
&__back-btn,
&__full-screen-btn,
&__thread-list-btn,
&__expand-btn {
height: 30px;
width: 30px;
display: flex;
justify-content: center;
align-items: center;
border-radius: 100%;
&:hover:active {
background: var(--primary-low);
}
.d-icon {
color: var(--primary-low-mid);
margin-right: 0;
}
&:visited {
.d-icon {
color: var(--primary-low-mid);
}
}
&:focus {
outline: none;
background: none;
.d-icon {
background: none;
color: var(--primary);
}
}
&:hover {
.d-icon {
color: var(--primary);
}
}
}
&__thread-list-btn.has-unreads {
margin-right: 0.5rem;
}
}
.chat-drawer-content {
@include chat-scrollbar();
box-sizing: border-box;
height: 100%;
min-height: 1px;
padding-bottom: 0.25em;
position: relative;
overflow-y: auto;
overscroll-behavior: contain;

View File

@ -1,8 +1,9 @@
@mixin chat-height($inset: 0px) {
// desktop and mobile
// 46px is the height of the navbar
height: calc(
var(--chat-vh, 1vh) * 100 - var(--header-offset, 0px) -
var(--composer-height, 0px)
var(--composer-height, 0px) - var(--chat-header-offset)
);
// mobile with keyboard opened

View File

@ -26,5 +26,5 @@
}
.full-page-chat .chat-mention-warnings {
top: 4rem;
top: 2rem;
}

View File

@ -1,23 +1,58 @@
.chat-navbar {
flex-shrink: 0;
.c-navbar {
display: flex;
align-items: center;
width: 100%;
gap: 0.25rem;
&-container {
padding-inline: 1rem;
position: sticky;
padding-inline: 0.5rem;
border-bottom: 1px solid var(--primary-low);
background: var(--secondary);
top: var(--header-offset);
height: 50px;
height: var(--chat-header-offset);
min-height: var(--chat-header-offset);
box-sizing: border-box;
display: flex;
z-index: z("header") - 1;
z-index: z("composer", "content") - 1;
&.-clickable {
cursor: pointer;
}
}
.single-select-header {
padding: 0.3675rem 0.584rem;
}
.c-navbar__channel-title {
@include ellipsis();
font-weight: 700;
}
.c-navbar__title {
@include ellipsis();
font-weight: 700;
}
.c-navbar__sub-title {
line-height: var(--line-height-small);
font-size: var(--font-down-1-rem);
font-weight: normal;
}
.c-navbar__threads-list-button {
gap: 0.25rem;
&.has-unreads {
.d-icon-discourse-threads {
color: var(--tertiary);
}
}
}
}
.chat-navbar__right-actions {
.c-navbar__actions {
list-style: none;
margin-left: auto;
display: flex;
align-items: center;
}

View File

@ -18,32 +18,25 @@
align-items: center;
justify-content: space-between;
background: var(--tertiary-low);
padding: 0.5em 0 0.5em 1em;
padding: 0.5rem;
color: var(--primary);
padding: 0.5em 0 0.5em 1em;
min-width: 280px;
margin-left: auto;
margin-right: auto;
gap: 0.25rem;
}
.dismiss-btn {
margin: 0 0.25em;
color: var(--primary-medium);
align-self: flex-start;
&:hover,
&:focus {
background-color: transparent;
.d-icon {
color: var(--primary);
}
}
.d-icon {
color: var(--primary-medium);
background: var(--tertiary-medium);
}
}
}
.full-page-chat .chat-notices {
top: 4rem;
top: 2rem;
}

View File

@ -1,7 +1,6 @@
.chat-side-panel-resizer {
top: 0;
bottom: 0;
position: absolute;
z-index: z("composer", "content") - 1;
transition: background-color 0.15s 0.15s;

View File

@ -1,66 +0,0 @@
@mixin chat-channel-header-button {
color: var(--primary-low-mid);
padding: 0.25em 0.4em;
.d-icon {
color: inherit;
}
&:visited {
color: var(--primary-low-mid);
}
&:hover {
color: var(--primary-medium);
background: var(--primary-very-low);
border-radius: var(--d-border-radius);
&:hover {
.d-icon {
color: inherit;
}
}
}
> * {
pointer-events: none;
}
}
.chat-channel {
.chat-threads-list-button {
@include chat-channel-header-button;
position: relative;
display: flex;
align-items: center;
&.has-unreads {
color: var(--tertiary-med-or-tertiary);
gap: 0.25rem;
&:hover {
color: var(--tertiary-hover);
}
}
.d-icon {
margin-right: 0;
}
&:hover {
.discourse-touch & {
background: none !important;
}
}
&:active {
.discourse-touch & {
background: var(--secondary-very-high) !important;
}
}
}
.open-drawer-btn {
@include chat-channel-header-button;
}
}

View File

@ -1,32 +1,8 @@
.chat-thread-header {
height: var(--chat-header-offset);
min-height: var(--chat-header-offset);
border-bottom: 1px solid var(--primary-low);
border-top: 1px solid var(--primary-low);
box-sizing: border-box;
display: flex;
align-items: center;
padding-inline: 0.5rem;
.touch & {
&__label {
font-size: var(--font-up-1-rem);
}
}
.chat-thread__back-to-previous-route {
padding: 0.5rem 0;
margin-right: 0.5rem;
}
&__buttons {
display: flex;
margin-left: auto;
}
&__left-buttons {
display: flex;
flex-direction: row;
align-items: center;
}
}

View File

@ -1,6 +1,4 @@
.chat-thread-list-header {
height: var(--chat-header-offset);
min-height: var(--chat-header-offset);
border-bottom: 1px solid var(--primary-low);
border-top: 1px solid var(--primary-low);
box-sizing: border-box;

View File

@ -1,46 +0,0 @@
.full-page-chat-header {
display: flex;
padding: 0.25rem;
border-bottom: 1px solid var(--primary-low);
justify-content: space-between;
@include ellipsis;
flex-direction: column;
.chat-channel-info-link {
justify-self: flex-end;
}
}
.full-page-chat-header__about-link {
@include ellipsis;
padding-right: 0.25rem;
.chat-channel-title__name {
font-weight: 700;
}
.chat-channel-title {
padding: 0.5rem 0.5rem 0.25rem 0.5rem;
}
}
.full-page-chat-header__members-link {
padding: 0 0.5rem 0.5rem 0.5rem;
font-size: var(--font-down-1);
color: var(--primary-medium);
&:visited {
color: var(--primary-medium);
}
}
.full-page-chat-header__first-row {
display: flex;
height: 45px;
align-items: center;
}
.full-page-chat-header__second-row {
display: flex;
height: 32px;
align-items: center;
}

View File

@ -1,6 +1,5 @@
@import "chat-unread-indicator";
@import "chat-height-mixin";
@import "chat-thread-header-buttons";
@import "base-common";
@import "sidebar-extensions";
@import "chat-browse";
@ -41,7 +40,6 @@
@import "chat-transcript";
@import "core-extensions";
@import "dc-filter-input";
@import "full-page-chat-header";
@import "incoming-chat-webhooks";
@import "reviewable-chat-message";
@import "chat-thread-list-item";

View File

@ -12,13 +12,6 @@
}
}
.chat-full-page-header {
padding: 0 1rem;
height: var(--chat-header-offset);
min-height: var(--chat-header-offset);
flex-shrink: 0;
}
.chat-channel {
.chat-messages-container {
&.has-reply {

View File

@ -1,14 +0,0 @@
.chat-channel-title-wrapper {
padding: 0.25rem;
&:hover {
background: var(--primary-very-low);
border-radius: var(--d-border-radius);
}
.chat-channel-title {
&__user-info {
overflow: hidden;
}
}
}

View File

@ -1,5 +1,4 @@
@import "base-desktop";
@import "chat-channel-title";
@import "chat-composer-uploads";
@import "chat-index-drawer";
@import "chat-index-full-page";

View File

@ -25,7 +25,7 @@ html.has-full-page-chat {
grid-template-columns: 1fr;
grid-template-areas: "threads";
.chat-channel {
.c-routes-channel {
display: none;
}
}
@ -54,13 +54,6 @@ html.has-full-page-chat {
.chat-drawer {
width: 100%;
}
.chat-full-page-header {
background-color: var(--secondary);
padding: 0 10px;
height: 50px;
min-height: 50px;
}
}
.sidebar-container .channels-list .chat-channel-divider {
@ -77,18 +70,6 @@ html.has-full-page-chat {
}
}
.chat-full-page-header {
.chat-channel-header-details {
.chat-channel-retry-archive {
flex-direction: column;
.chat-channel-archive-failed-retry {
margin-top: 0.5em;
}
}
}
}
.chat-message-separator {
margin-left: 0;
}

View File

@ -67,7 +67,7 @@ RSpec.describe "Browse page", type: :system do
context "when on mobile", mobile: true do
it "has a back button" do
chat_page.visit_browse
find(".chat-full-page-header__back-btn").click
find(".c-navbar__back-button").click
expect(browse_page).to have_current_path("/chat")
end
@ -80,6 +80,7 @@ RSpec.describe "Browse page", type: :system do
context "when results are found" do
it "lists expected results" do
chat_page.visit_browse
browse_page.search(category_channel_1.name)
expect(browse_page).to have_channel(name: category_channel_1.name)

View File

@ -18,7 +18,7 @@ RSpec.describe "Channel - Info - Settings page", type: :system 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
find(".c-navbar__back-button").click
expect(page).to have_current_path("/chat/browse/open")
end
@ -29,8 +29,8 @@ RSpec.describe "Channel - Info - Settings page", type: :system 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
find(".c-navbar__channel-title").click
find(".c-navbar__back-button").click
expect(page).to have_current_path(chat.channel_path(channel_1.slug, channel_1.id))
end

View File

@ -22,7 +22,7 @@ RSpec.describe "Drawer", type: :system do
visit("/")
chat_page.open_from_header
drawer_page.open_channel(channel)
page.find(".chat-channel-title").click
page.find(".c-navbar__channel-title").click
expect(page).to have_current_path("/chat/c/#{channel.slug}/#{channel.id}/info/members")
end
@ -94,7 +94,7 @@ RSpec.describe "Drawer", type: :system do
chat_page.open_from_header
expect(page).to have_selector(".chat-drawer.is-expanded")
page.find(".chat-drawer-header").click
page.find(".c-navbar").click
expect(page).to have_selector(".chat-drawer:not(.is-expanded)")
end

View File

@ -337,7 +337,7 @@ RSpec.describe "Navigation", type: :system do
context "when going back to channel from channel settings in full page" do
it "activates the channel in the sidebar" do
visit("/chat/c/#{category_channel.slug}/#{category_channel.id}/info/settings")
find(".chat-full-page-header__back-btn").click
find(".c-navbar__back-button").click
expect(page).to have_content(message.message)
end
end

View File

@ -93,10 +93,10 @@ module PageObjects
end
def minimize_full_page
find(".open-drawer-btn").click
find(".c-navbar__open-drawer-button").click
end
NEW_CHANNEL_BUTTON_SELECTOR = ".new-channel-btn"
NEW_CHANNEL_BUTTON_SELECTOR = ".c-navbar__new-channel-button"
def new_channel_button
find(NEW_CHANNEL_BUTTON_SELECTOR)

Some files were not shown because too many files have changed in this diff Show More