DEV: Convert components to gjs

- convert chat/channels-list -> gjs
- convert chat/chat-channel-archive-status -> gjs
- convert chat/chat-channel-card -> gjs
- convert chat/chat-channel-leave-btn -> gjs
- convert chat/chat-channel-metadata -> gjs
- convert chat/chat-channel-preview-card -> gjs
- convert chat/chat-channel-status -> gjs
- convert chat/chat-channel-unread-indicator -> gjs
- convert chat/chat-composer-dropdown -> gjs
- convert chat/chat-composer-message-details -> gjs
- convert chat/chat-composer-upload -> gjs
- convert chat/channel -> gjs
- convert chat/header -> gjs
- convert chat/back-link -> gjs
- convert chat/channel-title -> gjs
- convert chat/close-button -> gjs
- convert chat/full-page-button -> gjs
- convert chat/left-actions -> gjs
- convert chat/right-actions -> gjs
- convert chat/toggle-expand-button -> gjs
- convert chat/index -> gjs
- convert chat/chat-emoji-avatar -> gjs
- convert chat/chat-mention-warnings -> gjs
- convert chat/chat-message-in-reply-to-indicator -> gjs
- convert chat/chat-message-separator-date -> gjs
- convert chat/chat-message-separator-new -> gjs
- convert chat/chat-message-text -> gjs
- convert chat/chat-message-thread-indicator -> gjs
- convert chat/chat-notice -> gjs
- convert chat/chat-notices -> gjs
- convert chat/chat-replying-indicator -> gjs
- convert chat/chat-side-panel-resizer -> gjs
- convert chat/chat-side-panel -> gjs
- convert chat/chat-skeleton -> gjs
- convert chat/chat-upload-drop-zone -> gjs
- convert chat/chat-upload -> gjs
- convert chat/chat-user-display-name -> gjs
- convert chat/export-messages -> gjs
- convert chat/button -> gjs
- convert chat/separator -> gjs
- convert chat/avatar -> gjs
- convert chat/error -> gjs
- convert chat/info -> gjs
- convert chat/left-gutter -> gjs
- convert chat/archive-channel -> gjs
- convert chat/channel-summary -> gjs
- convert chat/delete-channel -> gjs
- convert chat/edit-channel-name -> gjs
- convert chat/move-message-to-channel -> gjs
- convert chat/thread-settings -> gjs
- convert chat/toggle-channel-status -> gjs
- convert chat/mention_without_membership -> gjs
- convert chat/scroll-to-bottom-arrow -> gjs
- convert chat/item -> gjs
- convert chat/unread-indicator -> gjs
- convert chat/user-card-button -> gjs
- convert chat/full-page-chat -> gjs
- convert chat/reviewable-chat-message -> gjs
- convert chat/chat -> gjs
- convert chat/toggle-channel-membership-button -> gjs
- convert chat/chat-preferences -> gjs
This commit is contained in:
Godfrey Chan 2023-11-13 09:52:52 -08:00 committed by David Taylor
parent e1f74fcb4b
commit 3a0cdd2734
122 changed files with 1911 additions and 1476 deletions

View File

@ -1,10 +1,24 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking"; import { tracked } from "@glimmer/tracking";
import { fn, hash } from "@ember/helper";
import { on } from "@ember/modifier";
import { action } from "@ember/object"; import { action } from "@ember/object";
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import { LinkTo } from "@ember/routing";
import { schedule } from "@ember/runloop"; import { schedule } from "@ember/runloop";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import DButton from "discourse/components/d-button";
import PluginOutlet from "discourse/components/plugin-outlet";
import concatClass from "discourse/helpers/concat-class";
import noop from "discourse/helpers/noop";
import dIcon from "discourse-common/helpers/d-icon";
import i18n from "discourse-common/helpers/i18n";
import { bind } from "discourse-common/utils/decorators"; import { bind } from "discourse-common/utils/decorators";
import and from "truth-helpers/helpers/and";
import not from "truth-helpers/helpers/not";
import ChatModalNewMessage from "discourse/plugins/chat/discourse/components/chat/modal/new-message"; import ChatModalNewMessage from "discourse/plugins/chat/discourse/components/chat/modal/new-message";
import onResize from "../modifiers/chat/on-resize";
import ChatChannelRow from "./chat-channel-row";
export default class ChannelsList extends Component { export default class ChannelsList extends Component {
@service chat; @service chat;
@ -119,4 +133,147 @@ export default class ChannelsList extends Component {
const scroller = document.querySelector(".channels-list"); const scroller = document.querySelector(".channels-list");
scroller.scrollTo(0, position); scroller.scrollTo(0, position);
} }
<template>
{{#if this.showMobileDirectMessageButton}}
<DButton
@icon="plus"
class="no-text btn-flat open-new-message-btn keep-mobile-sidebar-open btn-floating"
@action={{this.openNewMessageModal}}
title={{i18n this.createDirectMessageChannelLabel}}
/>
{{/if}}
<div
role="region"
aria-label={{i18n "chat.aria_roles.channels_list"}}
class={{concatClass
"channels-list"
(if this.hasScrollbar "has-scrollbar")
}}
{{on
"scroll"
(if
this.chatStateManager.isFullPageActive this.storeScrollPosition (noop)
)
}}
{{didInsert this.computeHasScrollbar}}
{{onResize this.computeResizedEntries}}
>
{{#if this.displayPublicChannels}}
<div class="chat-channel-divider public-channels-section">
{{#if this.inSidebar}}
<span
class="title-caret"
id="public-channels-caret"
role="button"
title="toggle nav list"
{{on "click" (fn this.toggleChannelSection "public-channels")}}
data-toggleable="public-channels"
>
{{dIcon "angle-up"}}
</span>
{{/if}}
<span class="channel-title">{{i18n "chat.chat_channels"}}</span>
<LinkTo
@route="chat.browse"
class="btn no-text btn-flat open-browse-page-btn title-action"
title={{i18n "chat.channels_list_popup.browse"}}
>
{{dIcon "pencil-alt"}}
</LinkTo>
</div>
<div
id="public-channels"
class={{concatClass
"channels-list-container"
"public-channels"
(if this.inSidebar "collapsible-sidebar-section")
}}
>
{{#if this.publicChannelsEmpty}}
<div class="public-channel-empty-message">
<span class="channel-title">{{i18n
"chat.no_public_channels"
}}</span>
<LinkTo @route="chat.browse">
{{i18n "chat.click_to_join"}}
</LinkTo>
</div>
{{else}}
{{#each
this.chatChannelsManager.publicMessageChannels
as |channel|
}}
<ChatChannelRow
@channel={{channel}}
@options={{hash settingsButton=true}}
/>
{{/each}}
{{/if}}
</div>
{{/if}}
<PluginOutlet
@name="below-public-chat-channels"
@tagName=""
@outletArgs={{hash inSidebar=this.inSidebar}}
/>
{{#if this.showDirectMessageChannels}}
<div class="chat-channel-divider direct-message-channels-section">
{{#if this.inSidebar}}
<span
class="title-caret"
id="direct-message-channels-caret"
role="button"
title="toggle nav list"
{{on
"click"
(fn this.toggleChannelSection "direct-message-channels")
}}
data-toggleable="direct-message-channels"
>
{{dIcon "angle-up"}}
</span>
{{/if}}
<span class="channel-title">{{i18n
"chat.direct_messages.title"
}}</span>
{{#if
(and
this.canCreateDirectMessageChannel
(not this.showMobileDirectMessageButton)
)
}}
<DButton
@icon="plus"
class="no-text btn-flat open-new-message-btn"
@action={{this.openNewMessageModal}}
title={{i18n this.createDirectMessageChannelLabel}}
/>
{{/if}}
</div>
{{/if}}
<div
id="direct-message-channels"
class={{this.directMessageChannelClasses}}
>
{{#each
this.chatChannelsManager.truncatedDirectMessageChannels
as |channel|
}}
<ChatChannelRow
@channel={{channel}}
@options={{hash leaveButton=true}}
/>
{{/each}}
</div>
</div>
</template>
} }

View File

@ -1,122 +0,0 @@
{{#if this.showMobileDirectMessageButton}}
<DButton
@icon="plus"
class="no-text btn-flat open-new-message-btn keep-mobile-sidebar-open btn-floating"
@action={{this.openNewMessageModal}}
title={{i18n this.createDirectMessageChannelLabel}}
/>
{{/if}}
<div
role="region"
aria-label={{i18n "chat.aria_roles.channels_list"}}
class={{concat-class "channels-list" (if this.hasScrollbar "has-scrollbar")}}
{{on
"scroll"
(if this.chatStateManager.isFullPageActive this.storeScrollPosition (noop))
}}
{{did-insert this.computeHasScrollbar}}
{{chat/on-resize this.computeResizedEntries}}
>
{{#if this.displayPublicChannels}}
<div class="chat-channel-divider public-channels-section">
{{#if this.inSidebar}}
<span
class="title-caret"
id="public-channels-caret"
role="button"
title="toggle nav list"
{{on "click" (fn this.toggleChannelSection "public-channels")}}
data-toggleable="public-channels"
>
{{d-icon "angle-up"}}
</span>
{{/if}}
<span class="channel-title">{{i18n "chat.chat_channels"}}</span>
<LinkTo
@route="chat.browse"
class="btn no-text btn-flat open-browse-page-btn title-action"
title={{i18n "chat.channels_list_popup.browse"}}
>
{{d-icon "pencil-alt"}}
</LinkTo>
</div>
<div
id="public-channels"
class={{concat-class
"channels-list-container"
"public-channels"
(if this.inSidebar "collapsible-sidebar-section")
}}
>
{{#if this.publicChannelsEmpty}}
<div class="public-channel-empty-message">
<span class="channel-title">{{i18n "chat.no_public_channels"}}</span>
<LinkTo @route="chat.browse">
{{i18n "chat.click_to_join"}}
</LinkTo>
</div>
{{else}}
{{#each this.chatChannelsManager.publicMessageChannels as |channel|}}
<ChatChannelRow
@channel={{channel}}
@options={{hash settingsButton=true}}
/>
{{/each}}
{{/if}}
</div>
{{/if}}
<PluginOutlet
@name="below-public-chat-channels"
@tagName=""
@outletArgs={{hash inSidebar=this.inSidebar}}
/>
{{#if this.showDirectMessageChannels}}
<div class="chat-channel-divider direct-message-channels-section">
{{#if this.inSidebar}}
<span
class="title-caret"
id="direct-message-channels-caret"
role="button"
title="toggle nav list"
{{on
"click"
(fn this.toggleChannelSection "direct-message-channels")
}}
data-toggleable="direct-message-channels"
>
{{d-icon "angle-up"}}
</span>
{{/if}}
<span class="channel-title">{{i18n "chat.direct_messages.title"}}</span>
{{#if
(and
this.canCreateDirectMessageChannel
(not this.showMobileDirectMessageButton)
)
}}
<DButton
@icon="plus"
class="no-text btn-flat open-new-message-btn"
@action={{this.openNewMessageModal}}
title={{i18n this.createDirectMessageChannelLabel}}
/>
{{/if}}
</div>
{{/if}}
<div id="direct-message-channels" class={{this.directMessageChannelClasses}}>
{{#each
this.chatChannelsManager.truncatedDirectMessageChannels
as |channel|
}}
<ChatChannelRow @channel={{channel}} @options={{hash leaveButton=true}} />
{{/each}}
</div>
</div>

View File

@ -3,6 +3,8 @@ import { action } from "@ember/object";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import { htmlSafe } from "@ember/template"; import { htmlSafe } from "@ember/template";
import { isPresent } from "@ember/utils"; import { isPresent } from "@ember/utils";
import DButton from "discourse/components/d-button";
import concatClass from "discourse/helpers/concat-class";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
import getURL from "discourse-common/lib/get-url"; import getURL from "discourse-common/lib/get-url";
import I18n from "discourse-i18n"; import I18n from "discourse-i18n";
@ -50,4 +52,34 @@ export default class ChatChannelArchiveStatus extends Component {
} }
return getURL(`/t/-/${this.args.channel.archive.topicId}`); return getURL(`/t/-/${this.args.channel.archive.topicId}`);
} }
<template>
{{#if this.shouldRender}}
{{#if @channel.archive.failed}}
<div
class={{concatClass
"alert alert-warn chat-channel-retry-archive"
@channel.status
}}
>
<div class="chat-channel-archive-failed-message">
{{this.channelArchiveFailedMessage}}
</div>
<div class="chat-channel-archive-failed-retry">
<DButton
@action={{this.retryArchive}}
@label="chat.channel_archive.retry"
/>
</div>
</div>
{{else if @channel.archive.completed}}
<div
class={{concatClass "chat-channel-archive-status" @channel.status}}
>
{{this.channelArchiveCompletedMessage}}
</div>
{{/if}}
{{/if}}
</template>
} }

View File

@ -1,25 +0,0 @@
{{#if this.shouldRender}}
{{#if @channel.archive.failed}}
<div
class={{concat-class
"alert alert-warn chat-channel-retry-archive"
@channel.status
}}
>
<div class="chat-channel-archive-failed-message">
{{this.channelArchiveFailedMessage}}
</div>
<div class="chat-channel-archive-failed-retry">
<DButton
@action={{this.retryArchive}}
@label="chat.channel_archive.retry"
/>
</div>
</div>
{{else if @channel.archive.completed}}
<div class={{concat-class "chat-channel-archive-status" @channel.status}}>
{{this.channelArchiveCompletedMessage}}
</div>
{{/if}}
{{/if}}

View File

@ -1,6 +1,112 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import { hash } from "@ember/helper";
import { LinkTo } from "@ember/routing";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import borderColor from "discourse/helpers/border-color";
import concatClass from "discourse/helpers/concat-class";
import replaceEmoji from "discourse/helpers/replace-emoji";
import dIcon from "discourse-common/helpers/d-icon";
import i18n from "discourse-common/helpers/i18n";
import gt from "truth-helpers/helpers/gt";
import ToggleChannelMembershipButton from "./toggle-channel-membership-button";
export default class ChatChannelCard extends Component { export default class ChatChannelCard extends Component {
@service chat; @service chat;
<template>
{{#if @channel}}
<div
class={{concatClass
"chat-channel-card"
(if @channel.isClosed "-closed")
(if @channel.isArchived "-archived")
}}
style={{borderColor @channel.chatable.color}}
data-channel-id={{@channel.id}}
>
<div class="chat-channel-card__header">
<LinkTo
@route="chat.channel"
@models={{@channel.routeModels}}
class="chat-channel-card__name-container"
>
<span class="chat-channel-card__name">
{{replaceEmoji @channel.title}}
</span>
{{#if @channel.chatable.read_restricted}}
{{dIcon "lock" class="chat-channel-card__read-restricted"}}
{{/if}}
</LinkTo>
<div class="chat-channel-card__header-actions">
{{#if @channel.currentUserMembership.muted}}
<LinkTo
@route="chat.channel.info.settings"
@models={{@channel.routeModels}}
class="chat-channel-card__tag -muted"
tabindex="-1"
>
{{i18n "chat.muted"}}
</LinkTo>
{{/if}}
<LinkTo
@route="chat.channel.info.settings"
@models={{@channel.routeModels}}
class="chat-channel-card__setting"
tabindex="-1"
>
{{dIcon "cog"}}
</LinkTo>
</div>
</div>
{{#if @channel.description}}
<div class="chat-channel-card__description">
{{replaceEmoji @channel.description}}
</div>
{{/if}}
<div class="chat-channel-card__cta">
{{#if @channel.isFollowing}}
<div class="chat-channel-card__tags">
<span class="chat-channel-card__tag -joined">
{{i18n "chat.joined"}}
</span>
<ToggleChannelMembershipButton
@channel={{@channel}}
@options={{hash
leaveClass="btn-link btn-small chat-channel-card__leave-btn"
labelType="short"
}}
/>
</div>
{{else if @channel.isJoinable}}
<ToggleChannelMembershipButton
@channel={{@channel}}
@options={{hash
joinClass="btn-primary btn-small chat-channel-card__join-btn"
labelType="short"
}}
/>
{{/if}}
{{#if (gt @channel.membershipsCount 0)}}
<LinkTo
@route="chat.channel.info.members"
@models={{@channel.routeModels}}
class="chat-channel-card__members"
tabindex="-1"
>
{{i18n
"chat.channel.memberships_count"
count=@channel.membershipsCount
}}
</LinkTo>
{{/if}}
</div>
</div>
{{/if}}
</template>
} }

View File

@ -1,94 +0,0 @@
{{#if @channel}}
<div
class={{concat-class
"chat-channel-card"
(if @channel.isClosed "-closed")
(if @channel.isArchived "-archived")
}}
style={{border-color @channel.chatable.color}}
data-channel-id={{@channel.id}}
>
<div class="chat-channel-card__header">
<LinkTo
@route="chat.channel"
@models={{@channel.routeModels}}
class="chat-channel-card__name-container"
>
<span class="chat-channel-card__name">
{{replace-emoji @channel.title}}
</span>
{{#if @channel.chatable.read_restricted}}
{{d-icon "lock" class="chat-channel-card__read-restricted"}}
{{/if}}
</LinkTo>
<div class="chat-channel-card__header-actions">
{{#if @channel.currentUserMembership.muted}}
<LinkTo
@route="chat.channel.info.settings"
@models={{@channel.routeModels}}
class="chat-channel-card__tag -muted"
tabindex="-1"
>
{{i18n "chat.muted"}}
</LinkTo>
{{/if}}
<LinkTo
@route="chat.channel.info.settings"
@models={{@channel.routeModels}}
class="chat-channel-card__setting"
tabindex="-1"
>
{{d-icon "cog"}}
</LinkTo>
</div>
</div>
{{#if @channel.description}}
<div class="chat-channel-card__description">
{{replace-emoji @channel.description}}
</div>
{{/if}}
<div class="chat-channel-card__cta">
{{#if @channel.isFollowing}}
<div class="chat-channel-card__tags">
<span class="chat-channel-card__tag -joined">
{{i18n "chat.joined"}}
</span>
<ToggleChannelMembershipButton
@channel={{@channel}}
@options={{hash
leaveClass="btn-link btn-small chat-channel-card__leave-btn"
labelType="short"
}}
/>
</div>
{{else if @channel.isJoinable}}
<ToggleChannelMembershipButton
@channel={{@channel}}
@options={{hash
joinClass="btn-primary btn-small chat-channel-card__join-btn"
labelType="short"
}}
/>
{{/if}}
{{#if (gt @channel.membershipsCount 0)}}
<LinkTo
@route="chat.channel.info.members"
@models={{@channel.routeModels}}
class="chat-channel-card__members"
tabindex="-1"
>
{{i18n
"chat.channel.memberships_count"
count=@channel.membershipsCount
}}
</LinkTo>
{{/if}}
</div>
</div>
{{/if}}

View File

@ -1,6 +1,7 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import { isPresent } from "@ember/utils"; import { isPresent } from "@ember/utils";
import DButton from "discourse/components/d-button";
export default class ChatChannelLeaveBtn extends Component { export default class ChatChannelLeaveBtn extends Component {
@service chat; @service chat;
@service site; @service site;
@ -16,4 +17,15 @@ export default class ChatChannelLeaveBtn extends Component {
return "chat.leave"; return "chat.leave";
} }
} }
<template>
{{#if this.shouldRender}}
<DButton
@icon="times"
@action={{@onLeaveChannel}}
@title={{this.leaveChatTitleKey}}
class="btn-flat chat-channel-leave-btn"
/>
{{/if}}
</template>
} }

View File

@ -1,8 +0,0 @@
{{#if this.shouldRender}}
<DButton
@icon="times"
@action={{@onLeaveChannel}}
@title={{this.leaveChatTitleKey}}
class="btn-flat chat-channel-leave-btn"
/>
{{/if}}

View File

@ -1,4 +1,5 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import ChatChannelUnreadIndicator from "./chat-channel-unread-indicator";
export default class ChatChannelMetadata extends Component { export default class ChatChannelMetadata extends Component {
get unreadIndicator() { get unreadIndicator() {
@ -15,4 +16,18 @@ export default class ChatChannelMetadata extends Component {
sameElse: "l", sameElse: "l",
}); });
} }
<template>
<div class="chat-channel-metadata">
{{#if @channel.lastMessage}}
<div class="chat-channel-metadata__date">
{{this.lastMessageFormattedDate}}
</div>
{{/if}}
{{#if this.unreadIndicator}}
<ChatChannelUnreadIndicator @channel={{@channel}} />
{{/if}}
</div>
</template>
} }

View File

@ -1,11 +0,0 @@
<div class="chat-channel-metadata">
{{#if @channel.lastMessage}}
<div class="chat-channel-metadata__date">
{{this.lastMessageFormattedDate}}
</div>
{{/if}}
{{#if this.unreadIndicator}}
<ChatChannelUnreadIndicator @channel={{@channel}} />
{{/if}}
</div>

View File

@ -1,6 +1,12 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import { hash } from "@ember/helper";
import { LinkTo } from "@ember/routing";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import { isEmpty } from "@ember/utils"; import { isEmpty } from "@ember/utils";
import concatClass from "discourse/helpers/concat-class";
import i18n from "discourse-common/helpers/i18n";
import ChatChannelTitle from "./chat-channel-title";
import ToggleChannelMembershipButton from "./toggle-channel-membership-button";
export default class ChatChannelPreviewCard extends Component { export default class ChatChannelPreviewCard extends Component {
@service chat; @service chat;
@ -12,4 +18,33 @@ export default class ChatChannelPreviewCard extends Component {
get hasDescription() { get hasDescription() {
return !isEmpty(this.args.channel?.description); return !isEmpty(this.args.channel?.description);
} }
<template>
<div
class={{concatClass
"chat-channel-preview-card"
(unless this.hasDescription "-no-description")
(unless this.showJoinButton "-no-button")
}}
>
<ChatChannelTitle @channel={{@channel}} />
{{#if this.hasDescription}}
<p class="chat-channel-preview-card__description">
{{@channel.description}}
</p>
{{/if}}
{{#if this.showJoinButton}}
<ToggleChannelMembershipButton
@channel={{@channel}}
@options={{hash joinClass="btn-primary"}}
/>
{{/if}}
<LinkTo
@route="chat.browse"
class="chat-channel-preview-card__browse-all"
>
{{i18n "chat.browse_all_channels"}}
</LinkTo>
</div>
</template>
} }

View File

@ -1,23 +0,0 @@
<div
class={{concat-class
"chat-channel-preview-card"
(unless this.hasDescription "-no-description")
(unless this.showJoinButton "-no-button")
}}
>
<ChatChannelTitle @channel={{@channel}} />
{{#if this.hasDescription}}
<p class="chat-channel-preview-card__description">
{{@channel.description}}
</p>
{{/if}}
{{#if this.showJoinButton}}
<ToggleChannelMembershipButton
@channel={{@channel}}
@options={{hash joinClass="btn-primary"}}
/>
{{/if}}
<LinkTo @route="chat.browse" class="chat-channel-preview-card__browse-all">
{{i18n "chat.browse_all_channels"}}
</LinkTo>
</div>

View File

@ -1,9 +1,11 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import dIcon from "discourse-common/helpers/d-icon";
import I18n from "discourse-i18n"; import I18n from "discourse-i18n";
import { import {
CHANNEL_STATUSES, CHANNEL_STATUSES,
channelStatusIcon, channelStatusIcon,
} from "discourse/plugins/chat/discourse/models/chat-channel"; } from "discourse/plugins/chat/discourse/models/chat-channel";
import ChatChannelArchiveStatus from "./chat-channel-archive-status";
export default class ChatChannelStatus extends Component { export default class ChatChannelStatus extends Component {
LONG_FORMAT = "long"; LONG_FORMAT = "long";
@ -60,4 +62,14 @@ export default class ChatChannelStatus extends Component {
return I18n.t("chat.channel_status.read_only_header"); return I18n.t("chat.channel_status.read_only_header");
} }
} }
<template>
{{#if this.shouldRender}}
<div class="chat-channel-status">
{{dIcon this.channelStatusIcon}}
<span>{{this.channelStatusMessage}}</span>
<ChatChannelArchiveStatus @channel={{@channel}} />
</div>
{{/if}}
</template>
} }

View File

@ -1,7 +0,0 @@
{{#if this.shouldRender}}
<div class="chat-channel-status">
{{d-icon this.channelStatusIcon}}
<span>{{this.channelStatusMessage}}</span>
<ChatChannelArchiveStatus @channel={{@channel}} />
</div>
{{/if}}

View File

@ -1,5 +1,6 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import concatClass from "discourse/helpers/concat-class";
import { hasChatIndicator } from "../lib/chat-user-preferences"; import { hasChatIndicator } from "../lib/chat-user-preferences";
export default class ChatChannelUnreadIndicator extends Component { export default class ChatChannelUnreadIndicator extends Component {
@ -47,4 +48,19 @@ export default class ChatChannelUnreadIndicator extends Component {
#onlyMentions() { #onlyMentions() {
return hasChatIndicator(this.currentUser).ONLY_MENTIONS; return hasChatIndicator(this.currentUser).ONLY_MENTIONS;
} }
<template>
{{#if this.showUnreadIndicator}}
<div
class={{concatClass
"chat-channel-unread-indicator"
(if this.isUrgent "-urgent")
}}
>
<div class="chat-channel-unread-indicator__number">{{#if
this.showUnreadCount
}}{{this.unreadCount}}{{else}}&nbsp;{{/if}}</div>
</div>
{{/if}}
</template>
} }

View File

@ -1,12 +0,0 @@
{{#if this.showUnreadIndicator}}
<div
class={{concat-class
"chat-channel-unread-indicator"
(if this.isUrgent "-urgent")
}}
>
<div class="chat-channel-unread-indicator__number">{{#if
this.showUnreadCount
}}{{this.unreadCount}}{{else}}&nbsp;{{/if}}</div>
</div>
{{/if}}

View File

@ -1,5 +1,10 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import { array, fn } from "@ember/helper";
import { action } from "@ember/object"; import { action } from "@ember/object";
import DButton from "discourse/components/d-button";
import concatClass from "discourse/helpers/concat-class";
import i18n from "discourse-common/helpers/i18n";
import DMenu from "float-kit/components/d-menu";
export default class ChatComposerDropdown extends Component { export default class ChatComposerDropdown extends Component {
@action @action
@ -7,4 +12,39 @@ export default class ChatComposerDropdown extends Component {
closeFn(); closeFn();
button.action(); button.action();
} }
<template>
{{#if @buttons.length}}
<DMenu
class={{concatClass
"chat-composer-dropdown__trigger-btn"
"btn-flat"
(if @hasActivePanel "has-active-panel")
}}
@title={{i18n "chat.composer.toggle_toolbar"}}
@icon="plus"
@disabled={{@isDisabled}}
@arrow={{true}}
@placements={{array "top" "bottom"}}
...attributes
as |menu|
>
<ul class="chat-composer-dropdown__list">
{{#each @buttons as |button|}}
<li class={{concatClass "chat-composer-dropdown__item" button.id}}>
<DButton
@icon={{button.icon}}
@action={{fn this.onButtonClick button menu.close}}
@label={{button.label}}
class={{concatClass
"chat-composer-dropdown__action-btn"
button.id
}}
/>
</li>
{{/each}}
</ul>
</DMenu>
{{/if}}
</template>
} }

View File

@ -1,32 +0,0 @@
{{#if @buttons.length}}
<DMenu
class={{concat-class
"chat-composer-dropdown__trigger-btn"
"btn-flat"
(if @hasActivePanel "has-active-panel")
}}
@title={{i18n "chat.composer.toggle_toolbar"}}
@icon="plus"
@disabled={{@isDisabled}}
@arrow={{true}}
@placements={{array "top" "bottom"}}
...attributes
as |menu|
>
<ul class="chat-composer-dropdown__list">
{{#each @buttons as |button|}}
<li class={{concat-class "chat-composer-dropdown__item" button.id}}>
<DButton
@icon={{button.icon}}
@action={{fn this.onButtonClick button menu.close}}
@label={{button.label}}
class={{concat-class
"chat-composer-dropdown__action-btn"
button.id
}}
/>
</li>
{{/each}}
</ul>
</DMenu>
{{/if}}

View File

@ -1,3 +1,32 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import DButton from "discourse/components/d-button";
import replaceEmoji from "discourse/helpers/replace-emoji";
import dIcon from "discourse-common/helpers/d-icon";
import htmlSafe from "discourse-common/helpers/html-safe";
import ChatUserAvatar from "./chat-user-avatar";
export default class ChatComposerMessageDetails extends Component {} export default class ChatComposerMessageDetails extends Component {
<template>
<div
class="chat-composer-message-details"
data-id={{@message.id}}
data-action={{if @message.editing "edit" "reply"}}
>
<div class="chat-reply">
{{dIcon (if @message.editing "pencil-alt" "reply")}}
<ChatUserAvatar @user={{@message.user}} />
<span class="chat-reply__username">{{@message.user.username}}</span>
<span class="chat-reply__excerpt">
{{replaceEmoji (htmlSafe @message.excerpt)}}
</span>
</div>
<DButton
@action={{@cancelAction}}
@icon="times-circle"
@title="cancel"
class="btn-flat cancel-message-action"
/>
</div>
</template>
}

View File

@ -1,21 +0,0 @@
<div
class="chat-composer-message-details"
data-id={{@message.id}}
data-action={{if @message.editing "edit" "reply"}}
>
<div class="chat-reply">
{{d-icon (if @message.editing "pencil-alt" "reply")}}
<ChatUserAvatar @user={{@message.user}} />
<span class="chat-reply__username">{{@message.user.username}}</span>
<span class="chat-reply__excerpt">
{{replace-emoji (html-safe @message.excerpt)}}
</span>
</div>
<DButton
@action={{@cancelAction}}
@icon="times-circle"
@title="cancel"
class="btn-flat cancel-message-action"
/>
</div>

View File

@ -1,5 +1,9 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import DButton from "discourse/components/d-button";
import concatClass from "discourse/helpers/concat-class";
import { isImage } from "discourse/lib/uploads"; import { isImage } from "discourse/lib/uploads";
import dIcon from "discourse-common/helpers/d-icon";
import i18n from "discourse-common/helpers/i18n";
export default class ChatComposerUpload extends Component { export default class ChatComposerUpload extends Component {
get isImage() { get isImage() {
@ -13,4 +17,64 @@ export default class ChatComposerUpload extends Component {
? this.args.upload.original_filename ? this.args.upload.original_filename
: this.args.upload.fileName; : this.args.upload.fileName;
} }
<template>
{{#if @upload}}
<div
class={{concatClass
"chat-composer-upload"
(if this.isImage "chat-composer-upload--image")
(unless @isDone "chat-composer-upload--in-progress")
}}
>
<div class="preview">
{{#if this.isImage}}
{{#if @isDone}}
<img class="preview-img" src={{@upload.short_path}} />
{{else}}
{{dIcon "far-image"}}
{{/if}}
{{else}}
{{dIcon "file-alt"}}
{{/if}}
</div>
<span class="data">
{{#unless this.isImage}}
<div class="top-data">
<span class="file-name">{{this.fileName}}</span>
</div>
{{/unless}}
<div class="bottom-data">
{{#if @isDone}}
{{#unless this.isImage}}
<span class="extension-pill">{{@upload.extension}}</span>
{{/unless}}
{{else}}
{{#if @upload.processing}}
<span class="processing">{{i18n "processing"}}</span>
{{else}}
<span class="uploading">{{i18n "uploading"}}</span>
{{/if}}
<progress
class="upload-progress"
id="file"
max="100"
value={{@upload.progress}}
></progress>
{{/if}}
</div>
</span>
<DButton
@action={{@onCancel}}
@icon="times"
@title="chat.remove_upload"
class="btn-flat chat-composer-upload__remove-btn"
/>
</div>
{{/if}}
</template>
} }

View File

@ -1,57 +0,0 @@
{{#if @upload}}
<div
class={{concat-class
"chat-composer-upload"
(if this.isImage "chat-composer-upload--image")
(unless @isDone "chat-composer-upload--in-progress")
}}
>
<div class="preview">
{{#if this.isImage}}
{{#if @isDone}}
<img class="preview-img" src={{@upload.short_path}} />
{{else}}
{{d-icon "far-image"}}
{{/if}}
{{else}}
{{d-icon "file-alt"}}
{{/if}}
</div>
<span class="data">
{{#unless this.isImage}}
<div class="top-data">
<span class="file-name">{{this.fileName}}</span>
</div>
{{/unless}}
<div class="bottom-data">
{{#if @isDone}}
{{#unless this.isImage}}
<span class="extension-pill">{{@upload.extension}}</span>
{{/unless}}
{{else}}
{{#if @upload.processing}}
<span class="processing">{{i18n "processing"}}</span>
{{else}}
<span class="uploading">{{i18n "uploading"}}</span>
{{/if}}
<progress
class="upload-progress"
id="file"
max="100"
value={{@upload.progress}}
></progress>
{{/if}}
</div>
</span>
<DButton
@action={{@onCancel}}
@icon="times"
@title="chat.remove_upload"
class="btn-flat chat-composer-upload__remove-btn"
/>
</div>
{{/if}}

View File

@ -1,6 +1,14 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import { array } from "@ember/helper";
import { action } from "@ember/object"; 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 { 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";
export default class ChatDrawerChannel extends Component { export default class ChatDrawerChannel extends Component {
@service appEvents; @service appEvents;
@ -20,4 +28,36 @@ export default class ChatDrawerChannel extends Component {
this.chat.activeChannel = channel; this.chat.activeChannel = channel;
}); });
} }
<template>
<Header @toggleExpand={{@drawerActions.toggleExpand}}>
<LeftActions />
<ChannelTitle
@channel={{this.chat.activeChannel}}
@drawerActions={{@drawerActions}}
/>
<RightActions @drawerActions={{@drawerActions}} />
</Header>
{{#if this.chatStateManager.isDrawerExpanded}}
<div
class="chat-drawer-content"
{{didInsert this.fetchChannel}}
{{didUpdate this.fetchChannel @params.channelId}}
>
{{#if this.chat.activeChannel}}
{{#each (array this.chat.activeChannel) as |channel|}}
{{#if channel}}
<ChatChannel
@targetMessageId={{readonly @params.messageId}}
@channel={{channel}}
/>
{{/if}}
{{/each}}
{{/if}}
</div>
{{/if}}
</template>
} }

View File

@ -1,29 +0,0 @@
<ChatDrawer::Header @toggleExpand={{@drawerActions.toggleExpand}}>
<ChatDrawer::Header::LeftActions />
<ChatDrawer::Header::ChannelTitle
@channel={{this.chat.activeChannel}}
@drawerActions={{@drawerActions}}
/>
<ChatDrawer::Header::RightActions @drawerActions={{@drawerActions}} />
</ChatDrawer::Header>
{{#if this.chatStateManager.isDrawerExpanded}}
<div
class="chat-drawer-content"
{{did-insert this.fetchChannel}}
{{did-update this.fetchChannel @params.channelId}}
>
{{#if this.chat.activeChannel}}
{{#each (array this.chat.activeChannel) as |channel|}}
{{#if channel}}
<ChatChannel
@targetMessageId={{readonly @params.messageId}}
@channel={{channel}}
/>
{{/if}}
{{/each}}
{{/if}}
</div>
{{/if}}

View File

@ -1,6 +1,25 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import { on } from "@ember/modifier";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import i18n from "discourse-common/helpers/i18n";
export default class ChatDrawerHeader extends Component { export default class ChatDrawerHeader extends Component {
@service chatStateManager; @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,14 +0,0 @@
{{! 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>

View File

@ -1,6 +1,21 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import { array } from "@ember/helper";
import { LinkTo } from "@ember/routing";
import { inject as service } from "@ember/service"; 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 { export default class ChatDrawerHeaderBackLink extends Component {
@service chatStateManager; @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,8 +0,0 @@
<LinkTo
title={{@title}}
class="chat-drawer-header__back-btn"
@route={{@route}}
@models={{or @routeModels (array)}}
>
{{d-icon "chevron-left"}}
</LinkTo>

View File

@ -1,6 +1,45 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import { on } from "@ember/modifier";
import { LinkTo } from "@ember/routing";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import ChatChannelTitle from "../../chat-channel-title";
export default class ChatDrawerChannelHeaderTitle extends Component { export default class ChatDrawerChannelHeaderTitle extends Component {
@service chatStateManager; @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">
<ChatChannelTitle @channel={{@channel}} />
</div>
</LinkTo>
{{else}}
<div
role="button"
{{on "click" @drawerActions.toggleExpand}}
class="chat-drawer-header__title"
>
<div class="chat-drawer-header__top-line">
<ChatChannelTitle @channel={{@channel}}>
{{#if @channel.tracking.unreadCount}}
<span class="chat-unread-count">
{{@channel.tracking.unreadCount}}
</span>
{{/if}}
</ChatChannelTitle>
</div>
</div>
{{/if}}
{{/if}}
</template>
} }

View File

@ -1,33 +0,0 @@
{{#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">
<ChatChannelTitle @channel={{@channel}} />
</div>
</LinkTo>
{{else}}
<div
role="button"
{{on "click" @drawerActions.toggleExpand}}
class="chat-drawer-header__title"
>
<div class="chat-drawer-header__top-line">
<ChatChannelTitle @channel={{@channel}}>
{{#if @channel.tracking.unreadCount}}
<span class="chat-unread-count">
{{@channel.tracking.unreadCount}}
</span>
{{/if}}
</ChatChannelTitle>
</div>
</div>
{{/if}}
{{/if}}

View File

@ -1,4 +1,13 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import DButton from "discourse/components/d-button";
export default class extends Component { export default class extends Component {
<template>
<DButton
@icon="times"
@action={{@close}}
@title="chat.close"
class="btn-flat btn-link chat-drawer-header__close-btn"
/>
</template>
} }

View File

@ -1,6 +0,0 @@
<DButton
@icon="times"
@action={{@close}}
@title="chat.close"
class="btn-flat btn-link chat-drawer-header__close-btn"
/>

View File

@ -1,6 +1,18 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import DButton from "discourse/components/d-button";
export default class ChatDrawerHeaderFullPageButton extends Component { export default class ChatDrawerHeaderFullPageButton extends Component {
@service chatStateManager; @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,8 +0,0 @@
{{#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}}

View File

@ -1,6 +1,18 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import { inject as service } from "@ember/service"; 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 { export default class ChatDrawerHeaderLeftActions extends Component {
@service chatStateManager; @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,10 +0,0 @@
{{#if this.chatStateManager.isDrawerExpanded}}
<div class="chat-drawer-header__left-actions">
<div class="chat-drawer-header__top-line">
<ChatDrawer::Header::BackLink
@route="chat"
@title={{i18n "chat.return_to_list"}}
/>
</div>
</div>
{{/if}}

View File

@ -1,5 +1,9 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import { inject as service } from "@ember/service"; 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 { export default class ChatDrawerHeaderRightActions extends Component {
@service chat; @service chat;
@ -7,4 +11,20 @@ export default class ChatDrawerHeaderRightActions extends Component {
get showThreadsListButton() { get showThreadsListButton() {
return this.chat.activeChannel?.threadingEnabled; 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,17 +0,0 @@
<div class="chat-drawer-header__right-actions">
<div class="chat-drawer-header__top-line">
{{#if this.showThreadsListButton}}
<Chat::Thread::ThreadsListButton @channel={{this.chat.activeChannel}} />
{{/if}}
<ChatDrawer::Header::ToggleExpandButton
@toggleExpand={{@drawerActions.toggleExpand}}
/>
<ChatDrawer::Header::FullPageButton
@openInFullPage={{@drawerActions.openInFullPage}}
/>
<ChatDrawer::Header::CloseButton @close={{@drawerActions.close}} />
</div>
</div>

View File

@ -1,6 +1,24 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import DButton from "discourse/components/d-button";
export default class ChatDrawerHeaderToggleExpandButton extends Component { export default class ChatDrawerHeaderToggleExpandButton extends Component {
@service chatStateManager; @service chatStateManager;
<template>
<DButton
@icon={{if
this.chatStateManager.isDrawerExpanded
"angle-double-down"
"angle-double-up"
}}
@action={{@toggleExpand}}
@title={{if
this.chatStateManager.isDrawerExpanded
"chat.collapse"
"chat.expand"
}}
class="btn-flat btn-link chat-drawer-header__expand-btn"
/>
</template>
} }

View File

@ -1,14 +0,0 @@
<DButton
@icon={{if
this.chatStateManager.isDrawerExpanded
"angle-double-down"
"angle-double-up"
}}
@action={{@toggleExpand}}
@title={{if
this.chatStateManager.isDrawerExpanded
"chat.collapse"
"chat.expand"
}}
class="btn-flat btn-link chat-drawer-header__expand-btn"
/>

View File

@ -1,6 +1,28 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import { inject as service } from "@ember/service"; 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 { export default class ChatDrawerIndex extends Component {
@service chatStateManager; @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,15 +0,0 @@
<ChatDrawer::Header @toggleExpand={{@drawerActions.toggleExpand}}>
<div class="chat-drawer-header__title">
<div class="chat-drawer-header__top-line">
{{i18n "chat.heading"}}
</div>
</div>
<ChatDrawer::Header::RightActions @drawerActions={{@drawerActions}} />
</ChatDrawer::Header>
{{#if this.chatStateManager.isDrawerExpanded}}
<div class="chat-drawer-content">
<ChannelsList />
</div>
{{/if}}

View File

@ -1,4 +1,12 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import replaceEmoji from "discourse/helpers/replace-emoji";
export default class extends Component { export default class extends Component {
<template>
<div class="chat-emoji-avatar">
<div class="chat-emoji-avatar-container">
{{replaceEmoji @emoji}}
</div>
</div>
</template>
} }

View File

@ -1,5 +0,0 @@
<div class="chat-emoji-avatar">
<div class="chat-emoji-avatar-container">
{{replace-emoji @emoji}}
</div>
</div>

View File

@ -1,6 +1,8 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import { htmlSafe } from "@ember/template"; import { htmlSafe } from "@ember/template";
import dIcon from "discourse-common/helpers/d-icon";
import i18n from "discourse-common/helpers/i18n";
import getURL from "discourse-common/lib/get-url"; import getURL from "discourse-common/lib/get-url";
import I18n from "discourse-i18n"; import I18n from "discourse-i18n";
@ -145,4 +147,36 @@ export default class ChatMentionWarnings extends Component {
}) })
); );
} }
<template>
{{#if this.show}}
<div class="chat-mention-warnings">
<div class="chat-mention-warning__icon">
{{dIcon "exclamation-triangle"}}
</div>
<div class="chat-mention-warning__text">
<div class="chat-mention-warning__header">
{{this.warningHeaderText}}
</div>
<ul class={{this.listStyleClass}}>
{{#if this.hasTooManyMentions}}
<li>{{this.tooManyMentionsBody}}</li>
{{else}}
{{#if this.channelWideMentionDisallowed}}
<li>{{i18n
"chat.mention_warning.channel_wide_mentions_disallowed"
}}</li>
{{/if}}
{{#if this.hasUnreachableGroupMentions}}
<li>{{this.unreachableBody}}</li>
{{/if}}
{{#if this.hasOverMembersLimitGroupMentions}}
<li>{{this.overMembersLimitBody}}</li>
{{/if}}
{{/if}}
</ul>
</div>
</div>
{{/if}}
</template>
} }

View File

@ -1,29 +0,0 @@
{{#if this.show}}
<div class="chat-mention-warnings">
<div class="chat-mention-warning__icon">
{{d-icon "exclamation-triangle"}}
</div>
<div class="chat-mention-warning__text">
<div class="chat-mention-warning__header">
{{this.warningHeaderText}}
</div>
<ul class={{this.listStyleClass}}>
{{#if this.hasTooManyMentions}}
<li>{{this.tooManyMentionsBody}}</li>
{{else}}
{{#if this.channelWideMentionDisallowed}}
<li>{{i18n
"chat.mention_warning.channel_wide_mentions_disallowed"
}}</li>
{{/if}}
{{#if this.hasUnreachableGroupMentions}}
<li>{{this.unreachableBody}}</li>
{{/if}}
{{#if this.hasOverMembersLimitGroupMentions}}
<li>{{this.overMembersLimitBody}}</li>
{{/if}}
{{/if}}
</ul>
</div>
</div>
{{/if}}

View File

@ -1,5 +1,11 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import { LinkTo } from "@ember/routing";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import replaceEmoji from "discourse/helpers/replace-emoji";
import dIcon from "discourse-common/helpers/d-icon";
import htmlSafe from "discourse-common/helpers/html-safe";
import ChatEmojiAvatar from "./chat-emoji-avatar";
import ChatUserAvatar from "./chat-user-avatar";
export default class ChatMessageInReplyToIndicator extends Component { export default class ChatMessageInReplyToIndicator extends Component {
@service router; @service router;
@ -32,4 +38,28 @@ export default class ChatMessageInReplyToIndicator extends Component {
this.args.message?.thread?.id this.args.message?.thread?.id
); );
} }
<template>
{{#if @message.inReplyTo}}
<LinkTo
@route={{this.route}}
@models={{this.model}}
class="chat-reply is-direct-reply"
>
{{dIcon "share" title="chat.in_reply_to"}}
{{#if @message.inReplyTo.chatWebhookEvent.emoji}}
<ChatEmojiAvatar
@emoji={{@message.inReplyTo.chatWebhookEvent.emoji}}
/>
{{else}}
<ChatUserAvatar @user={{@message.inReplyTo.user}} />
{{/if}}
<span class="chat-reply__excerpt">
{{replaceEmoji (htmlSafe @message.inReplyTo.excerpt)}}
</span>
</LinkTo>
{{/if}}
</template>
} }

View File

@ -1,19 +0,0 @@
{{#if @message.inReplyTo}}
<LinkTo
@route={{this.route}}
@models={{this.model}}
class="chat-reply is-direct-reply"
>
{{d-icon "share" title="chat.in_reply_to"}}
{{#if @message.inReplyTo.chatWebhookEvent.emoji}}
<ChatEmojiAvatar @emoji={{@message.inReplyTo.chatWebhookEvent.emoji}} />
{{else}}
<ChatUserAvatar @user={{@message.inReplyTo.user}} />
{{/if}}
<span class="chat-reply__excerpt">
{{replace-emoji (html-safe @message.inReplyTo.excerpt)}}
</span>
</LinkTo>
{{/if}}

View File

@ -1,5 +1,9 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import { on } from "@ember/modifier";
import { action } from "@ember/object"; import { action } from "@ember/object";
import concatClass from "discourse/helpers/concat-class";
import i18n from "discourse-common/helpers/i18n";
import trackMessageSeparatorDate from "../modifiers/chat/track-message-separator-date";
export default class ChatMessageSeparatorDate extends Component { export default class ChatMessageSeparatorDate extends Component {
@action @action
@ -8,4 +12,39 @@ export default class ChatMessageSeparatorDate extends Component {
this.args.message.firstMessageOfTheDayAt this.args.message.firstMessageOfTheDayAt
); );
} }
<template>
{{#if @message.formattedFirstMessageDate}}
<div
class={{concatClass
"chat-message-separator-date"
(if @message.newest "with-last-visit")
}}
role="button"
{{on "click" this.onDateClick passive=true}}
>
<div
class="chat-message-separator__text-container"
{{trackMessageSeparatorDate}}
>
<span class="chat-message-separator__text">
{{@message.formattedFirstMessageDate}}
{{#if @message.newest}}
<span class="chat-message-separator__last-visit">
<span
class="chat-message-separator__last-visit-separator"
>-</span>
{{i18n "chat.last_visit"}}
</span>
{{/if}}
</span>
</div>
</div>
<div class="chat-message-separator__line-container">
<div class="chat-message-separator__line"></div>
</div>
{{/if}}
</template>
} }

View File

@ -1,30 +0,0 @@
{{#if @message.formattedFirstMessageDate}}
<div
class={{concat-class
"chat-message-separator-date"
(if @message.newest "with-last-visit")
}}
role="button"
{{on "click" this.onDateClick passive=true}}
>
<div
class="chat-message-separator__text-container"
{{chat/track-message-separator-date}}
>
<span class="chat-message-separator__text">
{{@message.formattedFirstMessageDate}}
{{#if @message.newest}}
<span class="chat-message-separator__last-visit">
<span class="chat-message-separator__last-visit-separator">-</span>
{{i18n "chat.last_visit"}}
</span>
{{/if}}
</span>
</div>
</div>
<div class="chat-message-separator__line-container">
<div class="chat-message-separator__line"></div>
</div>
{{/if}}

View File

@ -1,4 +1,22 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import i18n from "discourse-common/helpers/i18n";
import and from "truth-helpers/helpers/and";
import not from "truth-helpers/helpers/not";
export default class extends Component { export default class extends Component {
<template>
{{#if (and @message.newest (not @message.formattedFirstMessageDate))}}
<div class="chat-message-separator-new">
<div class="chat-message-separator__text-container">
<span class="chat-message-separator__text">
{{i18n "chat.last_visit"}}
</span>
</div>
<div class="chat-message-separator__line-container">
<div class="chat-message-separator__line"></div>
</div>
</div>
{{/if}}
</template>
} }

View File

@ -1,13 +0,0 @@
{{#if (and @message.newest (not @message.formattedFirstMessageDate))}}
<div class="chat-message-separator-new">
<div class="chat-message-separator__text-container">
<span class="chat-message-separator__text">
{{i18n "chat.last_visit"}}
</span>
</div>
<div class="chat-message-separator__line-container">
<div class="chat-message-separator__line"></div>
</div>
</div>
{{/if}}

View File

@ -1,5 +1,8 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import htmlSafe from "discourse-common/helpers/html-safe";
import i18n from "discourse-common/helpers/i18n";
import { isCollapsible } from "discourse/plugins/chat/discourse/components/chat-message-collapser"; import { isCollapsible } from "discourse/plugins/chat/discourse/components/chat-message-collapser";
import ChatMessageCollapser from "./chat-message-collapser";
export default class ChatMessageText extends Component { export default class ChatMessageText extends Component {
get isEdited() { get isEdited() {
@ -9,4 +12,24 @@ export default class ChatMessageText extends Component {
get isCollapsible() { get isCollapsible() {
return isCollapsible(this.args.cooked, this.args.uploads); return isCollapsible(this.args.cooked, this.args.uploads);
} }
<template>
<div class="chat-message-text">
{{#if this.isCollapsible}}
<ChatMessageCollapser
@cooked={{@cooked}}
@uploads={{@uploads}}
@onToggleCollapse={{@onToggleCollapse}}
/>
{{else}}
{{htmlSafe @cooked}}
{{/if}}
{{#if this.isEdited}}
<span class="chat-message-edited">({{i18n "chat.edited"}})</span>
{{/if}}
{{yield}}
</div>
</template>
} }

View File

@ -1,17 +0,0 @@
<div class="chat-message-text">
{{#if this.isCollapsible}}
<ChatMessageCollapser
@cooked={{@cooked}}
@uploads={{@uploads}}
@onToggleCollapse={{@onToggleCollapse}}
/>
{{else}}
{{html-safe @cooked}}
{{/if}}
{{#if this.isEdited}}
<span class="chat-message-edited">({{i18n "chat.edited"}})</span>
{{/if}}
{{yield}}
</div>

View File

@ -1,8 +1,17 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking"; import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object"; import { action } from "@ember/object";
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import willDestroy from "@ember/render-modifiers/modifiers/will-destroy";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import concatClass from "discourse/helpers/concat-class";
import formatDate from "discourse/helpers/format-date";
import replaceEmoji from "discourse/helpers/replace-emoji";
import htmlSafe from "discourse-common/helpers/html-safe";
import i18n from "discourse-common/helpers/i18n";
import { bind } from "discourse-common/utils/decorators"; import { bind } from "discourse-common/utils/decorators";
import ChatThreadParticipants from "./chat-thread-participants";
import ChatUserAvatar from "./chat-user-avatar";
export default class ChatMessageThreadIndicator extends Component { export default class ChatMessageThreadIndicator extends Component {
@service capabilities; @service capabilities;
@ -83,4 +92,44 @@ export default class ChatMessageThreadIndicator extends Component {
...this.args.message.thread.routeModels ...this.args.message.thread.routeModels
); );
} }
<template>
<div
class={{concatClass
"chat-message-thread-indicator"
(if this.isActive "-active")
}}
{{didInsert this.setup}}
{{willDestroy this.teardown}}
role="button"
title={{i18n "chat.threads.open"}}
>
<div class="chat-message-thread-indicator__last-reply-avatar">
<ChatUserAvatar
@user={{@message.thread.preview.lastReplyUser}}
@avatarSize="small"
/>
</div>
<div class="chat-message-thread-indicator__last-reply-info">
<span class="chat-message-thread-indicator__last-reply-username">
{{@message.thread.preview.lastReplyUser.username}}
</span>
<span class="chat-message-thread-indicator__last-reply-timestamp">
{{formatDate
@message.thread.preview.lastReplyCreatedAt
leaveAgo="true"
}}
</span>
</div>
<div class="chat-message-thread-indicator__replies-count">
{{i18n "chat.thread.replies" count=@message.thread.preview.replyCount}}
</div>
<ChatThreadParticipants @thread={{@message.thread}} />
<div class="chat-message-thread-indicator__last-reply-excerpt">
{{replaceEmoji (htmlSafe @message.thread.preview.lastReplyExcerpt)}}
</div>
</div>
</template>
} }

View File

@ -1,34 +0,0 @@
<div
class={{concat-class
"chat-message-thread-indicator"
(if this.isActive "-active")
}}
{{did-insert this.setup}}
{{will-destroy this.teardown}}
role="button"
title={{i18n "chat.threads.open"}}
>
<div class="chat-message-thread-indicator__last-reply-avatar">
<ChatUserAvatar
@user={{@message.thread.preview.lastReplyUser}}
@avatarSize="small"
/>
</div>
<div class="chat-message-thread-indicator__last-reply-info">
<span class="chat-message-thread-indicator__last-reply-username">
{{@message.thread.preview.lastReplyUser.username}}
</span>
<span class="chat-message-thread-indicator__last-reply-timestamp">
{{format-date @message.thread.preview.lastReplyCreatedAt leaveAgo="true"}}
</span>
</div>
<div class="chat-message-thread-indicator__replies-count">
{{i18n "chat.thread.replies" count=@message.thread.preview.replyCount}}
</div>
<ChatThreadParticipants @thread={{@message.thread}} />
<div class="chat-message-thread-indicator__last-reply-excerpt">
{{replace-emoji (html-safe @message.thread.preview.lastReplyExcerpt)}}
</div>
</div>

View File

@ -1,6 +1,7 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import { action } from "@ember/object"; import { action } from "@ember/object";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import DButton from "discourse/components/d-button";
import MentionWithoutMembership from "discourse/plugins/chat/discourse/components/chat/notices/mention_without_membership"; import MentionWithoutMembership from "discourse/plugins/chat/discourse/components/chat/notices/mention_without_membership";
const COMPONENT_DICT = { const COMPONENT_DICT = {
@ -18,4 +19,27 @@ export default class ChatNotices extends Component {
get component() { get component() {
return COMPONENT_DICT[this.args.notice.type]; return COMPONENT_DICT[this.args.notice.type];
} }
<template>
<div class="chat-notices__notice">
{{#if @notice.textContent}}
<p class="chat-notices__notice__content">
{{@notice.textContent}}
</p>
{{else}}
<this.component
@channel={{@channel}}
@notice={{@notice}}
@clearNotice={{this.clearNotice}}
/>
{{/if}}
<DButton
@icon="times"
@action={{this.clearNotice}}
class="btn-flat chat-notices__notice__clear"
/>
</div>
</template>
} }

View File

@ -1,20 +0,0 @@
<div class="chat-notices__notice">
{{#if @notice.textContent}}
<p class="chat-notices__notice__content">
{{@notice.textContent}}
</p>
{{else}}
<this.component
@channel={{@channel}}
@notice={{@notice}}
@clearNotice={{this.clearNotice}}
/>
{{/if}}
<DButton
@icon="times"
@action={{this.clearNotice}}
class="btn-flat chat-notices__notice__clear"
/>
</div>

View File

@ -1,5 +1,7 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import ChatNotice from "./chat-notice";
import ChatRetentionReminder from "./chat-retention-reminder";
export default class ChatNotices extends Component { export default class ChatNotices extends Component {
@service("chat-channel-notices-manager") noticesManager; @service("chat-channel-notices-manager") noticesManager;
@ -9,4 +11,14 @@ export default class ChatNotices extends Component {
(notice) => notice.channelId === this.args.channel.id (notice) => notice.channelId === this.args.channel.id
); );
} }
<template>
<div class="chat-notices">
<ChatRetentionReminder @channel={{@channel}} />
{{#each this.noticesForChannel as |notice|}}
<ChatNotice @notice={{notice}} @channel={{@channel}} />
{{/each}}
</div>
</template>
} }

View File

@ -1,7 +0,0 @@
<div class="chat-notices">
<ChatRetentionReminder @channel={{@channel}} />
{{#each this.noticesForChannel as |notice|}}
<ChatNotice @notice={{notice}} @channel={{@channel}} />
{{/each}}
</div>

View File

@ -1,8 +1,12 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking"; import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object"; import { action } from "@ember/object";
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import didUpdate from "@ember/render-modifiers/modifiers/did-update";
import willDestroy from "@ember/render-modifiers/modifiers/will-destroy";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import { isPresent } from "@ember/utils"; import { isPresent } from "@ember/utils";
import concatClass from "discourse/helpers/concat-class";
import I18n from "discourse-i18n"; import I18n from "discourse-i18n";
export default class ChatReplyingIndicator extends Component { export default class ChatReplyingIndicator extends Component {
@ -74,4 +78,27 @@ export default class ChatReplyingIndicator extends Component {
get shouldRender() { get shouldRender() {
return isPresent(this.usernames); return isPresent(this.usernames);
} }
<template>
{{#if @presenceChannelName}}
<div
class={{concatClass
"chat-replying-indicator"
(if this.presenceChannel.subscribed "is-subscribed")
}}
{{didInsert this.subscribe}}
{{didUpdate this.updateSubscription @presenceChannelName}}
{{willDestroy this.unsubscribe}}
>
{{#if this.shouldRender}}
<span class="chat-replying-indicator__text">{{this.text}}</span>
<span class="chat-replying-indicator__wave">
<span class="chat-replying-indicator__dot">.</span>
<span class="chat-replying-indicator__dot">.</span>
<span class="chat-replying-indicator__dot">.</span>
</span>
{{/if}}
</div>
{{/if}}
</template>
} }

View File

@ -1,20 +0,0 @@
{{#if @presenceChannelName}}
<div
class={{concat-class
"chat-replying-indicator"
(if this.presenceChannel.subscribed "is-subscribed")
}}
{{did-insert this.subscribe}}
{{did-update this.updateSubscription @presenceChannelName}}
{{will-destroy this.unsubscribe}}
>
{{#if this.shouldRender}}
<span class="chat-replying-indicator__text">{{this.text}}</span>
<span class="chat-replying-indicator__wave">
<span class="chat-replying-indicator__dot">.</span>
<span class="chat-replying-indicator__dot">.</span>
<span class="chat-replying-indicator__dot">.</span>
</span>
{{/if}}
</div>
{{/if}}

View File

@ -1,4 +1,7 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
export default class extends Component { export default class extends Component {
<template>
<div class="chat-side-panel-resizer"></div>
</template>
} }

View File

@ -1 +0,0 @@
<div class="chat-side-panel-resizer"></div>

View File

@ -1,8 +1,13 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking"; import { tracked } from "@glimmer/tracking";
import { hash } from "@ember/helper";
import { action } from "@ember/object"; import { action } from "@ember/object";
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import { htmlSafe } from "@ember/template"; import { htmlSafe } from "@ember/template";
import and from "truth-helpers/helpers/and";
import resizableNode from "../modifiers/chat/resizable-node";
import ChatSidePanelResizer from "./chat-side-panel-resizer";
const MIN_CHAT_CHANNEL_WIDTH = 250; const MIN_CHAT_CHANNEL_WIDTH = 250;
@ -38,4 +43,30 @@ export default class ChatSidePanel extends Component {
const parentWidth = element.parentElement.getBoundingClientRect().width; const parentWidth = element.parentElement.getBoundingClientRect().width;
return parentWidth - MIN_CHAT_CHANNEL_WIDTH; return parentWidth - MIN_CHAT_CHANNEL_WIDTH;
} }
<template>
{{#if this.chatStateManager.isSidePanelExpanded}}
<div
class="chat-side-panel"
{{didInsert this.setupSize}}
{{resizableNode
".chat-side-panel-resizer"
this.didResize
(hash
position=false vertical=false mutate=false resetOnWindowResize=true
)
}}
style={{if
(and this.site.desktopView this.chatStateManager.isFullPageActive)
this.widthStyle
}}
>
{{yield}}
{{#if this.site.desktopView}}
<ChatSidePanelResizer />
{{/if}}
</div>
{{/if}}
</template>
} }

View File

@ -1,21 +0,0 @@
{{#if this.chatStateManager.isSidePanelExpanded}}
<div
class="chat-side-panel"
{{did-insert this.setupSize}}
{{chat/resizable-node
".chat-side-panel-resizer"
this.didResize
(hash position=false vertical=false mutate=false resetOnWindowResize=true)
}}
style={{if
(and this.site.desktopView this.chatStateManager.isFullPageActive)
this.widthStyle
}}
>
{{yield}}
{{#if this.site.desktopView}}
<ChatSidePanelResizer />
{{/if}}
</div>
{{/if}}

View File

@ -17,4 +17,36 @@ export default class ChatSkeleton extends Component {
#randomIntFromInterval(min, max) { #randomIntFromInterval(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min); return Math.floor(Math.random() * (max - min + 1) + min);
} }
<template>
<div class="chat-skeleton -animation">
{{#each this.placeholders as |placeholder|}}
<div class="chat-skeleton__body">
<div class="chat-skeleton__message">
<div class="chat-skeleton__message-avatar"></div>
<div class="chat-skeleton__message-poster"></div>
<div class="chat-skeleton__message-content">
{{#if placeholder.image}}
<div class="chat-skeleton__message-img"></div>
{{/if}}
<div class="chat-skeleton__message-text">
{{#each placeholder.rows as |row|}}
<div class="chat-skeleton__message-msg" style={{row}}></div>
{{/each}}
</div>
{{#if placeholder.reactions}}
<div class="chat-skeleton__message-reactions">
{{#each placeholder.reactions}}
<div class="chat-skeleton__message-reaction"></div>
{{/each}}
</div>
{{/if}}
</div>
</div>
</div>
{{/each}}
</div>
</template>
} }

View File

@ -1,29 +0,0 @@
<div class="chat-skeleton -animation">
{{#each this.placeholders as |placeholder|}}
<div class="chat-skeleton__body">
<div class="chat-skeleton__message">
<div class="chat-skeleton__message-avatar"></div>
<div class="chat-skeleton__message-poster"></div>
<div class="chat-skeleton__message-content">
{{#if placeholder.image}}
<div class="chat-skeleton__message-img"></div>
{{/if}}
<div class="chat-skeleton__message-text">
{{#each placeholder.rows as |row|}}
<div class="chat-skeleton__message-msg" style={{row}}></div>
{{/each}}
</div>
{{#if placeholder.reactions}}
<div class="chat-skeleton__message-reactions">
{{#each placeholder.reactions}}
<div class="chat-skeleton__message-reaction"></div>
{{/each}}
</div>
{{/if}}
</div>
</div>
</div>
{{/each}}
</div>

View File

@ -16,4 +16,72 @@ export default class ChatUploadDropZone extends Component {
#isThread() { #isThread() {
return this.args.model instanceof ChatThread; return this.args.model instanceof ChatThread;
} }
<template>
<div class="chat-upload-drop-zone">
<div class="chat-upload-drop-zone__content">
<div class="chat-upload-drop-zone__background">
<svg
width="94"
height="90"
viewBox="0 0 94 90"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M94 40.5591C94 69.8685 64.0686 90 40.9592 90C17.8499 90 0 83.9085 0 60.6907C0 37.4729 28.458 0 51.5674 0C74.6768 0 94 17.3413 94 40.5591Z"
fill="#D1F0FF"
></path>
</svg>
</div>
<div class="chat-upload-drop-zone__illustration">
<svg
width="106"
height="84"
viewBox="0 0 106 84"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect
x="55.832"
y="6.82959"
width="45.7854"
height="33.8408"
transform="rotate(13.4039 55.832 6.82959)"
fill="#D9D9D9"
></rect>
<path
d="M100.66 13.7645L61.1414 4.34705C58.6715 3.75846 56.1786 5.37298 55.59 7.84288L47.6214 41.2815C47.0102 43.8464 48.5297 46.3167 50.9996 46.9053L90.518 56.3227C92.9879 56.9113 95.5532 55.4145 96.1644 52.8496L104.133 19.4109C104.722 16.941 103.225 14.3757 100.66 13.7645ZM58.7093 41.5144C58.5961 41.9894 58.1482 42.1838 57.7682 42.0933L53.2084 41.0067C52.7334 40.8935 52.5163 40.5406 52.6295 40.0656L53.7161 35.5058C53.8067 35.1258 54.1822 34.8137 54.6572 34.9269L59.122 35.9909C59.502 36.0815 59.7915 36.552 59.7009 36.932L58.7093 41.5144ZM61.6069 29.3549C61.4938 29.8299 61.0459 30.0243 60.6659 29.9338L56.1061 28.8472C55.6311 28.734 55.414 28.3811 55.5272 27.9061L56.6138 23.3463C56.7044 22.9663 57.0799 22.6542 57.5549 22.7674L62.0197 23.8314C62.3997 23.922 62.6891 24.3925 62.5986 24.7725L61.5119 29.3323L61.6069 29.3549ZM64.5046 17.1954C64.3914 17.6704 63.9435 17.8648 63.5635 17.7743L59.0037 16.6877C58.5287 16.5745 58.3117 16.2216 58.4249 15.7466L59.5115 11.1868C59.602 10.8068 59.9776 10.4947 60.4526 10.6079L64.9174 11.6719C65.2974 11.7625 65.5868 12.233 65.4962 12.613L64.5046 17.1954ZM81.6894 46.1876C81.4857 47.0426 80.5673 47.5264 79.8073 47.3453L64.6079 43.7232C63.753 43.5195 63.2464 42.6961 63.4502 41.8411L65.6234 32.7215C65.8045 31.9615 66.6506 31.36 67.5056 31.5637L82.705 35.1858C83.4649 35.3669 84.0438 36.308 83.8627 37.068L81.6894 46.1876ZM86.036 27.9483C85.8322 28.8033 84.9138 29.2872 84.1538 29.1061L68.9544 25.484C68.0995 25.2802 67.593 24.4568 67.7967 23.6018L69.97 14.4822C70.1511 13.7222 70.9971 13.1207 71.8521 13.3245L87.0515 16.9466C87.8114 17.1277 88.3903 18.0687 88.2092 18.8287L86.036 27.9483ZM92.1479 49.483C92.0347 49.958 91.5868 50.1524 91.2068 50.0619L86.742 48.9979C86.267 48.8847 86.05 48.5318 86.1631 48.0568L87.2498 43.497C87.3403 43.117 87.8109 42.8276 88.1908 42.9181L92.7507 44.0048C93.1306 44.0953 93.4201 44.5659 93.3295 44.9458L92.2429 49.5057L92.1479 49.483ZM95.0456 37.3235C94.9324 37.7985 94.4845 37.9929 94.1045 37.9024L89.6397 36.8384C89.1647 36.7252 88.9476 36.3723 89.0608 35.8973L90.1474 31.3375C90.238 30.9575 90.6135 30.6455 91.0885 30.7586L95.5533 31.8226C95.9333 31.9132 96.2228 32.3837 96.1322 32.7637L95.0456 37.3235ZM97.9432 25.164C97.8301 25.639 97.3822 25.8334 97.0022 25.7429L92.5374 24.6789C92.0624 24.5657 91.8453 24.2128 91.9585 23.7378L93.0451 19.178C93.1357 18.798 93.5112 18.486 93.8912 18.5765L98.356 19.6405C98.736 19.731 99.0254 20.2016 98.9349 20.5816L97.8483 25.1414L97.9432 25.164Z"
fill="#AFAFAF"
></path>
<path
d="M30.7898 24.814L27.2823 9.2672L4.41944 14.4252C2.81904 14.7863 1.95958 16.3017 2.29486 17.7878L14.2615 70.8296C14.6226 72.43 16.0236 73.3153 17.624 72.9542L56.0337 64.2887C57.5198 63.9534 58.5193 62.5266 58.1582 60.9262L49.699 23.4311L34.1523 26.9385C32.5519 27.2996 31.1508 26.4144 30.7898 24.814ZM48.719 19.0871C48.5643 18.4012 48.0666 17.7927 47.4804 17.3243L33.7501 8.64894C33.0754 8.32063 32.3121 8.13243 31.6263 8.28717L30.9404 8.44191L34.2415 23.0742L48.8738 19.773L48.719 19.0871Z"
fill="#0AADFF"
></path>
<rect
x="41.7334"
y="40.3967"
width="37.6309"
height="28.6511"
transform="rotate(6.29289 41.7334 40.3967)"
fill="#66CCFF"
></rect>
<path
d="M76.768 40.4721L44.4638 36.9097C42.3671 36.6785 40.548 38.2071 40.3254 40.2261L37.8591 62.5905C37.6279 64.6872 39.0788 66.4977 41.1755 66.729L73.4796 70.2913C75.4987 70.514 77.3869 69.0716 77.6181 66.9749L80.0843 44.6105C80.307 42.5915 78.787 40.6947 76.768 40.4721ZM73.4248 66.5125L42.0524 63.0529C41.7418 63.0187 41.6036 62.8462 41.6379 62.5356L44.0014 41.103C44.0271 40.8701 44.2081 40.6542 44.5187 40.6885L75.891 44.1481C76.124 44.1738 76.3312 44.4324 76.3056 44.6654L73.9421 66.098C73.9078 66.4086 73.6577 66.5382 73.4248 66.5125ZM49.9226 44.4284C48.1365 44.2314 46.6623 45.4836 46.4739 47.192C46.2769 48.978 47.4514 50.4437 49.2375 50.6407C50.9459 50.8291 52.4892 49.6631 52.6862 47.8771C52.8746 46.1687 51.631 44.6168 49.9226 44.4284ZM45.725 59.6852L70.5743 62.4255L71.2594 56.2131L65.1708 48.7036C64.8254 48.2725 64.2818 48.2126 63.8507 48.558L53.5908 56.7799L50.8186 53.4088C50.4732 52.9777 49.9296 52.9178 49.4985 53.2632L46.136 55.9578L45.725 59.6852Z"
fill="white"
></path>
<path
d="M37.8174 63.0181L77.5892 66.862L77.012 72.8342C76.9057 73.9336 75.9283 74.7388 74.8288 74.6325L39.0385 71.1734C37.939 71.0671 37.1339 70.0897 37.2402 68.9902L37.8174 63.0181Z"
fill="white"
></path>
</svg>
</div>
<div class="chat-upload-drop-zone__text">
<span class="chat-upload-drop-zone__text__title">
{{this.title}}
</span>
</div>
</div>
</div>
</template>
} }

View File

@ -1,65 +0,0 @@
<div class="chat-upload-drop-zone">
<div class="chat-upload-drop-zone__content">
<div class="chat-upload-drop-zone__background">
<svg
width="94"
height="90"
viewBox="0 0 94 90"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M94 40.5591C94 69.8685 64.0686 90 40.9592 90C17.8499 90 0 83.9085 0 60.6907C0 37.4729 28.458 0 51.5674 0C74.6768 0 94 17.3413 94 40.5591Z"
fill="#D1F0FF"
></path>
</svg>
</div>
<div class="chat-upload-drop-zone__illustration">
<svg
width="106"
height="84"
viewBox="0 0 106 84"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect
x="55.832"
y="6.82959"
width="45.7854"
height="33.8408"
transform="rotate(13.4039 55.832 6.82959)"
fill="#D9D9D9"
></rect>
<path
d="M100.66 13.7645L61.1414 4.34705C58.6715 3.75846 56.1786 5.37298 55.59 7.84288L47.6214 41.2815C47.0102 43.8464 48.5297 46.3167 50.9996 46.9053L90.518 56.3227C92.9879 56.9113 95.5532 55.4145 96.1644 52.8496L104.133 19.4109C104.722 16.941 103.225 14.3757 100.66 13.7645ZM58.7093 41.5144C58.5961 41.9894 58.1482 42.1838 57.7682 42.0933L53.2084 41.0067C52.7334 40.8935 52.5163 40.5406 52.6295 40.0656L53.7161 35.5058C53.8067 35.1258 54.1822 34.8137 54.6572 34.9269L59.122 35.9909C59.502 36.0815 59.7915 36.552 59.7009 36.932L58.7093 41.5144ZM61.6069 29.3549C61.4938 29.8299 61.0459 30.0243 60.6659 29.9338L56.1061 28.8472C55.6311 28.734 55.414 28.3811 55.5272 27.9061L56.6138 23.3463C56.7044 22.9663 57.0799 22.6542 57.5549 22.7674L62.0197 23.8314C62.3997 23.922 62.6891 24.3925 62.5986 24.7725L61.5119 29.3323L61.6069 29.3549ZM64.5046 17.1954C64.3914 17.6704 63.9435 17.8648 63.5635 17.7743L59.0037 16.6877C58.5287 16.5745 58.3117 16.2216 58.4249 15.7466L59.5115 11.1868C59.602 10.8068 59.9776 10.4947 60.4526 10.6079L64.9174 11.6719C65.2974 11.7625 65.5868 12.233 65.4962 12.613L64.5046 17.1954ZM81.6894 46.1876C81.4857 47.0426 80.5673 47.5264 79.8073 47.3453L64.6079 43.7232C63.753 43.5195 63.2464 42.6961 63.4502 41.8411L65.6234 32.7215C65.8045 31.9615 66.6506 31.36 67.5056 31.5637L82.705 35.1858C83.4649 35.3669 84.0438 36.308 83.8627 37.068L81.6894 46.1876ZM86.036 27.9483C85.8322 28.8033 84.9138 29.2872 84.1538 29.1061L68.9544 25.484C68.0995 25.2802 67.593 24.4568 67.7967 23.6018L69.97 14.4822C70.1511 13.7222 70.9971 13.1207 71.8521 13.3245L87.0515 16.9466C87.8114 17.1277 88.3903 18.0687 88.2092 18.8287L86.036 27.9483ZM92.1479 49.483C92.0347 49.958 91.5868 50.1524 91.2068 50.0619L86.742 48.9979C86.267 48.8847 86.05 48.5318 86.1631 48.0568L87.2498 43.497C87.3403 43.117 87.8109 42.8276 88.1908 42.9181L92.7507 44.0048C93.1306 44.0953 93.4201 44.5659 93.3295 44.9458L92.2429 49.5057L92.1479 49.483ZM95.0456 37.3235C94.9324 37.7985 94.4845 37.9929 94.1045 37.9024L89.6397 36.8384C89.1647 36.7252 88.9476 36.3723 89.0608 35.8973L90.1474 31.3375C90.238 30.9575 90.6135 30.6455 91.0885 30.7586L95.5533 31.8226C95.9333 31.9132 96.2228 32.3837 96.1322 32.7637L95.0456 37.3235ZM97.9432 25.164C97.8301 25.639 97.3822 25.8334 97.0022 25.7429L92.5374 24.6789C92.0624 24.5657 91.8453 24.2128 91.9585 23.7378L93.0451 19.178C93.1357 18.798 93.5112 18.486 93.8912 18.5765L98.356 19.6405C98.736 19.731 99.0254 20.2016 98.9349 20.5816L97.8483 25.1414L97.9432 25.164Z"
fill="#AFAFAF"
></path>
<path
d="M30.7898 24.814L27.2823 9.2672L4.41944 14.4252C2.81904 14.7863 1.95958 16.3017 2.29486 17.7878L14.2615 70.8296C14.6226 72.43 16.0236 73.3153 17.624 72.9542L56.0337 64.2887C57.5198 63.9534 58.5193 62.5266 58.1582 60.9262L49.699 23.4311L34.1523 26.9385C32.5519 27.2996 31.1508 26.4144 30.7898 24.814ZM48.719 19.0871C48.5643 18.4012 48.0666 17.7927 47.4804 17.3243L33.7501 8.64894C33.0754 8.32063 32.3121 8.13243 31.6263 8.28717L30.9404 8.44191L34.2415 23.0742L48.8738 19.773L48.719 19.0871Z"
fill="#0AADFF"
></path>
<rect
x="41.7334"
y="40.3967"
width="37.6309"
height="28.6511"
transform="rotate(6.29289 41.7334 40.3967)"
fill="#66CCFF"
></rect>
<path
d="M76.768 40.4721L44.4638 36.9097C42.3671 36.6785 40.548 38.2071 40.3254 40.2261L37.8591 62.5905C37.6279 64.6872 39.0788 66.4977 41.1755 66.729L73.4796 70.2913C75.4987 70.514 77.3869 69.0716 77.6181 66.9749L80.0843 44.6105C80.307 42.5915 78.787 40.6947 76.768 40.4721ZM73.4248 66.5125L42.0524 63.0529C41.7418 63.0187 41.6036 62.8462 41.6379 62.5356L44.0014 41.103C44.0271 40.8701 44.2081 40.6542 44.5187 40.6885L75.891 44.1481C76.124 44.1738 76.3312 44.4324 76.3056 44.6654L73.9421 66.098C73.9078 66.4086 73.6577 66.5382 73.4248 66.5125ZM49.9226 44.4284C48.1365 44.2314 46.6623 45.4836 46.4739 47.192C46.2769 48.978 47.4514 50.4437 49.2375 50.6407C50.9459 50.8291 52.4892 49.6631 52.6862 47.8771C52.8746 46.1687 51.631 44.6168 49.9226 44.4284ZM45.725 59.6852L70.5743 62.4255L71.2594 56.2131L65.1708 48.7036C64.8254 48.2725 64.2818 48.2126 63.8507 48.558L53.5908 56.7799L50.8186 53.4088C50.4732 52.9777 49.9296 52.9178 49.4985 53.2632L46.136 55.9578L45.725 59.6852Z"
fill="white"
></path>
<path
d="M37.8174 63.0181L77.5892 66.862L77.012 72.8342C76.9057 73.9336 75.9283 74.7388 74.8288 74.6325L39.0385 71.1734C37.939 71.0671 37.1339 70.0897 37.2402 68.9902L37.8174 63.0181Z"
fill="white"
></path>
</svg>
</div>
<div class="chat-upload-drop-zone__text">
<span class="chat-upload-drop-zone__text__title">
{{this.title}}
</span>
</div>
</div>
</div>

View File

@ -1,9 +1,11 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking"; import { tracked } from "@glimmer/tracking";
import { on } from "@ember/modifier";
import { action } from "@ember/object"; import { action } from "@ember/object";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import { htmlSafe } from "@ember/template"; import { htmlSafe } from "@ember/template";
import { isAudio, isImage, isVideo } from "discourse/lib/uploads"; import { isAudio, isImage, isVideo } from "discourse/lib/uploads";
import eq from "truth-helpers/helpers/eq";
export default class extends Component { export default class extends Component {
@service siteSettings; @service siteSettings;
@ -52,4 +54,37 @@ export default class extends Component {
imageLoaded() { imageLoaded() {
this.loaded = true; this.loaded = true;
} }
<template>
{{#if (eq this.type this.IMAGE_TYPE)}}
<img
class="chat-img-upload"
data-orig-src={{@upload.short_url}}
height={{this.size.height}}
width={{this.size.width}}
src={{@upload.url}}
style={{this.imageStyle}}
loading="lazy"
tabindex="0"
data-dominant-color={{@upload.dominant_color}}
{{on "load" this.imageLoaded}}
/>
{{else if (eq this.type this.VIDEO_TYPE)}}
<video class="chat-video-upload" preload="metadata" height="150" controls>
<source src={{@upload.url}} />
</video>
{{else if (eq this.type this.AUDIO_TYPE)}}
<audio class="chat-audio-upload" preload="metadata" controls>
<source src={{@upload.url}} />
</audio>
{{else}}
<a
class="chat-other-upload"
data-orig-href={{@upload.short_url}}
href={{@upload.url}}
>
{{@upload.original_filename}}
</a>
{{/if}}
</template>
} }

View File

@ -1,30 +0,0 @@
{{#if (eq this.type this.IMAGE_TYPE)}}
<img
class="chat-img-upload"
data-orig-src={{@upload.short_url}}
height={{this.size.height}}
width={{this.size.width}}
src={{@upload.url}}
style={{this.imageStyle}}
loading="lazy"
tabindex="0"
data-dominant-color={{@upload.dominant_color}}
{{on "load" this.imageLoaded}}
/>
{{else if (eq this.type this.VIDEO_TYPE)}}
<video class="chat-video-upload" preload="metadata" height="150" controls>
<source src={{@upload.url}} />
</video>
{{else if (eq this.type this.AUDIO_TYPE)}}
<audio class="chat-audio-upload" preload="metadata" controls>
<source src={{@upload.url}} />
</audio>
{{else}}
<a
class="chat-other-upload"
data-orig-href={{@upload.short_url}}
href={{@upload.url}}
>
{{@upload.original_filename}}
</a>
{{/if}}

View File

@ -1,5 +1,6 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import concatClass from "discourse/helpers/concat-class";
import { formatUsername } from "discourse/lib/utilities"; import { formatUsername } from "discourse/lib/utilities";
export default class ChatUserDisplayName extends Component { export default class ChatUserDisplayName extends Component {
@ -24,4 +25,27 @@ export default class ChatUserDisplayName extends Component {
get shouldShowNameLast() { get shouldShowNameLast() {
return !this.shouldPrioritizeNameInUx && this.hasValidName; return !this.shouldPrioritizeNameInUx && this.hasValidName;
} }
<template>
<span class="chat-user-display-name">
{{#if this.shouldShowNameFirst}}
<span class="chat-user-display-name__name -first">{{@user.name}}</span>
<span class="separator"></span>
{{/if}}
<span
class={{concatClass
"chat-user-display-name__username"
(unless this.shouldShowNameFirst "-first")
}}
>
{{this.formattedUsername}}
</span>
{{#if this.shouldShowNameLast}}
<span class="separator"></span>
<span class="chat-user-display-name__name">{{@user.name}}</span>
{{/if}}
</span>
</template>
} }

View File

@ -1,20 +0,0 @@
<span class="chat-user-display-name">
{{#if this.shouldShowNameFirst}}
<span class="chat-user-display-name__name -first">{{@user.name}}</span>
<span class="separator">—</span>
{{/if}}
<span
class={{concat-class
"chat-user-display-name__username"
(unless this.shouldShowNameFirst "-first")
}}
>
{{this.formattedUsername}}
</span>
{{#if this.shouldShowNameLast}}
<span class="separator">—</span>
<span class="chat-user-display-name__name">{{@user.name}}</span>
{{/if}}
</span>

View File

@ -1,7 +1,9 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import { action } from "@ember/object"; import { action } from "@ember/object";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import DButton from "discourse/components/d-button";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
import i18n from "discourse-common/helpers/i18n";
import I18n from "discourse-i18n"; import I18n from "discourse-i18n";
export default class ChatAdminExportMessages extends Component { export default class ChatAdminExportMessages extends Component {
@ -19,4 +21,17 @@ export default class ChatAdminExportMessages extends Component {
popupAjaxError(error); popupAjaxError(error);
} }
} }
<template>
<section class="admin-section">
<h3>{{i18n "chat.admin.export_messages.title"}}</h3>
<p>{{i18n "chat.admin.export_messages.description"}}</p>
<DButton
@label="chat.admin.export_messages.create_export"
@title="chat.admin.export_messages.create_export"
@action={{this.exportMessages}}
class="btn-primary"
/>
</section>
</template>
} }

View File

@ -1,10 +0,0 @@
<section class="admin-section">
<h3>{{i18n "chat.admin.export_messages.title"}}</h3>
<p>{{i18n "chat.admin.export_messages.description"}}</p>
<DButton
@label="chat.admin.export_messages.create_export"
@title="chat.admin.export_messages.create_export"
@action={{this.exportMessages}}
class="btn-primary"
/>
</section>

View File

@ -1,3 +1,12 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import dIcon from "discourse-common/helpers/d-icon";
export default class ChatComposerButton extends Component {} export default class ChatComposerButton extends Component {
<template>
<div class="chat-composer-button__wrapper">
<button type="button" class="chat-composer-button" ...attributes>
{{dIcon @icon}}
</button>
</div>
</template>
}

View File

@ -1,5 +0,0 @@
<div class="chat-composer-button__wrapper">
<button type="button" class="chat-composer-button" ...attributes>
{{d-icon @icon}}
</button>
</div>

View File

@ -1,3 +1,7 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
export default class ChatComposerSeparator extends Component {} export default class ChatComposerSeparator extends Component {
<template>
<div class="chat-composer-separator"></div>
</template>
}

View File

@ -1 +0,0 @@
<div class="chat-composer-separator"></div>

View File

@ -1,4 +1,15 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import ChatEmojiAvatar from "../../chat-emoji-avatar";
import ChatUserAvatar from "../../chat-user-avatar";
export default class extends Component { export default class extends Component {
<template>
<div class="chat-message-avatar">
{{#if @message.chatWebhookEvent.emoji}}
<ChatEmojiAvatar @emoji={{@message.chatWebhookEvent.emoji}} />
{{else}}
<ChatUserAvatar @user={{@message.user}} @avatarSize="medium" />
{{/if}}
</div>
</template>
} }

View File

@ -1,7 +0,0 @@
<div class="chat-message-avatar">
{{#if @message.chatWebhookEvent.emoji}}
<ChatEmojiAvatar @emoji={{@message.chatWebhookEvent.emoji}} />
{{else}}
<ChatUserAvatar @user={{@message.user}} @avatarSize="medium" />
{{/if}}
</div>

View File

@ -1,4 +1,30 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import { fn } from "@ember/helper";
import DButton from "discourse/components/d-button";
import i18n from "discourse-common/helpers/i18n";
import eq from "truth-helpers/helpers/eq";
export default class extends Component { export default class extends Component {
<template>
{{#if @message.error}}
<div class="chat-message-error">
{{#if (eq @message.error "network_error")}}
<DButton
class="chat-message-error__retry-btn"
@action={{fn @onRetry @message}}
@icon="exclamation-circle"
>
<span class="chat-message-error__retry-btn-title">
{{i18n "chat.retry_staged_message.title"}}
</span>
<span class="chat-message-error__retry-btn-action">
{{i18n "chat.retry_staged_message.action"}}
</span>
</DButton>
{{else}}
{{@message.error}}
{{/if}}
</div>
{{/if}}
</template>
} }

View File

@ -1,20 +0,0 @@
{{#if @message.error}}
<div class="chat-message-error">
{{#if (eq @message.error "network_error")}}
<DButton
class="chat-message-error__retry-btn"
@action={{fn @onRetry @message}}
@icon="exclamation-circle"
>
<span class="chat-message-error__retry-btn-title">
{{i18n "chat.retry_staged_message.title"}}
</span>
<span class="chat-message-error__retry-btn-action">
{{i18n "chat.retry_staged_message.action"}}
</span>
</DButton>
{{else}}
{{@message.error}}
{{/if}}
</div>
{{/if}}

View File

@ -1,7 +1,16 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import willDestroy from "@ember/render-modifiers/modifiers/will-destroy";
import { LinkTo } from "@ember/routing";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import BookmarkIcon from "discourse/components/bookmark-icon";
import UserStatusMessage from "discourse/components/user-status-message";
import concatClass from "discourse/helpers/concat-class";
import { prioritizeNameInUx } from "discourse/lib/settings"; import { prioritizeNameInUx } from "discourse/lib/settings";
import dIcon from "discourse-common/helpers/d-icon";
import i18n from "discourse-common/helpers/i18n";
import { bind } from "discourse-common/utils/decorators"; import { bind } from "discourse-common/utils/decorators";
import formatChatDate from "../../../helpers/format-chat-date";
export default class ChatMessageInfo extends Component { export default class ChatMessageInfo extends Component {
@service siteSettings; @service siteSettings;
@ -69,4 +78,70 @@ export default class ChatMessageInfo extends Component {
get #message() { get #message() {
return this.args.message; return this.args.message;
} }
<template>
{{#if @show}}
<div
class="chat-message-info"
{{didInsert this.trackStatus}}
{{willDestroy this.stopTrackingStatus}}
>
{{#if @message.chatWebhookEvent}}
{{#if @message.chatWebhookEvent.username}}
<span
class={{concatClass
"chat-message-info__username"
this.usernameClasses
}}
>
{{@message.chatWebhookEvent.username}}
</span>
{{/if}}
<span class="chat-message-info__bot-indicator">
{{i18n "chat.bot"}}
</span>
{{else}}
<span
role="button"
class={{concatClass
"chat-message-info__username"
this.usernameClasses
"clickable"
}}
data-user-card={{@message.user.username}}
>
<span class="chat-message-info__username__name">{{this.name}}</span>
{{#if this.showStatus}}
<div class="chat-message-info__status">
<UserStatusMessage @status={{@message.user.status}} />
</div>
{{/if}}
</span>
{{/if}}
<span class="chat-message-info__date">
{{formatChatDate @message}}
</span>
{{#if @message.bookmark}}
<span class="chat-message-info__bookmark">
<BookmarkIcon @bookmark={{@message.bookmark}} />
</span>
{{/if}}
{{#if this.isFlagged}}
<span class="chat-message-info__flag">
{{#if @message.reviewableId}}
<LinkTo @route="review.show" @model={{@message.reviewableId}}>
{{dIcon "flag" title="chat.flagged"}}
</LinkTo>
{{else}}
{{dIcon "flag" title="chat.you_flagged"}}
{{/if}}
</span>
{{/if}}
</div>
{{/if}}
</template>
} }

View File

@ -1,63 +0,0 @@
{{#if @show}}
<div
class="chat-message-info"
{{did-insert this.trackStatus}}
{{will-destroy this.stopTrackingStatus}}
>
{{#if @message.chatWebhookEvent}}
{{#if @message.chatWebhookEvent.username}}
<span
class={{concat-class
"chat-message-info__username"
this.usernameClasses
}}
>
{{@message.chatWebhookEvent.username}}
</span>
{{/if}}
<span class="chat-message-info__bot-indicator">
{{i18n "chat.bot"}}
</span>
{{else}}
<span
role="button"
class={{concat-class
"chat-message-info__username"
this.usernameClasses
"clickable"
}}
data-user-card={{@message.user.username}}
>
<span class="chat-message-info__username__name">{{this.name}}</span>
{{#if this.showStatus}}
<div class="chat-message-info__status">
<UserStatusMessage @status={{@message.user.status}} />
</div>
{{/if}}
</span>
{{/if}}
<span class="chat-message-info__date">
{{format-chat-date @message}}
</span>
{{#if @message.bookmark}}
<span class="chat-message-info__bookmark">
<BookmarkIcon @bookmark={{@message.bookmark}} />
</span>
{{/if}}
{{#if this.isFlagged}}
<span class="chat-message-info__flag">
{{#if @message.reviewableId}}
<LinkTo @route="review.show" @model={{@message.reviewableId}}>
{{d-icon "flag" title="chat.flagged"}}
</LinkTo>
{{else}}
{{d-icon "flag" title="chat.you_flagged"}}
{{/if}}
</span>
{{/if}}
</div>
{{/if}}

View File

@ -1,6 +1,38 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import { LinkTo } from "@ember/routing";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import BookmarkIcon from "discourse/components/bookmark-icon";
import dIcon from "discourse-common/helpers/d-icon";
import eq from "truth-helpers/helpers/eq";
import formatChatDate from "../../../helpers/format-chat-date";
export default class ChatMessageLeftGutter extends Component { export default class ChatMessageLeftGutter extends Component {
@service site; @service site;
<template>
<div class="chat-message-left-gutter">
{{#if @message.reviewableId}}
<LinkTo
@route="review.show"
@model={{@message.reviewableId}}
class="chat-message-left-gutter__flag"
>
{{dIcon "flag" title="chat.flagged"}}
</LinkTo>
{{else if (eq @message.userFlagStatus 0)}}
<div class="chat-message-left-gutter__flag">
{{dIcon "flag" title="chat.you_flagged"}}
</div>
{{else if this.site.desktopView}}
<span class="chat-message-left-gutter__date">
{{formatChatDate @message "tiny"}}
</span>
{{/if}}
{{#if @message.bookmark}}
<span class="chat-message-left-gutter__bookmark">
<BookmarkIcon @bookmark={{@message.bookmark}} />
</span>
{{/if}}
</div>
</template>
} }

View File

@ -1,24 +0,0 @@
<div class="chat-message-left-gutter">
{{#if @message.reviewableId}}
<LinkTo
@route="review.show"
@model={{@message.reviewableId}}
class="chat-message-left-gutter__flag"
>
{{d-icon "flag" title="chat.flagged"}}
</LinkTo>
{{else if (eq @message.userFlagStatus 0)}}
<div class="chat-message-left-gutter__flag">
{{d-icon "flag" title="chat.you_flagged"}}
</div>
{{else if this.site.desktopView}}
<span class="chat-message-left-gutter__date">
{{format-chat-date @message "tiny"}}
</span>
{{/if}}
{{#if @message.bookmark}}
<span class="chat-message-left-gutter__bookmark">
<BookmarkIcon @bookmark={{@message.bookmark}} />
</span>
{{/if}}
</div>

View File

@ -4,7 +4,10 @@ import { action } from "@ember/object";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import { htmlSafe } from "@ember/template"; import { htmlSafe } from "@ember/template";
import { isEmpty } from "@ember/utils"; import { isEmpty } from "@ember/utils";
import DButton from "discourse/components/d-button";
import DModal from "discourse/components/d-modal";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
import i18n from "discourse-common/helpers/i18n";
import discourseLater from "discourse-common/lib/later"; import discourseLater from "discourse-common/lib/later";
import I18n from "discourse-i18n"; import I18n from "discourse-i18n";
import { import {
@ -12,6 +15,7 @@ import {
NEW_TOPIC_SELECTION, NEW_TOPIC_SELECTION,
} from "discourse/plugins/chat/discourse/components/chat-to-topic-selector"; } from "discourse/plugins/chat/discourse/components/chat-to-topic-selector";
import { CHANNEL_STATUSES } from "discourse/plugins/chat/discourse/models/chat-channel"; import { CHANNEL_STATUSES } from "discourse/plugins/chat/discourse/models/chat-channel";
import ChatToTopicSelector from "../../chat-to-topic-selector";
export default class ChatModalArchiveChannel extends Component { export default class ChatModalArchiveChannel extends Component {
@service chatApi; @service chatApi;
@ -109,4 +113,39 @@ export default class ChatModalArchiveChannel extends Component {
} }
return data; return data;
} }
<template>
<DModal
@closeModal={{@closeModal}}
class="chat-modal-archive-channel"
@inline={{@inline}}
@title={{i18n "chat.channel_archive.title"}}
@flash={{this.flash}}
@flashType={{this.flashType}}
>
<:body>
<p class="chat-modal-archive-channel__instructions">
{{this.instructionsText}}
</p>
<ChatToTopicSelector
@selection={{this.selection}}
@topicTitle={{this.topicTitle}}
@categoryId={{this.categoryId}}
@tags={{this.tags}}
@selectedTopicId={{this.selectedTopicId}}
@instructionLabels={{this.instructionLabels}}
@allowNewMessage={{false}}
/>
</:body>
<:footer>
<DButton
@disabled={{this.buttonDisabled}}
@action={{this.archiveChannel}}
@label="chat.channel_archive.title"
id="chat-confirm-archive-channel"
class="btn-primary"
/>
</:footer>
</DModal>
</template>
} }

View File

@ -1,32 +0,0 @@
<DModal
@closeModal={{@closeModal}}
class="chat-modal-archive-channel"
@inline={{@inline}}
@title={{i18n "chat.channel_archive.title"}}
@flash={{this.flash}}
@flashType={{this.flashType}}
>
<:body>
<p class="chat-modal-archive-channel__instructions">
{{this.instructionsText}}
</p>
<ChatToTopicSelector
@selection={{this.selection}}
@topicTitle={{this.topicTitle}}
@categoryId={{this.categoryId}}
@tags={{this.tags}}
@selectedTopicId={{this.selectedTopicId}}
@instructionLabels={{this.instructionLabels}}
@allowNewMessage={{false}}
/>
</:body>
<:footer>
<DButton
@disabled={{this.buttonDisabled}}
@action={{this.archiveChannel}}
@label="chat.channel_archive.title"
id="chat-confirm-archive-channel"
class="btn-primary"
/>
</:footer>
</DModal>

View File

@ -2,8 +2,13 @@ import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking"; import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object"; import { action } from "@ember/object";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import ConditionalLoadingSection from "discourse/components/conditional-loading-section";
import DModal from "discourse/components/d-modal";
import DModalCancel from "discourse/components/d-modal-cancel";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
import i18n from "discourse-common/helpers/i18n";
import I18n from "discourse-i18n"; import I18n from "discourse-i18n";
import ComboBox from "select-kit/components/combo-box";
export default class ChatModalChannelSummary extends Component { export default class ChatModalChannelSummary extends Component {
@service chatApi; @service chatApi;
@ -45,4 +50,29 @@ export default class ChatModalChannelSummary extends Component {
.catch(popupAjaxError) .catch(popupAjaxError)
.finally(() => (this.loading = false)); .finally(() => (this.loading = false));
} }
<template>
<DModal
@closeModal={{@closeModal}}
class="chat-modal-channel-summary"
@title={{i18n "chat.summarization.title"}}
>
<:body>
<span>{{i18n "chat.summarization.description"}}</span>
<ComboBox
@value={{this.sinceHours}}
@content={{this.sinceOptions}}
@onChange={{this.summarize}}
@valueProperty="value"
@class="summarization-since"
/>
<ConditionalLoadingSection @isLoading={{this.loading}}>
<p class="summary-area">{{this.summary}}</p>
</ConditionalLoadingSection>
</:body>
<:footer>
<DModalCancel @close={{@closeModal}} />
</:footer>
</DModal>
</template>
} }

View File

@ -1,22 +0,0 @@
<DModal
@closeModal={{@closeModal}}
class="chat-modal-channel-summary"
@title={{i18n "chat.summarization.title"}}
>
<:body>
<span>{{i18n "chat.summarization.description"}}</span>
<ComboBox
@value={{this.sinceHours}}
@content={{this.sinceOptions}}
@onChange={{this.summarize}}
@valueProperty="value"
@class="summarization-since"
/>
<ConditionalLoadingSection @isLoading={{this.loading}}>
<p class="summary-area">{{this.summary}}</p>
</ConditionalLoadingSection>
</:body>
<:footer>
<DModalCancel @close={{@closeModal}} />
</:footer>
</DModal>

View File

@ -4,7 +4,11 @@ import { action } from "@ember/object";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import { htmlSafe } from "@ember/template"; import { htmlSafe } from "@ember/template";
import { isEmpty } from "@ember/utils"; import { isEmpty } from "@ember/utils";
import DButton from "discourse/components/d-button";
import DModal from "discourse/components/d-modal";
import TextField from "discourse/components/text-field";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
import i18n from "discourse-common/helpers/i18n";
import discourseLater from "discourse-common/lib/later"; import discourseLater from "discourse-common/lib/later";
import I18n from "discourse-i18n"; import I18n from "discourse-i18n";
@ -65,4 +69,39 @@ export default class ChatModalDeleteChannel extends Component {
.catch(popupAjaxError) .catch(popupAjaxError)
.finally(() => (this.deleting = false)); .finally(() => (this.deleting = false));
} }
<template>
<DModal
@closeModal={{@closeModal}}
class="chat-modal-delete-channel"
@inline={{@inline}}
@title={{i18n "chat.channel_delete.title"}}
@flash={{this.flash}}
@flashType={{this.flashType}}
>
<:body>
<p class="chat-modal-delete-channel__instructions">
{{this.instructionsText}}
</p>
<TextField
@value={{this.channelNameConfirmation}}
@id="channel-delete-confirm-name"
@placeholderKey="chat.channel_delete.confirm_channel_name"
@autocorrect="off"
@autocapitalize="off"
/>
</:body>
<:footer>
<DButton
@disabled={{this.buttonDisabled}}
@action={{this.deleteChannel}}
@label="chat.channel_delete.confirm"
id="chat-confirm-delete-channel"
class="btn-danger"
/>
<DButton @label="cancel" @action={{@closeModal}} class="btn-flat" />
</:footer>
</DModal>
</template>
} }

View File

@ -1,32 +0,0 @@
<DModal
@closeModal={{@closeModal}}
class="chat-modal-delete-channel"
@inline={{@inline}}
@title={{i18n "chat.channel_delete.title"}}
@flash={{this.flash}}
@flashType={{this.flashType}}
>
<:body>
<p class="chat-modal-delete-channel__instructions">
{{this.instructionsText}}
</p>
<TextField
@value={{this.channelNameConfirmation}}
@id="channel-delete-confirm-name"
@placeholderKey="chat.channel_delete.confirm_channel_name"
@autocorrect="off"
@autocapitalize="off"
/>
</:body>
<:footer>
<DButton
@disabled={{this.buttonDisabled}}
@action={{this.deleteChannel}}
@label="chat.channel_delete.confirm"
id="chat-confirm-delete-channel"
class="btn-danger"
/>
<DButton @label="cancel" @action={{@closeModal}} class="btn-flat" />
</:footer>
</DModal>

View File

@ -1,11 +1,18 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking"; import { tracked } from "@glimmer/tracking";
import { Input } from "@ember/component";
import { on } from "@ember/modifier";
import { action } from "@ember/object"; import { action } from "@ember/object";
import { cancel } from "@ember/runloop"; import { cancel } from "@ember/runloop";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import DButton from "discourse/components/d-button";
import DModal from "discourse/components/d-modal";
import DModalCancel from "discourse/components/d-modal-cancel";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import { extractError } from "discourse/lib/ajax-error"; import { extractError } from "discourse/lib/ajax-error";
import i18n from "discourse-common/helpers/i18n";
import discourseDebounce from "discourse-common/lib/debounce"; import discourseDebounce from "discourse-common/lib/debounce";
import DTooltip from "float-kit/components/d-tooltip";
import slugifyChannel from "discourse/plugins/chat/discourse/lib/slugify-channel"; import slugifyChannel from "discourse/plugins/chat/discourse/lib/slugify-channel";
const SLUG_MAX_LENGTH = 100; const SLUG_MAX_LENGTH = 100;
@ -96,4 +103,61 @@ export default class ChatModalEditChannelName extends Component {
console.log(error); console.log(error);
} }
} }
<template>
<DModal
@closeModal={{@closeModal}}
class="chat-modal-edit-channel-name"
@inline={{@inline}}
@title={{i18n "chat.channel_edit_name_slug_modal.title"}}
@flash={{this.flash}}
>
<:body>
<div class="edit-channel-control">
<label for="channel-name" class="edit-channel-label">
{{i18n "chat.channel_edit_name_slug_modal.name"}}
</label>
<Input
name="channel-name"
class="chat-channel-edit-name-slug-modal__name-input"
placeholder={{i18n
"chat.channel_edit_name_slug_modal.input_placeholder"
}}
@type="text"
@value={{this.editedName}}
{{on "input" this.onChangeChatChannelName}}
/>
</div>
<div class="edit-channel-control">
<label for="channel-slug" class="edit-channel-label">
{{i18n "chat.channel_edit_name_slug_modal.slug"}}&nbsp;
<DTooltip
@icon="info-circle"
@content={{i18n
"chat.channel_edit_name_slug_modal.slug_description"
}}
/>
</label>
<Input
name="channel-slug"
class="chat-channel-edit-name-slug-modal__slug-input"
placeholder={{this.autoGeneratedSlug}}
{{on "input" this.onChangeChatChannelSlug}}
@type="text"
@value={{this.editedSlug}}
/>
</div>
</:body>
<:footer>
<DButton
@action={{this.onSave}}
@label="save"
@disabled={{this.isSaveDisabled}}
class="btn-primary create"
/>
<DModalCancel @close={{@closeModal}} />
</:footer>
</DModal>
</template>
} }

View File

@ -1,52 +0,0 @@
<DModal
@closeModal={{@closeModal}}
class="chat-modal-edit-channel-name"
@inline={{@inline}}
@title={{i18n "chat.channel_edit_name_slug_modal.title"}}
@flash={{this.flash}}
>
<:body>
<div class="edit-channel-control">
<label for="channel-name" class="edit-channel-label">
{{i18n "chat.channel_edit_name_slug_modal.name"}}
</label>
<Input
name="channel-name"
class="chat-channel-edit-name-slug-modal__name-input"
placeholder={{i18n
"chat.channel_edit_name_slug_modal.input_placeholder"
}}
@type="text"
@value={{this.editedName}}
{{on "input" this.onChangeChatChannelName}}
/>
</div>
<div class="edit-channel-control">
<label for="channel-slug" class="edit-channel-label">
{{i18n "chat.channel_edit_name_slug_modal.slug"}}&nbsp;
<DTooltip
@icon="info-circle"
@content={{i18n "chat.channel_edit_name_slug_modal.slug_description"}}
/>
</label>
<Input
name="channel-slug"
class="chat-channel-edit-name-slug-modal__slug-input"
placeholder={{this.autoGeneratedSlug}}
{{on "input" this.onChangeChatChannelSlug}}
@type="text"
@value={{this.editedSlug}}
/>
</div>
</:body>
<:footer>
<DButton
@action={{this.onSave}}
@label="save"
@disabled={{this.isSaveDisabled}}
class="btn-primary create"
/>
<DModalCancel @close={{@closeModal}} />
</:footer>
</DModal>

View File

@ -4,8 +4,12 @@ import { action } from "@ember/object";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import { htmlSafe } from "@ember/template"; import { htmlSafe } from "@ember/template";
import { isBlank } from "@ember/utils"; import { isBlank } from "@ember/utils";
import DButton from "discourse/components/d-button";
import DModal from "discourse/components/d-modal";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
import i18n from "discourse-common/helpers/i18n";
import I18n from "discourse-i18n"; import I18n from "discourse-i18n";
import ChatChannelChooser from "../../chat-channel-chooser";
export default class ChatModalMoveMessageToChannel extends Component { export default class ChatModalMoveMessageToChannel extends Component {
@service chat; @service chat;
@ -65,4 +69,36 @@ export default class ChatModalMoveMessageToChannel extends Component {
}) })
.catch(popupAjaxError); .catch(popupAjaxError);
} }
<template>
<DModal
@closeModal={{@closeModal}}
class="chat-modal-move-message-to-channel"
@inline={{@inline}}
@title={{i18n "chat.move_to_channel.title"}}
>
<:body>
{{#if this.selectedMessageCount}}
<p>{{this.instructionsText}}</p>
{{/if}}
<ChatChannelChooser
@class="chat-modal-move-message-to-channel__channel-chooser"
@content={{this.availableChannels}}
@value={{this.destinationChannelId}}
@nameProperty="title"
/>
</:body>
<:footer>
<DButton
@icon="sign-out-alt"
@disabled={{this.disableMoveButton}}
@action={{this.moveMessages}}
@label="chat.move_to_channel.confirm_move"
class="btn-primary"
/>
<DButton @label="cancel" @action={{@closeModal}} class="btn-flat" />
</:footer>
</DModal>
</template>
} }

View File

@ -1,29 +0,0 @@
<DModal
@closeModal={{@closeModal}}
class="chat-modal-move-message-to-channel"
@inline={{@inline}}
@title={{i18n "chat.move_to_channel.title"}}
>
<:body>
{{#if this.selectedMessageCount}}
<p>{{this.instructionsText}}</p>
{{/if}}
<ChatChannelChooser
@class="chat-modal-move-message-to-channel__channel-chooser"
@content={{this.availableChannels}}
@value={{this.destinationChannelId}}
@nameProperty="title"
/>
</:body>
<:footer>
<DButton
@icon="sign-out-alt"
@disabled={{this.disableMoveButton}}
@action={{this.moveMessages}}
@label="chat.move_to_channel.confirm_move"
class="btn-primary"
/>
<DButton @label="cancel" @action={{@closeModal}} class="btn-flat" />
</:footer>
</DModal>

View File

@ -1,8 +1,12 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking"; import { tracked } from "@glimmer/tracking";
import { Input } from "@ember/component";
import { action } from "@ember/object"; import { action } from "@ember/object";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import DButton from "discourse/components/d-button";
import DModal from "discourse/components/d-modal";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
import i18n from "discourse-common/helpers/i18n";
export default class ChatModalThreadSettings extends Component { export default class ChatModalThreadSettings extends Component {
@service chatApi; @service chatApi;
@ -35,4 +39,33 @@ export default class ChatModalThreadSettings extends Component {
this.saving = false; this.saving = false;
}); });
} }
<template>
<DModal
@closeModal={{@closeModal}}
class="chat-modal-thread-settings"
@inline={{@inline}}
@title={{i18n "chat.thread.settings"}}
>
<:body>
<label for="thread-title" class="thread-title-label">
{{i18n "chat.thread.title"}}
</label>
<Input
name="thread-title"
class="chat-modal-thread-settings__title-input"
@type="text"
@value={{this.editedTitle}}
/>
</:body>
<:footer>
<DButton
@disabled={{this.buttonDisabled}}
@action={{this.saveThread}}
@label="save"
class="btn-primary"
/>
</:footer>
</DModal>
</template>
} }

View File

@ -1,26 +0,0 @@
<DModal
@closeModal={{@closeModal}}
class="chat-modal-thread-settings"
@inline={{@inline}}
@title={{i18n "chat.thread.settings"}}
>
<:body>
<label for="thread-title" class="thread-title-label">
{{i18n "chat.thread.title"}}
</label>
<Input
name="thread-title"
class="chat-modal-thread-settings__title-input"
@type="text"
@value={{this.editedTitle}}
/>
</:body>
<:footer>
<DButton
@disabled={{this.buttonDisabled}}
@action={{this.saveThread}}
@label="save"
class="btn-primary"
/>
</:footer>
</DModal>

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