DEV: Move gjs `<template>` to bottom of class definitions

To satisfy updated eslint configuration
This commit is contained in:
David Taylor 2023-10-10 19:26:27 +01:00
parent 31e4191a9b
commit ee0fef489f
38 changed files with 1344 additions and 1344 deletions

View File

@ -14,6 +14,39 @@ export default class AdminPostMenu extends Component {
@service store;
@service adminPostMenuButtons;
get reviewUrl() {
return `/review?topic_id=${this.args.data.transformedPost.id}&status=all`;
}
get extraButtons() {
return this.adminPostMenuButtons.callbacks
.map((callback) => {
return callback(this.args.data.transformedPost);
})
.filter(Boolean);
}
@action
async topicAction(actionName) {
await this.args.close();
try {
await this.args.data[actionName]?.();
} catch (error) {
// eslint-disable-next-line no-console
console.error(`Unknown error while attempting \`${actionName}\`:`, error);
}
await this.args.data.scheduleRerender();
}
@action
async extraAction(button) {
await this.args.close();
await button.action(this.args.data.post);
await this.args.data.scheduleRerender();
}
<template>
<ul>
{{#if this.currentUser.staff}}
@ -205,37 +238,4 @@ export default class AdminPostMenu extends Component {
{{/each}}
</ul>
</template>
get reviewUrl() {
return `/review?topic_id=${this.args.data.transformedPost.id}&status=all`;
}
get extraButtons() {
return this.adminPostMenuButtons.callbacks
.map((callback) => {
return callback(this.args.data.transformedPost);
})
.filter(Boolean);
}
@action
async topicAction(actionName) {
await this.args.close();
try {
await this.args.data[actionName]?.();
} catch (error) {
// eslint-disable-next-line no-console
console.error(`Unknown error while attempting \`${actionName}\`:`, error);
}
await this.args.data.scheduleRerender();
}
@action
async extraAction(button) {
await this.args.close();
await button.action(this.args.data.post);
await this.args.data.scheduleRerender();
}
}

View File

@ -9,19 +9,6 @@ import { resolveAllShortUrls } from "pretty-text/upload-short-url";
import { ajax } from "discourse/lib/ajax";
export default class CookText extends Component {
<template>
{{! template-lint-disable modifier-name-case }}
<div
...attributes
{{didUpdate this.buildOneboxes this.cooked}}
{{didUpdate this.resolveShortUrls this.cooked}}
{{didUpdate this.calculateOffsetHeight this.cooked}}
{{didUpdate this.loadCookedText @rawText}}
>
{{this.cooked}}
</div>
</template>
@service siteSettings;
@tracked cooked = null;
@ -63,4 +50,17 @@ export default class CookText extends Component {
resolveShortUrls(element) {
resolveAllShortUrls(ajax, this.siteSettings, element, this.args.opts);
}
<template>
{{! template-lint-disable modifier-name-case }}
<div
...attributes
{{didUpdate this.buildOneboxes this.cooked}}
{{didUpdate this.resolveShortUrls this.cooked}}
{{didUpdate this.calculateOffsetHeight this.cooked}}
{{didUpdate this.loadCookedText @rawText}}
>
{{this.cooked}}
</div>
</template>
}

View File

@ -17,62 +17,6 @@ const ACTION_AS_STRING_DEPRECATION_ARGS = [
];
export default class DButton extends GlimmerComponentWithDeprecatedParentView {
<template>
{{! template-lint-disable no-pointer-down-event-binding }}
<button
{{! For legacy compatibility. Prefer passing class as attributes. }}
class={{concatClass
@class
(if @isLoading "is-loading")
(if this.btnLink "btn-link" "btn")
(if this.noText "no-text")
this.btnType
}}
{{! For legacy compatibility. Prefer passing these as html attributes. }}
id={{@id}}
form={{@form}}
aria-controls={{@ariaControls}}
aria-expanded={{this.computedAriaExpanded}}
tabindex={{@tabindex}}
type={{or @type "button"}}
...attributes
disabled={{this.isDisabled}}
title={{this.computedTitle}}
aria-label={{this.computedAriaLabel}}
{{on "keydown" this.keyDown}}
{{on "click" this.click}}
{{on "mousedown" this.mouseDown}}
>
{{#if @isLoading}}
{{~icon "spinner" class="loading-icon"~}}
{{else}}
{{#if @icon}}
{{#if @ariaHidden}}
<span aria-hidden="true">
{{~icon @icon~}}
</span>
{{else}}
{{~icon @icon~}}
{{/if}}
{{/if}}
{{/if}}
{{~#if this.computedLabel~}}
<span class="d-button-label">
{{~htmlSafe this.computedLabel~}}
{{~#if @ellipsis~}}
&hellip;
{{~/if~}}
</span>
{{~else~}}
&#8203;
{{! Zero-width space character, so icon-only button height = regular button height }}
{{~/if~}}
{{yield}}
</button>
</template>
@service router;
@notEmpty("args.icon") btnIcon;
@ -199,4 +143,60 @@ export default class DButton extends GlimmerComponentWithDeprecatedParentView {
return false;
}
}
<template>
{{! template-lint-disable no-pointer-down-event-binding }}
<button
{{! For legacy compatibility. Prefer passing class as attributes. }}
class={{concatClass
@class
(if @isLoading "is-loading")
(if this.btnLink "btn-link" "btn")
(if this.noText "no-text")
this.btnType
}}
{{! For legacy compatibility. Prefer passing these as html attributes. }}
id={{@id}}
form={{@form}}
aria-controls={{@ariaControls}}
aria-expanded={{this.computedAriaExpanded}}
tabindex={{@tabindex}}
type={{or @type "button"}}
...attributes
disabled={{this.isDisabled}}
title={{this.computedTitle}}
aria-label={{this.computedAriaLabel}}
{{on "keydown" this.keyDown}}
{{on "click" this.click}}
{{on "mousedown" this.mouseDown}}
>
{{#if @isLoading}}
{{~icon "spinner" class="loading-icon"~}}
{{else}}
{{#if @icon}}
{{#if @ariaHidden}}
<span aria-hidden="true">
{{~icon @icon~}}
</span>
{{else}}
{{~icon @icon~}}
{{/if}}
{{/if}}
{{/if}}
{{~#if this.computedLabel~}}
<span class="d-button-label">
{{~htmlSafe this.computedLabel~}}
{{~#if @ellipsis~}}
&hellip;
{{~/if~}}
</span>
{{~else~}}
&#8203;
{{! Zero-width space character, so icon-only button height = regular button height }}
{{~/if~}}
{{yield}}
</button>
</template>
}

View File

@ -6,6 +6,18 @@ import deprecated from "discourse-common/lib/deprecated";
// Can add a body class from within a component
export default class DSection extends Component {
constructor() {
super(...arguments);
deprecated(
`<DSection> is deprecated. Use {{body-class "foo-page" "bar"}} and/or <section></section> instead.`,
{
since: "3.2.0.beta1",
dropFrom: "3.3.0.beta1",
id: "discourse.d-section",
}
);
}
<template>
{{#if @pageClass}}
{{bodyClass (concat @pageClass "-page")}}
@ -21,16 +33,4 @@ export default class DSection extends Component {
{{yield}}
{{/if}}
</template>
constructor() {
super(...arguments);
deprecated(
`<DSection> is deprecated. Use {{body-class "foo-page" "bar"}} and/or <section></section> instead.`,
{
since: "3.2.0.beta1",
dropFrom: "3.3.0.beta1",
id: "discourse.d-section",
}
);
}
}

View File

@ -11,29 +11,6 @@ import { on } from "@ember/modifier";
import autoFocus from "discourse/modifiers/auto-focus";
export default class FastEdit extends Component {
<template>
{{! template-lint-disable modifier-name-case }}
{{! template-lint-disable no-pointer-down-event-binding }}
{{! template-lint-disable no-invalid-interactive }}
<div class="fast-edit-container" {{on "keydown" this.onKeydown}}>
<textarea
{{on "input" this.updateValue}}
id="fast-edit-input"
{{autoFocus}}
>{{@initialValue}}</textarea>
<DButton
class="btn-small btn-primary save-fast-edit"
@action={{this.save}}
@icon="pencil-alt"
@label="composer.save_edit"
@translatedTitle={{this.buttonTitle}}
@isLoading={{this.isSaving}}
@disabled={{this.disabled}}
/>
</div>
</template>
@tracked value = this.args.initialValue;
@tracked isSaving = false;
@ -81,4 +58,27 @@ export default class FastEdit extends Component {
this.args.close();
}
}
<template>
{{! template-lint-disable modifier-name-case }}
{{! template-lint-disable no-pointer-down-event-binding }}
{{! template-lint-disable no-invalid-interactive }}
<div class="fast-edit-container" {{on "keydown" this.onKeydown}}>
<textarea
{{on "input" this.updateValue}}
id="fast-edit-input"
{{autoFocus}}
>{{@initialValue}}</textarea>
<DButton
class="btn-small btn-primary save-fast-edit"
@action={{this.save}}
@icon="pencil-alt"
@label="composer.save_edit"
@translatedTitle={{this.buttonTitle}}
@isLoading={{this.isSaving}}
@disabled={{this.disabled}}
/>
</div>
</template>
}

View File

@ -22,28 +22,6 @@ const FormTemplateField = <template>
</template>;
export default class FormTemplateFieldWrapper extends Component {
<template>
{{#if this.parsedTemplate}}
<div
class="form-template-form__wrapper"
{{! template-lint-disable modifier-name-case }}
{{didUpdate this.refreshTemplate @id}}
>
{{#each this.parsedTemplate as |content|}}
<FormTemplateField
@component={{get this.fieldTypes content.type}}
@content={{content}}
@initialValue={{get this.initialValues content.id}}
/>
{{/each}}
</div>
{{else}}
<div class="alert alert-error">
{{this.error}}
</div>
{{/if}}
</template>
@tracked error = null;
@tracked parsedTemplate = null;
@ -94,4 +72,26 @@ export default class FormTemplateFieldWrapper extends Component {
const templateContent = await response.form_template.template;
return this._loadTemplate(templateContent);
}
<template>
{{#if this.parsedTemplate}}
<div
class="form-template-form__wrapper"
{{! template-lint-disable modifier-name-case }}
{{didUpdate this.refreshTemplate @id}}
>
{{#each this.parsedTemplate as |content|}}
<FormTemplateField
@component={{get this.fieldTypes content.type}}
@content={{content}}
@initialValue={{get this.initialValues content.id}}
/>
{{/each}}
</div>
{{else}}
<div class="alert alert-error">
{{this.error}}
</div>
{{/if}}
</template>
}

View File

@ -10,49 +10,6 @@ const REPLIES_SUBSET = "replies";
const TOPICS_SUBSET = "topics";
export default class DismissNew extends Component {
<template>
<DModal
@closeModal={{@closeModal}}
@title={{this.modalTitle}}
@inline={{@inline}}
>
<:body>
<p>
{{#if this.showDismissNewTopics}}
<PreferenceCheckbox
@labelKey={{this.dismissNewTopicsLabel}}
@labelCount={{this.countNewTopics}}
@checked={{this.dismissTopics}}
@class="dismiss-topics"
/>
{{/if}}
{{#if this.showDismissNewReplies}}
<PreferenceCheckbox
@labelKey={{this.dismissNewRepliesLabel}}
@labelCount={{this.countNewReplies}}
@checked={{this.dismissPosts}}
@class="dismiss-posts"
/>
{{/if}}
<PreferenceCheckbox
@labelKey="topics.bulk.dismiss_new_modal.untrack"
@checked={{this.untrack}}
@class="untrack"
/>
</p>
</:body>
<:footer>
<DButton
@action={{this.dismissed}}
@icon="check"
@label="topics.bulk.dismiss"
id="dismiss-read-confirm"
class="btn-primary"
/>
</:footer>
</DModal>
</template>
@tracked untrack = false;
@tracked dismissTopics = true;
@tracked dismissPosts = true;
@ -141,4 +98,47 @@ export default class DismissNew extends Component {
this.args.closeModal();
}
<template>
<DModal
@closeModal={{@closeModal}}
@title={{this.modalTitle}}
@inline={{@inline}}
>
<:body>
<p>
{{#if this.showDismissNewTopics}}
<PreferenceCheckbox
@labelKey={{this.dismissNewTopicsLabel}}
@labelCount={{this.countNewTopics}}
@checked={{this.dismissTopics}}
@class="dismiss-topics"
/>
{{/if}}
{{#if this.showDismissNewReplies}}
<PreferenceCheckbox
@labelKey={{this.dismissNewRepliesLabel}}
@labelCount={{this.countNewReplies}}
@checked={{this.dismissPosts}}
@class="dismiss-posts"
/>
{{/if}}
<PreferenceCheckbox
@labelKey="topics.bulk.dismiss_new_modal.untrack"
@checked={{this.untrack}}
@class="untrack"
/>
</p>
</:body>
<:footer>
<DButton
@action={{this.dismissed}}
@icon="check"
@label="topics.bulk.dismiss"
id="dismiss-read-confirm"
class="btn-primary"
/>
</:footer>
</DModal>
</template>
}

View File

@ -22,86 +22,6 @@ export function fixQuotes(str) {
}
export default class PostTextSelectionToolbar extends Component {
<template>
{{! template-lint-disable modifier-name-case }}
{{! template-lint-disable no-invalid-interactive }}
{{! template-lint-disable no-pointer-down-event-binding }}
<div
{{on "mousedown" this.trapEvents}}
{{on "mouseup" this.trapEvents}}
class={{concatClass "quote-button" "visible"}}
{{this.appEventsListeners}}
>
<div class="buttons">
<PluginOutlet
@name="post-text-buttons"
@defaultGlimmer={{true}}
@outletArgs={{hash data=@data}}
>
{{#if this.embedQuoteButton}}
<DButton
@icon="quote-left"
@label="post.quote_reply"
@title="post.quote_reply_shortcut"
class="btn-flat insert-quote"
@action={{@data.insertQuote}}
/>
{{/if}}
{{#if @data.canEditPost}}
<DButton
@icon="pencil-alt"
@label="post.quote_edit"
@title="post.quote_edit_shortcut"
class="btn-flat quote-edit-label"
{{on "click" this.toggleFastEdit}}
/>
{{/if}}
{{#if this.quoteSharingEnabled}}
<span class="quote-sharing">
{{#if this.quoteSharingShowLabel}}
<DButton
@icon="share"
@label="post.quote_share"
class="btn-flat quote-share-label"
/>
{{/if}}
<span class="quote-share-buttons">
{{#each this.quoteSharingSources as |source|}}
<DButton
@action={{fn this.share source}}
@translatedTitle={{source.title}}
@icon={{source.icon}}
class="btn-flat"
/>
{{/each}}
<PluginOutlet
@name="quote-share-buttons-after"
@connectorTagName="span"
/>
</span>
</span>
{{/if}}
</PluginOutlet>
</div>
<div class="extra">
{{#if this.isFastEditing}}
<FastEdit
@initialValue={{@data.quoteState.buffer}}
@post={{this.post}}
@close={{this.closeFastEdit}}
/>
{{/if}}
<PluginOutlet @name="quote-button-after" @connectorTagName="div" />
</div>
</div>
</template>
@service currentUser;
@service modal;
@service site;
@ -256,4 +176,84 @@ export default class PostTextSelectionToolbar extends Component {
quote: window.getSelection().toString(),
});
}
<template>
{{! template-lint-disable modifier-name-case }}
{{! template-lint-disable no-invalid-interactive }}
{{! template-lint-disable no-pointer-down-event-binding }}
<div
{{on "mousedown" this.trapEvents}}
{{on "mouseup" this.trapEvents}}
class={{concatClass "quote-button" "visible"}}
{{this.appEventsListeners}}
>
<div class="buttons">
<PluginOutlet
@name="post-text-buttons"
@defaultGlimmer={{true}}
@outletArgs={{hash data=@data}}
>
{{#if this.embedQuoteButton}}
<DButton
@icon="quote-left"
@label="post.quote_reply"
@title="post.quote_reply_shortcut"
class="btn-flat insert-quote"
@action={{@data.insertQuote}}
/>
{{/if}}
{{#if @data.canEditPost}}
<DButton
@icon="pencil-alt"
@label="post.quote_edit"
@title="post.quote_edit_shortcut"
class="btn-flat quote-edit-label"
{{on "click" this.toggleFastEdit}}
/>
{{/if}}
{{#if this.quoteSharingEnabled}}
<span class="quote-sharing">
{{#if this.quoteSharingShowLabel}}
<DButton
@icon="share"
@label="post.quote_share"
class="btn-flat quote-share-label"
/>
{{/if}}
<span class="quote-share-buttons">
{{#each this.quoteSharingSources as |source|}}
<DButton
@action={{fn this.share source}}
@translatedTitle={{source.title}}
@icon={{source.icon}}
class="btn-flat"
/>
{{/each}}
<PluginOutlet
@name="quote-share-buttons-after"
@connectorTagName="span"
/>
</span>
</span>
{{/if}}
</PluginOutlet>
</div>
<div class="extra">
{{#if this.isFastEditing}}
<FastEdit
@initialValue={{@data.quoteState.buffer}}
@post={{this.post}}
@close={{this.closeFastEdit}}
/>
{{/if}}
<PluginOutlet @name="quote-button-after" @connectorTagName="div" />
</div>
</div>
</template>
}

View File

@ -38,15 +38,6 @@ export function fixQuotes(str) {
}
export default class PostTextSelection extends Component {
<template>
{{! template-lint-disable modifier-name-case }}
<div
{{this.documentListeners}}
{{this.appEventsListeners}}
{{this.runLoopHandlers}}
></div>
</template>
@service appEvents;
@service capabilities;
@service currentUser;
@ -284,4 +275,13 @@ export default class PostTextSelection extends Component {
await this.args.selectText();
await this.hideToolbar();
}
<template>
{{! template-lint-disable modifier-name-case }}
<div
{{this.documentListeners}}
{{this.appEventsListeners}}
{{this.runLoopHandlers}}
></div>
</template>
}

View File

@ -2,6 +2,8 @@ import Component from "@glimmer/component";
import { inject as service } from "@ember/service";
export default class RenderGlimmerContainer extends Component {
@service renderGlimmer;
<template>
{{#each this.renderGlimmer._registrations as |info|}}
{{#in-element info.element insertBefore=null}}
@ -12,6 +14,4 @@ export default class RenderGlimmerContainer extends Component {
{{/in-element}}
{{/each}}
</template>
@service renderGlimmer;
}

View File

@ -9,6 +9,33 @@ import { action } from "@ember/object";
import { isRTL } from "discourse/lib/text-direction";
export default class ReviewableBundledAction extends Component {
@service site;
get multiple() {
return this.args.bundle.actions.length > 1;
}
get first() {
return this.args.bundle.actions[0];
}
get placement() {
const vertical = this.site.mobileView ? "top" : "bottom";
const horizontal = isRTL() ? "end" : "start";
return `${vertical}-${horizontal}`;
}
@action
perform(id) {
if (id) {
const _action = this.args.bundle.actions.find((a) => a.id === id);
this.args.performAction(_action);
} else {
this.args.performAction(this.first);
}
}
<template>
{{#if this.multiple}}
<DropdownSelectBox
@ -41,31 +68,4 @@ export default class ReviewableBundledAction extends Component {
/>
{{/if}}
</template>
@service site;
get multiple() {
return this.args.bundle.actions.length > 1;
}
get first() {
return this.args.bundle.actions[0];
}
get placement() {
const vertical = this.site.mobileView ? "top" : "bottom";
const horizontal = isRTL() ? "end" : "start";
return `${vertical}-${horizontal}`;
}
@action
perform(id) {
if (id) {
const _action = this.args.bundle.actions.find((a) => a.id === id);
this.args.performAction(_action);
} else {
this.args.performAction(this.first);
}
}
}

View File

@ -2,20 +2,6 @@ import Component from "@glimmer/component";
import I18n from "I18n";
export default class ScoreValue extends Component {
<template>
{{#if @value}}
<span class="op">{{if this.isNegative "-" "+"}}</span>
<span class="score-value">
<span class="score-number">{{this.numericValue}}</span>
{{#if @label}}
<span title={{this.explanationTitle}} class="score-value-type">
{{this.explanationContent}}
</span>
{{/if}}
</span>
{{/if}}
</template>
get numericValue() {
return parseFloat(Math.abs(this.args.value)).toFixed(1);
}
@ -31,4 +17,18 @@ export default class ScoreValue extends Component {
get explanationContent() {
return I18n.t(`review.explain.${this.args.label}.name`);
}
<template>
{{#if @value}}
<span class="op">{{if this.isNegative "-" "+"}}</span>
<span class="score-value">
<span class="score-number">{{this.numericValue}}</span>
{{#if @label}}
<span title={{this.explanationTitle}} class="score-value-type">
{{this.explanationContent}}
</span>
{{/if}}
</span>
{{/if}}
</template>
}

View File

@ -5,6 +5,19 @@ import { action } from "@ember/object";
import { inject as service } from "@ember/service";
export default class UserTipContainer extends Component {
@service userTips;
get safeHtmlContent() {
return htmlSafe(this.args.data.contentHtml);
}
@action
handleDismiss(_, event) {
event.preventDefault();
this.args.close();
this.userTips.hideUserTipForever(this.args.data.id);
}
<template>
<div class="user-tip__container">
<div class="user-tip__title">{{@data.titleText}}</div>
@ -27,17 +40,4 @@ export default class UserTipContainer extends Component {
{{/if}}
</div>
</template>
@service userTips;
get safeHtmlContent() {
return htmlSafe(this.args.data.contentHtml);
}
@action
handleDismiss(_, event) {
event.preventDefault();
this.args.close();
this.userTips.hideUserTipForever(this.args.data.id);
}
}

View File

@ -10,15 +10,6 @@ import I18n from "I18n";
import { iconHTML } from "discourse-common/lib/icon-library";
export default class UserTip extends Component {
<template>
{{! template-lint-disable modifier-name-case }}
<div {{this.registerTip}}>
{{#if this.shouldRenderTip}}
<span {{this.tip}}></span>
{{/if}}
</div>
</template>
@service currentUser;
@service userTips;
@service tooltip;
@ -80,4 +71,13 @@ export default class UserTip extends Component {
get shouldRenderTip() {
return this.userTips.renderedId === this.args.id;
}
<template>
{{! template-lint-disable modifier-name-case }}
<div {{this.registerTip}}>
{{#if this.shouldRenderTip}}
<span {{this.tip}}></span>
{{/if}}
</div>
</template>
}

View File

@ -11,6 +11,40 @@ import TrapTab from "discourse/modifiers/trap-tab";
import DFloatPortal from "float-kit/components/d-float-portal";
export default class DFloatBody extends Component {
closeOnScroll = modifier(() => {
const firstScrollParent = getScrollParent(this.trigger);
const handler = () => {
this.args.instance.close();
};
firstScrollParent.addEventListener("scroll", handler, { passive: true });
return () => {
firstScrollParent.removeEventListener("scroll", handler);
};
});
get supportsCloseOnClickOutside() {
return this.args.instance.expanded && this.options.closeOnClickOutside;
}
get supportsCloseOnEscape() {
return this.args.instance.expanded && this.options.closeOnEscape;
}
get supportsCloseOnScroll() {
return this.args.instance.expanded && this.options.closeOnScroll;
}
get trigger() {
return this.args.instance.trigger;
}
get options() {
return this.args.instance.options;
}
<template>
{{! template-lint-disable modifier-name-case }}
<DFloatPortal
@ -48,38 +82,4 @@ export default class DFloatBody extends Component {
</div>
</DFloatPortal>
</template>
closeOnScroll = modifier(() => {
const firstScrollParent = getScrollParent(this.trigger);
const handler = () => {
this.args.instance.close();
};
firstScrollParent.addEventListener("scroll", handler, { passive: true });
return () => {
firstScrollParent.removeEventListener("scroll", handler);
};
});
get supportsCloseOnClickOutside() {
return this.args.instance.expanded && this.options.closeOnClickOutside;
}
get supportsCloseOnEscape() {
return this.args.instance.expanded && this.options.closeOnEscape;
}
get supportsCloseOnScroll() {
return this.args.instance.expanded && this.options.closeOnScroll;
}
get trigger() {
return this.args.instance.trigger;
}
get options() {
return this.args.instance.options;
}
}

View File

@ -2,6 +2,10 @@ import Component from "@glimmer/component";
import { isTesting } from "discourse-common/config/environment";
export default class DFloatPortal extends Component {
get inline() {
return this.args.inline ?? isTesting();
}
<template>
{{#if this.inline}}
{{yield}}
@ -11,8 +15,4 @@ export default class DFloatPortal extends Component {
{{/in-element}}
{{/if}}
</template>
get inline() {
return this.args.inline ?? isTesting();
}
}

View File

@ -5,6 +5,8 @@ import { MENU } from "float-kit/lib/constants";
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
export default class DInlineMenu extends Component {
@service menu;
<template>
{{! template-lint-disable modifier-name-case }}
<div
@ -22,6 +24,4 @@ export default class DInlineMenu extends Component {
@inline={{@inline}}
/>
</template>
@service menu;
}

View File

@ -6,6 +6,8 @@ import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import and from "truth-helpers/helpers/and";
export default class DInlineTooltip extends Component {
@service tooltip;
<template>
{{! template-lint-disable modifier-name-case }}
<div
@ -26,6 +28,4 @@ export default class DInlineTooltip extends Component {
@inline={{@inline}}
/>
</template>
@service tooltip;
}

View File

@ -9,6 +9,49 @@ import { getOwner } from "@ember/application";
import DMenuInstance from "float-kit/lib/d-menu-instance";
export default class DMenu extends Component {
@service menu;
@tracked menuInstance = null;
registerTrigger = modifier((element) => {
const options = {
...this.args,
...{
autoUpdate: true,
listeners: true,
beforeTrigger: () => {
this.menu.close();
},
},
};
const instance = new DMenuInstance(getOwner(this), element, options);
this.menuInstance = instance;
return () => {
instance.destroy();
if (this.isDestroying) {
this.menuInstance = null;
}
};
});
get menuId() {
return `d-menu-${this.menuInstance.id}`;
}
get options() {
return this.menuInstance?.options ?? {};
}
get componentArgs() {
return {
close: this.menuInstance.close,
data: this.options.data,
};
}
<template>
{{! template-lint-disable modifier-name-case }}
<DButton
@ -58,47 +101,4 @@ export default class DMenu extends Component {
</DFloatBody>
{{/if}}
</template>
@service menu;
@tracked menuInstance = null;
registerTrigger = modifier((element) => {
const options = {
...this.args,
...{
autoUpdate: true,
listeners: true,
beforeTrigger: () => {
this.menu.close();
},
},
};
const instance = new DMenuInstance(getOwner(this), element, options);
this.menuInstance = instance;
return () => {
instance.destroy();
if (this.isDestroying) {
this.menuInstance = null;
}
};
});
get menuId() {
return `d-menu-${this.menuInstance.id}`;
}
get options() {
return this.menuInstance?.options ?? {};
}
get componentArgs() {
return {
close: this.menuInstance.close,
data: this.options.data,
};
}
}

View File

@ -5,13 +5,6 @@ import { modifier } from "ember-modifier";
import deprecated from "discourse-common/lib/deprecated";
export default class DPopover extends Component {
<template>
{{! template-lint-disable modifier-name-case }}
<div style="display:inline-flex;" {{this.registerDTooltip}}>
{{yield}}
</div>
</template>
@service tooltip;
registerDTooltip = modifier((element) => {
@ -37,4 +30,11 @@ export default class DPopover extends Component {
instance.destroy();
};
});
<template>
{{! template-lint-disable modifier-name-case }}
<div style="display:inline-flex;" {{this.registerDTooltip}}>
{{yield}}
</div>
</template>
}

View File

@ -4,6 +4,8 @@ import concatClass from "discourse/helpers/concat-class";
import { on } from "@ember/modifier";
export default class DToasts extends Component {
@service toasts;
<template>
<div class="fk-d-toasts">
{{#each this.toasts.activeToasts as |toast|}}
@ -22,6 +24,4 @@ export default class DToasts extends Component {
{{/each}}
</div>
</template>
@service toasts;
}

View File

@ -10,6 +10,44 @@ import { getOwner } from "@ember/application";
import and from "truth-helpers/helpers/and";
export default class DTooltip extends Component {
@service tooltip;
@tracked tooltipInstance = null;
registerTrigger = modifier((element) => {
const options = {
...this.args,
...{
listeners: true,
beforeTrigger: () => {
this.tooltip.close();
},
},
};
const instance = new DTooltipInstance(getOwner(this), element, options);
this.tooltipInstance = instance;
return () => {
instance.destroy();
if (this.isDestroying) {
this.tooltipInstance = null;
}
};
});
get options() {
return this.tooltipInstance?.options;
}
get componentArgs() {
return {
close: this.tooltip.close,
data: this.options.data,
};
}
<template>
{{! template-lint-disable modifier-name-case }}
<span
@ -68,42 +106,4 @@ export default class DTooltip extends Component {
</DFloatBody>
{{/if}}
</template>
@service tooltip;
@tracked tooltipInstance = null;
registerTrigger = modifier((element) => {
const options = {
...this.args,
...{
listeners: true,
beforeTrigger: () => {
this.tooltip.close();
},
},
};
const instance = new DTooltipInstance(getOwner(this), element, options);
this.tooltipInstance = instance;
return () => {
instance.destroy();
if (this.isDestroying) {
this.tooltipInstance = null;
}
};
});
get options() {
return this.tooltipInstance?.options;
}
get componentArgs() {
return {
close: this.tooltip.close,
data: this.options.data,
};
}
}

View File

@ -7,6 +7,22 @@ import ChatChannelStatus from "discourse/plugins/chat/discourse/components/chat-
import I18n from "I18n";
export default class ChatChannelMessageEmojiPicker extends Component {
@service chatChannelInfoRouteOriginManager;
@service site;
membersLabel = I18n.t("chat.channel_info.tabs.members");
settingsLabel = I18n.t("chat.channel_info.tabs.settings");
backToChannelLabel = I18n.t("chat.channel_info.back_to_all_channel");
backToAllChannelsLabel = I18n.t("chat.channel_info.back_to_channel");
get showTabs() {
return (
this.site.desktopView &&
this.args.channel.membershipsCount > 1 &&
this.args.channel.isOpen
);
}
<template>
<div class="chat-full-page-header">
<div class="chat-channel-header-details">
@ -66,20 +82,4 @@ export default class ChatChannelMessageEmojiPicker extends Component {
{{outlet}}
</div>
</template>
@service chatChannelInfoRouteOriginManager;
@service site;
membersLabel = I18n.t("chat.channel_info.tabs.members");
settingsLabel = I18n.t("chat.channel_info.tabs.settings");
backToChannelLabel = I18n.t("chat.channel_info.back_to_all_channel");
backToAllChannelsLabel = I18n.t("chat.channel_info.back_to_channel");
get showTabs() {
return (
this.site.desktopView &&
this.args.channel.membershipsCount > 1 &&
this.args.channel.isOpen
);
}
}

View File

@ -14,45 +14,6 @@ import { hash } from "@ember/helper";
import { schedule } from "@ember/runloop";
export default class ChatChannelMembers extends Component {
<template>
{{! template-lint-disable modifier-name-case }}
<div class="chat-channel-members">
<DcFilterInput
@class="chat-channel-members__filter"
@filterAction={{this.mutFilter}}
@icons={{hash right="search"}}
placeholder={{this.filterPlaceholder}}
{{this.focusInput}}
/>
{{#if (gt @channel.membershipsCount 0)}}
<ul class="chat-channel-members__list" {{this.fill}}>
{{#each this.members as |membership|}}
<li class="chat-channel-members__list-item">
<ChatUserInfo @user={{membership.user}} @avatarSize="tiny" />
</li>
{{else}}
{{#if this.noResults}}
<li
class="chat-channel-members__list-item -no-results alert alert-info"
>
{{this.noMembershipsFoundLabel}}
</li>
{{/if}}
{{/each}}
</ul>
<div {{this.loadMore}}>
<br />
</div>
{{else}}
<p class="alert alert-info">
{{this.noMembershipsLabel}}
</p>
{{/if}}
</div>
</template>
@service chatApi;
@service modal;
@service loadingSlider;
@ -122,4 +83,43 @@ export default class ChatChannelMembers extends Component {
await this.members.load({ limit: 20 });
this.loadingSlider.transitionEnded();
}
<template>
{{! template-lint-disable modifier-name-case }}
<div class="chat-channel-members">
<DcFilterInput
@class="chat-channel-members__filter"
@filterAction={{this.mutFilter}}
@icons={{hash right="search"}}
placeholder={{this.filterPlaceholder}}
{{this.focusInput}}
/>
{{#if (gt @channel.membershipsCount 0)}}
<ul class="chat-channel-members__list" {{this.fill}}>
{{#each this.members as |membership|}}
<li class="chat-channel-members__list-item">
<ChatUserInfo @user={{membership.user}} @avatarSize="tiny" />
</li>
{{else}}
{{#if this.noResults}}
<li
class="chat-channel-members__list-item -no-results alert alert-info"
>
{{this.noMembershipsFoundLabel}}
</li>
{{/if}}
{{/each}}
</ul>
<div {{this.loadMore}}>
<br />
</div>
{{else}}
<p class="alert alert-info">
{{this.noMembershipsLabel}}
</p>
{{/if}}
</div>
</template>
}

View File

@ -7,18 +7,6 @@ import ChatEmojiPicker from "discourse/plugins/chat/discourse/components/chat-em
import { modifier } from "ember-modifier";
export default class ChatChannelMessageEmojiPicker extends Component {
<template>
{{! template-lint-disable modifier-name-case }}
<ChatEmojiPicker
@context="chat-channel-message"
@didInsert={{this.didInsert}}
@willDestroy={{this.willDestroy}}
@didSelectEmoji={{this.didSelectEmoji}}
@class="hidden"
{{this.listenToBodyScroll}}
/>
</template>
@service site;
@service chatEmojiPickerManager;
@ -74,4 +62,16 @@ export default class ChatChannelMessageEmojiPicker extends Component {
willDestroy() {
this._popper?.destroy();
}
<template>
{{! template-lint-disable modifier-name-case }}
<ChatEmojiPicker
@context="chat-channel-message"
@didInsert={{this.didInsert}}
@willDestroy={{this.willDestroy}}
@didSelectEmoji={{this.didSelectEmoji}}
@class="hidden"
{{this.listenToBodyScroll}}
/>
</template>
}

View File

@ -22,70 +22,6 @@ import { htmlSafe } from "@ember/template";
const FADEOUT_CLASS = "-fade-out";
export default class ChatChannelRow extends Component {
<template>
{{! template-lint-disable modifier-name-case }}
<LinkTo
@route="chat.channel"
@models={{@channel.routeModels}}
class={{concatClass
"chat-channel-row"
(if @channel.focused "focused")
(if @channel.currentUserMembership.muted "muted")
(if @options.leaveButton "can-leave")
(if (eq this.chat.activeChannel.id @channel.id) "active")
(if this.channelHasUnread "has-unread")
}}
tabindex="0"
data-chat-channel-id={{@channel.id}}
{{didInsert this.startTrackingStatus}}
{{willDestroy this.stopTrackingStatus}}
{{(if this.shouldRemoveChannel (modifier this.onRemoveChannel))}}
>
<div
class={{concatClass
"chat-channel-row__content"
(if this.shouldReset "-animate-reset")
}}
{{(if this.shouldHandleSwipe (modifier this.registerSwipableRow))}}
{{(if this.shouldHandleSwipe (modifier this.handleSwipe))}}
{{(if this.shouldReset (modifier this.onReset))}}
style={{this.rowStyle}}
>
<ChatChannelTitle @channel={{@channel}} />
<ChatChannelMetadata @channel={{@channel}} @unreadIndicator={{true}} />
{{#if
(and @options.leaveButton @channel.isFollowing this.site.desktopView)
}}
<ToggleChannelMembershipButton
@channel={{@channel}}
@options={{hash
leaveClass="btn-flat chat-channel-leave-btn"
labelType="none"
leaveIcon="times"
leaveTitle=(if
@channel.isDirectMessageChannel
this.leaveDirectMessageLabel
this.leaveChannelLabel
)
}}
/>
{{/if}}
</div>
{{#if this.showRemoveButton}}
<div
class={{concatClass
"chat-channel-row__action-btn"
(if this.isAtThreshold "-at-threshold" "-not-at-threshold")
}}
>
{{icon "times-circle"}}
</div>
{{/if}}
</LinkTo>
</template>
@service api;
@service capabilities;
@service chat;
@ -219,4 +155,68 @@ export default class ChatChannelRow extends Component {
stopTrackingStatus() {
this.#firstDirectMessageUser?.stopTrackingStatus();
}
<template>
{{! template-lint-disable modifier-name-case }}
<LinkTo
@route="chat.channel"
@models={{@channel.routeModels}}
class={{concatClass
"chat-channel-row"
(if @channel.focused "focused")
(if @channel.currentUserMembership.muted "muted")
(if @options.leaveButton "can-leave")
(if (eq this.chat.activeChannel.id @channel.id) "active")
(if this.channelHasUnread "has-unread")
}}
tabindex="0"
data-chat-channel-id={{@channel.id}}
{{didInsert this.startTrackingStatus}}
{{willDestroy this.stopTrackingStatus}}
{{(if this.shouldRemoveChannel (modifier this.onRemoveChannel))}}
>
<div
class={{concatClass
"chat-channel-row__content"
(if this.shouldReset "-animate-reset")
}}
{{(if this.shouldHandleSwipe (modifier this.registerSwipableRow))}}
{{(if this.shouldHandleSwipe (modifier this.handleSwipe))}}
{{(if this.shouldReset (modifier this.onReset))}}
style={{this.rowStyle}}
>
<ChatChannelTitle @channel={{@channel}} />
<ChatChannelMetadata @channel={{@channel}} @unreadIndicator={{true}} />
{{#if
(and @options.leaveButton @channel.isFollowing this.site.desktopView)
}}
<ToggleChannelMembershipButton
@channel={{@channel}}
@options={{hash
leaveClass="btn-flat chat-channel-leave-btn"
labelType="none"
leaveIcon="times"
leaveTitle=(if
@channel.isDirectMessageChannel
this.leaveDirectMessageLabel
this.leaveChannelLabel
)
}}
/>
{{/if}}
</div>
{{#if this.showRemoveButton}}
<div
class={{concatClass
"chat-channel-row__action-btn"
(if this.isAtThreshold "-at-threshold" "-not-at-threshold")
}}
>
{{icon "times-circle"}}
</div>
{{/if}}
</LinkTo>
</template>
}

View File

@ -27,279 +27,6 @@ const NOTIFICATION_LEVELS = [
];
export default class ChatAboutScreen extends Component {
<template>
<div class="chat-channel-settings">
<ChatForm as |form|>
{{#if this.shouldRenderTitleSection}}
<form.section @title={{this.titleSectionTitle}} as |section|>
<section.row>
<:default>
<div class="chat-channel-settings__name">
{{replaceEmoji @channel.title}}
</div>
{{#if @channel.isCategoryChannel}}
<div class="chat-channel-settings__slug">
<LinkTo
@route="chat.channel"
@models={{@channel.routeModels}}
>
/chat/c/{{@channel.slug}}/{{@channel.id}}
</LinkTo>
</div>
{{/if}}
</:default>
<:action>
{{#if this.canEditChannel}}
<DButton
@label="chat.channel_settings.edit"
@action={{this.onEditChannelName}}
class="edit-name-slug-btn btn-flat"
/>
{{/if}}
</:action>
</section.row>
</form.section>
{{/if}}
{{#if this.shouldRenderDescriptionSection}}
<form.section @title={{this.descriptionSectionTitle}} as |section|>
<section.row>
<:default>
{{#if @channel.description.length}}
{{@channel.description}}
{{else}}
{{this.descriptionPlaceholder}}
{{/if}}
</:default>
<:action>
{{#if this.canEditChannel}}
<DButton
@label={{if
@channel.description.length
"chat.channel_settings.edit"
"chat.channel_settings.add"
}}
@action={{this.onEditChannelDescription}}
class="edit-description-btn btn-flat"
/>
{{/if}}
</:action>
</section.row>
</form.section>
{{/if}}
{{#if this.site.mobileView}}
<form.section as |section|>
<section.row
@label={{this.membersLabel}}
@route="chat.channel.info.members"
@routeModels={{@channel.routeModels}}
/>
</form.section>
{{/if}}
{{#if @channel.isOpen}}
<form.section @title={{this.settingsSectionTitle}} as |section|>
<section.row @label={{this.muteSectionLabel}}>
<:action>
<DToggleSwitch
@state={{@channel.currentUserMembership.muted}}
class="chat-channel-settings__mute-switch"
{{on "click" this.onToggleMuted}}
/>
</:action>
</section.row>
{{#if this.shouldRenderDesktopNotificationsLevelSection}}
<section.row @label={{this.desktopNotificationsLevelLabel}}>
<:action>
<ComboBox
@content={{this.notificationLevels}}
@value={{@channel.currentUserMembership.desktopNotificationLevel}}
@valueProperty="value"
@class="chat-channel-settings__selector chat-channel-settings__desktop-notifications-selector"
@onChange={{fn
this.saveNotificationSettings
"desktopNotificationLevel"
"desktop_notification_level"
}}
/>
</:action>
</section.row>
{{/if}}
{{#if this.shouldRenderMobileNotificationsLevelSection}}
<section.row @label={{this.mobileNotificationsLevelLabel}}>
<:action>
<ComboBox
@content={{this.notificationLevels}}
@value={{@channel.currentUserMembership.mobileNotificationLevel}}
@valueProperty="value"
@class="chat-channel-settings__selector chat-channel-settings__mobile-notifications-selector"
@onChange={{fn
this.saveNotificationSettings
"mobileNotificationLevel"
"mobile_notification_level"
}}
/>
</:action>
</section.row>
{{/if}}
</form.section>
{{/if}}
<form.section @title={{this.channelInfoSectionTitle}} as |section|>
{{#if @channel.isCategoryChannel}}
<section.row @label={{this.categoryLabel}}>
{{categoryBadge
@channel.chatable
link=true
allowUncategorized=true
}}
</section.row>
{{/if}}
<section.row @label={{this.historyLabel}}>
<ChatRetentionReminderText @channel={{@channel}} />
</section.row>
</form.section>
{{#if this.shouldRenderAdminSection}}
<form.section
@title={{this.adminSectionTitle}}
data-section="admin"
as |section|
>
{{#if this.autoJoinAvailable}}
<section.row @label={{this.autoJoinLabel}}>
<:action>
<DToggleSwitch
@state={{@channel.autoJoinUsers}}
class="chat-channel-settings__auto-join-switch"
{{on
"click"
(fn this.onToggleAutoJoinUsers @channel.autoJoinUsers)
}}
/>
</:action>
</section.row>
{{/if}}
{{#if this.toggleChannelWideMentionsAvailable}}
<section.row @label={{this.channelWideMentionsLabel}}>
<:action>
<DToggleSwitch
class="chat-channel-settings__channel-wide-mentions"
@state={{@channel.allowChannelWideMentions}}
{{on
"click"
(fn
this.onToggleChannelWideMentions
@channel.allowChannelWideMentions
)
}}
/>
</:action>
<:description>
{{this.channelWideMentionsDescription}}
</:description>
</section.row>
{{/if}}
{{#if this.toggleThreadingAvailable}}
<section.row @label={{this.toggleThreadingLabel}}>
<:action>
<DToggleSwitch
@state={{@channel.threadingEnabled}}
class="chat-channel-settings__threading-switch"
{{on
"click"
(fn
this.onToggleThreadingEnabled @channel.threadingEnabled
)
}}
/>
</:action>
<:description>
{{this.toggleThreadingDescription}}
</:description>
</section.row>
{{/if}}
{{#if this.shouldRenderStatusSection}}
{{#if this.shouldRenderArchiveRow}}
<section.row>
<:action>
<DButton
@action={{this.onArchiveChannel}}
@label="chat.channel_settings.archive_channel"
@icon="archive"
class="archive-btn chat-form__btn btn-flat"
/>
</:action>
</section.row>
{{/if}}
<section.row>
<:action>
{{#if @channel.isOpen}}
<DButton
@action={{this.onToggleChannelState}}
@label="chat.channel_settings.close_channel"
@icon="lock"
class="close-btn chat-form__btn btn-flat"
/>
{{else}}
<DButton
@action={{this.onToggleChannelState}}
@label="chat.channel_settings.open_channel"
@icon="unlock"
class="open-btn chat-form__btn btn-flat"
/>
{{/if}}
</:action>
</section.row>
<section.row>
<:action>
<DButton
@action={{this.onDeleteChannel}}
@label="chat.channel_settings.delete_channel"
@icon="trash-alt"
class="delete-btn chat-form__btn btn-flat"
/>
</:action>
</section.row>
{{/if}}
</form.section>
{{/if}}
<form.section as |section|>
<section.row>
<:action>
<ToggleChannelMembershipButton
@channel={{@channel}}
@options={{hash
joinClass="btn-primary"
leaveClass="btn-flat"
joinIcon="sign-in-alt"
leaveIcon="sign-out-alt"
}}
/>
</:action>
</section.row>
</form.section>
</ChatForm>
</div>
</template>
@service chatApi;
@service chatGuardian;
@service currentUser;
@ -578,4 +305,277 @@ export default class ChatAboutScreen extends Component {
model: this.args.channel,
});
}
<template>
<div class="chat-channel-settings">
<ChatForm as |form|>
{{#if this.shouldRenderTitleSection}}
<form.section @title={{this.titleSectionTitle}} as |section|>
<section.row>
<:default>
<div class="chat-channel-settings__name">
{{replaceEmoji @channel.title}}
</div>
{{#if @channel.isCategoryChannel}}
<div class="chat-channel-settings__slug">
<LinkTo
@route="chat.channel"
@models={{@channel.routeModels}}
>
/chat/c/{{@channel.slug}}/{{@channel.id}}
</LinkTo>
</div>
{{/if}}
</:default>
<:action>
{{#if this.canEditChannel}}
<DButton
@label="chat.channel_settings.edit"
@action={{this.onEditChannelName}}
class="edit-name-slug-btn btn-flat"
/>
{{/if}}
</:action>
</section.row>
</form.section>
{{/if}}
{{#if this.shouldRenderDescriptionSection}}
<form.section @title={{this.descriptionSectionTitle}} as |section|>
<section.row>
<:default>
{{#if @channel.description.length}}
{{@channel.description}}
{{else}}
{{this.descriptionPlaceholder}}
{{/if}}
</:default>
<:action>
{{#if this.canEditChannel}}
<DButton
@label={{if
@channel.description.length
"chat.channel_settings.edit"
"chat.channel_settings.add"
}}
@action={{this.onEditChannelDescription}}
class="edit-description-btn btn-flat"
/>
{{/if}}
</:action>
</section.row>
</form.section>
{{/if}}
{{#if this.site.mobileView}}
<form.section as |section|>
<section.row
@label={{this.membersLabel}}
@route="chat.channel.info.members"
@routeModels={{@channel.routeModels}}
/>
</form.section>
{{/if}}
{{#if @channel.isOpen}}
<form.section @title={{this.settingsSectionTitle}} as |section|>
<section.row @label={{this.muteSectionLabel}}>
<:action>
<DToggleSwitch
@state={{@channel.currentUserMembership.muted}}
class="chat-channel-settings__mute-switch"
{{on "click" this.onToggleMuted}}
/>
</:action>
</section.row>
{{#if this.shouldRenderDesktopNotificationsLevelSection}}
<section.row @label={{this.desktopNotificationsLevelLabel}}>
<:action>
<ComboBox
@content={{this.notificationLevels}}
@value={{@channel.currentUserMembership.desktopNotificationLevel}}
@valueProperty="value"
@class="chat-channel-settings__selector chat-channel-settings__desktop-notifications-selector"
@onChange={{fn
this.saveNotificationSettings
"desktopNotificationLevel"
"desktop_notification_level"
}}
/>
</:action>
</section.row>
{{/if}}
{{#if this.shouldRenderMobileNotificationsLevelSection}}
<section.row @label={{this.mobileNotificationsLevelLabel}}>
<:action>
<ComboBox
@content={{this.notificationLevels}}
@value={{@channel.currentUserMembership.mobileNotificationLevel}}
@valueProperty="value"
@class="chat-channel-settings__selector chat-channel-settings__mobile-notifications-selector"
@onChange={{fn
this.saveNotificationSettings
"mobileNotificationLevel"
"mobile_notification_level"
}}
/>
</:action>
</section.row>
{{/if}}
</form.section>
{{/if}}
<form.section @title={{this.channelInfoSectionTitle}} as |section|>
{{#if @channel.isCategoryChannel}}
<section.row @label={{this.categoryLabel}}>
{{categoryBadge
@channel.chatable
link=true
allowUncategorized=true
}}
</section.row>
{{/if}}
<section.row @label={{this.historyLabel}}>
<ChatRetentionReminderText @channel={{@channel}} />
</section.row>
</form.section>
{{#if this.shouldRenderAdminSection}}
<form.section
@title={{this.adminSectionTitle}}
data-section="admin"
as |section|
>
{{#if this.autoJoinAvailable}}
<section.row @label={{this.autoJoinLabel}}>
<:action>
<DToggleSwitch
@state={{@channel.autoJoinUsers}}
class="chat-channel-settings__auto-join-switch"
{{on
"click"
(fn this.onToggleAutoJoinUsers @channel.autoJoinUsers)
}}
/>
</:action>
</section.row>
{{/if}}
{{#if this.toggleChannelWideMentionsAvailable}}
<section.row @label={{this.channelWideMentionsLabel}}>
<:action>
<DToggleSwitch
class="chat-channel-settings__channel-wide-mentions"
@state={{@channel.allowChannelWideMentions}}
{{on
"click"
(fn
this.onToggleChannelWideMentions
@channel.allowChannelWideMentions
)
}}
/>
</:action>
<:description>
{{this.channelWideMentionsDescription}}
</:description>
</section.row>
{{/if}}
{{#if this.toggleThreadingAvailable}}
<section.row @label={{this.toggleThreadingLabel}}>
<:action>
<DToggleSwitch
@state={{@channel.threadingEnabled}}
class="chat-channel-settings__threading-switch"
{{on
"click"
(fn
this.onToggleThreadingEnabled @channel.threadingEnabled
)
}}
/>
</:action>
<:description>
{{this.toggleThreadingDescription}}
</:description>
</section.row>
{{/if}}
{{#if this.shouldRenderStatusSection}}
{{#if this.shouldRenderArchiveRow}}
<section.row>
<:action>
<DButton
@action={{this.onArchiveChannel}}
@label="chat.channel_settings.archive_channel"
@icon="archive"
class="archive-btn chat-form__btn btn-flat"
/>
</:action>
</section.row>
{{/if}}
<section.row>
<:action>
{{#if @channel.isOpen}}
<DButton
@action={{this.onToggleChannelState}}
@label="chat.channel_settings.close_channel"
@icon="lock"
class="close-btn chat-form__btn btn-flat"
/>
{{else}}
<DButton
@action={{this.onToggleChannelState}}
@label="chat.channel_settings.open_channel"
@icon="unlock"
class="open-btn chat-form__btn btn-flat"
/>
{{/if}}
</:action>
</section.row>
<section.row>
<:action>
<DButton
@action={{this.onDeleteChannel}}
@label="chat.channel_settings.delete_channel"
@icon="trash-alt"
class="delete-btn chat-form__btn btn-flat"
/>
</:action>
</section.row>
{{/if}}
</form.section>
{{/if}}
<form.section as |section|>
<section.row>
<:action>
<ToggleChannelMembershipButton
@channel={{@channel}}
@options={{hash
joinClass="btn-primary"
leaveClass="btn-flat"
joinIcon="sign-in-alt"
leaveIcon="sign-out-alt"
}}
/>
</:action>
</section.row>
</form.section>
</ChatForm>
</div>
</template>
}

View File

@ -25,6 +25,91 @@ const REDUCED = "reduced";
const REDUCED_WIDTH_THRESHOLD = 500;
export default class ChatMessageActionsDesktop extends Component {
@service chat;
@service chatEmojiPickerManager;
@service site;
@tracked size = FULL;
popper = null;
get message() {
return this.chat.activeMessage.model;
}
get context() {
return this.chat.activeMessage.context;
}
get messageInteractor() {
return new ChatMessageInteractor(
getOwner(this),
this.message,
this.context
);
}
get shouldRenderFavoriteReactions() {
return this.size === FULL;
}
@action
onWheel() {
// prevents menu to stop scroll on the list of messages
this.chat.activeMessage = null;
}
@action
setup(element) {
this.popper?.destroy();
schedule("afterRender", () => {
const messageContainer = chatMessageContainer(
this.message.id,
this.context
);
if (!messageContainer) {
return;
}
const viewport = messageContainer.closest(".popper-viewport");
this.size =
viewport.clientWidth < REDUCED_WIDTH_THRESHOLD ? REDUCED : FULL;
if (!messageContainer) {
return;
}
this.popper = createPopper(messageContainer, element, {
placement: "top-end",
strategy: "fixed",
modifiers: [
{
name: "flip",
enabled: true,
options: {
boundary: viewport,
fallbackPlacements: ["bottom-end"],
},
},
{ name: "hide", enabled: true },
{ name: "eventListeners", options: { scroll: false } },
{
name: "offset",
options: { offset: [-2, MSG_ACTIONS_VERTICAL_PADDING] },
},
],
});
});
}
@action
teardown() {
this.popper?.destroy();
this.popper = null;
}
<template>
{{! template-lint-disable modifier-name-case }}
{{#if (and this.site.desktopView this.chat.activeMessage.model.persisted)}}
@ -112,89 +197,4 @@ export default class ChatMessageActionsDesktop extends Component {
</div>
{{/if}}
</template>
@service chat;
@service chatEmojiPickerManager;
@service site;
@tracked size = FULL;
popper = null;
get message() {
return this.chat.activeMessage.model;
}
get context() {
return this.chat.activeMessage.context;
}
get messageInteractor() {
return new ChatMessageInteractor(
getOwner(this),
this.message,
this.context
);
}
get shouldRenderFavoriteReactions() {
return this.size === FULL;
}
@action
onWheel() {
// prevents menu to stop scroll on the list of messages
this.chat.activeMessage = null;
}
@action
setup(element) {
this.popper?.destroy();
schedule("afterRender", () => {
const messageContainer = chatMessageContainer(
this.message.id,
this.context
);
if (!messageContainer) {
return;
}
const viewport = messageContainer.closest(".popper-viewport");
this.size =
viewport.clientWidth < REDUCED_WIDTH_THRESHOLD ? REDUCED : FULL;
if (!messageContainer) {
return;
}
this.popper = createPopper(messageContainer, element, {
placement: "top-end",
strategy: "fixed",
modifiers: [
{
name: "flip",
enabled: true,
options: {
boundary: viewport,
fallbackPlacements: ["bottom-end"],
},
},
{ name: "hide", enabled: true },
{ name: "eventListeners", options: { scroll: false } },
{
name: "offset",
options: { offset: [-2, MSG_ACTIONS_VERTICAL_PADDING] },
},
],
});
});
}
@action
teardown() {
this.popper?.destroy();
this.popper = null;
}
}

View File

@ -17,6 +17,86 @@ import or from "truth-helpers/helpers/or";
import BookmarkIcon from "discourse/components/bookmark-icon";
export default class ChatMessageActionsMobile extends Component {
@service chat;
@service site;
@service capabilities;
@tracked hasExpandedReply = false;
@tracked showFadeIn = false;
get message() {
return this.chat.activeMessage.model;
}
get context() {
return this.chat.activeMessage.context;
}
get messageInteractor() {
return new ChatMessageInteractor(
getOwner(this),
this.message,
this.context
);
}
@action
fadeAndVibrate() {
discourseLater(this.#addFadeIn.bind(this));
if (this.capabilities.userHasBeenActive && this.capabilities.canVibrate) {
navigator.vibrate(5);
}
}
@action
expandReply(event) {
event.stopPropagation();
this.hasExpandedReply = true;
}
@action
collapseMenu(event) {
event.preventDefault();
this.#onCloseMenu();
}
@action
actAndCloseMenu(fnId) {
this.messageInteractor[fnId]();
this.#onCloseMenu();
}
@action
openEmojiPicker(_, event) {
this.messageInteractor.openEmojiPicker(_, event);
this.#onCloseMenu();
}
#onCloseMenu() {
this.#removeFadeIn();
// we don't want to remove the component right away as it's animating
// 200 is equal to the duration of the css animation
discourseLater(() => {
if (this.isDestroying || this.isDestroyed) {
return;
}
// by ensuring we are not hovering any message anymore
// we also ensure the menu is fully removed
this.chat.activeMessage = null;
}, 200);
}
#addFadeIn() {
this.showFadeIn = true;
}
#removeFadeIn() {
this.showFadeIn = false;
}
<template>
{{! template-lint-disable modifier-name-case }}
{{#if (and this.site.mobileView this.chat.activeMessage.model.persisted)}}
@ -113,84 +193,4 @@ export default class ChatMessageActionsMobile extends Component {
</div>
{{/if}}
</template>
@service chat;
@service site;
@service capabilities;
@tracked hasExpandedReply = false;
@tracked showFadeIn = false;
get message() {
return this.chat.activeMessage.model;
}
get context() {
return this.chat.activeMessage.context;
}
get messageInteractor() {
return new ChatMessageInteractor(
getOwner(this),
this.message,
this.context
);
}
@action
fadeAndVibrate() {
discourseLater(this.#addFadeIn.bind(this));
if (this.capabilities.userHasBeenActive && this.capabilities.canVibrate) {
navigator.vibrate(5);
}
}
@action
expandReply(event) {
event.stopPropagation();
this.hasExpandedReply = true;
}
@action
collapseMenu(event) {
event.preventDefault();
this.#onCloseMenu();
}
@action
actAndCloseMenu(fnId) {
this.messageInteractor[fnId]();
this.#onCloseMenu();
}
@action
openEmojiPicker(_, event) {
this.messageInteractor.openEmojiPicker(_, event);
this.#onCloseMenu();
}
#onCloseMenu() {
this.#removeFadeIn();
// we don't want to remove the component right away as it's animating
// 200 is equal to the duration of the css animation
discourseLater(() => {
if (this.isDestroying || this.isDestroyed) {
return;
}
// by ensuring we are not hovering any message anymore
// we also ensure the menu is fully removed
this.chat.activeMessage = null;
}, 200);
}
#addFadeIn() {
this.showFadeIn = true;
}
#removeFadeIn() {
this.showFadeIn = false;
}
}

View File

@ -11,38 +11,6 @@ import and from "truth-helpers/helpers/and";
import concatClass from "discourse/helpers/concat-class";
export default class ChatMessageReaction extends Component {
<template>
{{! template-lint-disable modifier-name-case }}
{{#if (and @reaction this.emojiUrl)}}
<button
type="button"
tabindex="0"
class={{concatClass
"chat-message-reaction"
(if @reaction.reacted "reacted")
(if this.isActive "-active")
}}
data-emoji-name={{@reaction.emoji}}
title={{this.emojiString}}
{{on "click" this.handleClick passive=true}}
{{this.registerTooltip}}
>
<img
loading="lazy"
class="emoji"
width="20"
height="20"
alt={{this.emojiString}}
src={{this.emojiUrl}}
/>
{{#if (and this.showCount @reaction.count)}}
<span class="count">{{@reaction.count}}</span>
{{/if}}
</button>
{{/if}}
</template>
@service capabilities;
@service currentUser;
@service tooltip;
@ -101,4 +69,36 @@ export default class ChatMessageReaction extends Component {
return emojiUnescape(getReactionText(this.args.reaction, this.currentUser));
}
<template>
{{! template-lint-disable modifier-name-case }}
{{#if (and @reaction this.emojiUrl)}}
<button
type="button"
tabindex="0"
class={{concatClass
"chat-message-reaction"
(if @reaction.reacted "reacted")
(if this.isActive "-active")
}}
data-emoji-name={{@reaction.emoji}}
title={{this.emojiString}}
{{on "click" this.handleClick passive=true}}
{{this.registerTooltip}}
>
<img
loading="lazy"
class="emoji"
width="20"
height="20"
alt={{this.emojiString}}
src={{this.emojiUrl}}
/>
{{#if (and this.showCount @reaction.count)}}
<span class="count">{{@reaction.count}}</span>
{{/if}}
</button>
{{/if}}
</template>
}

View File

@ -48,140 +48,6 @@ export const MENTION_KEYWORDS = ["here", "all"];
export const MESSAGE_CONTEXT_THREAD = "thread";
export default class ChatMessage extends Component {
<template>
{{! template-lint-disable no-invalid-interactive }}
{{! template-lint-disable modifier-name-case }}
{{#if this.shouldRender}}
{{#if (eq @context "channel")}}
<ChatMessageSeparatorDate
@fetchMessagesByDate={{@fetchMessagesByDate}}
@message={{@message}}
/>
<ChatMessageSeparatorNew @message={{@message}} />
{{/if}}
<div
class={{concatClass
"chat-message-container"
(if this.pane.selectingMessages "-selectable")
(if @message.highlighted "-highlighted")
(if (eq @message.user.id this.currentUser.id) "is-by-current-user")
(if @message.staged "-staged" "-persisted")
(if this.hasActiveState "-active")
(if @message.bookmark "-bookmarked")
(if @message.deletedAt "-deleted")
(if @message.selected "-selected")
(if @message.error "-errored")
(if this.showThreadIndicator "has-thread-indicator")
(if this.hideUserInfo "-user-info-hidden")
(if this.hasReply "has-reply")
}}
data-id={{@message.id}}
data-thread-id={{@message.thread.id}}
{{didInsert this.didInsertMessage}}
{{didUpdate this.didUpdateMessageId @message.id}}
{{didUpdate this.didUpdateMessageVersion @message.version}}
{{willDestroy this.willDestroyMessage}}
{{on "mouseenter" this.onMouseEnter passive=true}}
{{on "mouseleave" this.onMouseLeave passive=true}}
{{on "mousemove" this.onMouseMove passive=true}}
{{this.toggleCheckIfPossible}}
{{ChatOnLongPress
this.onLongPressStart
this.onLongPressEnd
this.onLongPressCancel
}}
...attributes
>
{{#if this.show}}
{{#if this.pane.selectingMessages}}
<Input
@type="checkbox"
class="chat-message-selector"
@checked={{@message.selected}}
{{on "click" this.toggleChecked}}
/>
{{/if}}
{{#if this.deletedAndCollapsed}}
<div class="chat-message-text -deleted">
<DButton
@action={{this.expand}}
@translatedLabel={{this.deletedMessageLabel}}
class="btn-flat chat-message-expand"
/>
</div>
{{else if this.hiddenAndCollapsed}}
<div class="chat-message-text -hidden">
<DButton
@action={{this.expand}}
@label="chat.hidden"
class="btn-flat chat-message-expand"
/>
</div>
{{else}}
<div class="chat-message">
{{#unless this.hideReplyToInfo}}
<ChatMessageInReplyToIndicator @message={{@message}} />
{{/unless}}
{{#if this.hideUserInfo}}
<ChatMessageLeftGutter @message={{@message}} />
{{else}}
<ChatMessageAvatar @message={{@message}} />
{{/if}}
<div class="chat-message-content">
<ChatMessageInfo
@message={{@message}}
@show={{not this.hideUserInfo}}
/>
<ChatMessageText
@cooked={{@message.cooked}}
@uploads={{@message.uploads}}
@edited={{@message.edited}}
>
{{#if @message.reactions.length}}
<div class="chat-message-reaction-list">
{{#each @message.reactions as |reaction|}}
<ChatMessageReaction
@reaction={{reaction}}
@onReaction={{this.messageInteractor.react}}
@message={{@message}}
@showTooltip={{true}}
/>
{{/each}}
{{#if this.shouldRenderOpenEmojiPickerButton}}
<DButton
@action={{this.messageInteractor.openEmojiPicker}}
@icon="discourse-emojis"
@title="chat.react"
@forwardEvent={{true}}
class="chat-message-react-btn"
/>
{{/if}}
</div>
{{/if}}
</ChatMessageText>
<ChatMessageError
@message={{@message}}
@onRetry={{@resendStagedMessage}}
/>
</div>
{{#if this.showThreadIndicator}}
<ChatMessageThreadIndicator @message={{@message}} />
{{/if}}
</div>
{{/if}}
{{/if}}
</div>
{{/if}}
</template>
@service site;
@service dialog;
@service currentUser;
@ -624,4 +490,138 @@ export default class ChatMessage extends Component {
user.off("status-changed", this, "refreshStatusOnMentions");
});
}
<template>
{{! template-lint-disable no-invalid-interactive }}
{{! template-lint-disable modifier-name-case }}
{{#if this.shouldRender}}
{{#if (eq @context "channel")}}
<ChatMessageSeparatorDate
@fetchMessagesByDate={{@fetchMessagesByDate}}
@message={{@message}}
/>
<ChatMessageSeparatorNew @message={{@message}} />
{{/if}}
<div
class={{concatClass
"chat-message-container"
(if this.pane.selectingMessages "-selectable")
(if @message.highlighted "-highlighted")
(if (eq @message.user.id this.currentUser.id) "is-by-current-user")
(if @message.staged "-staged" "-persisted")
(if this.hasActiveState "-active")
(if @message.bookmark "-bookmarked")
(if @message.deletedAt "-deleted")
(if @message.selected "-selected")
(if @message.error "-errored")
(if this.showThreadIndicator "has-thread-indicator")
(if this.hideUserInfo "-user-info-hidden")
(if this.hasReply "has-reply")
}}
data-id={{@message.id}}
data-thread-id={{@message.thread.id}}
{{didInsert this.didInsertMessage}}
{{didUpdate this.didUpdateMessageId @message.id}}
{{didUpdate this.didUpdateMessageVersion @message.version}}
{{willDestroy this.willDestroyMessage}}
{{on "mouseenter" this.onMouseEnter passive=true}}
{{on "mouseleave" this.onMouseLeave passive=true}}
{{on "mousemove" this.onMouseMove passive=true}}
{{this.toggleCheckIfPossible}}
{{ChatOnLongPress
this.onLongPressStart
this.onLongPressEnd
this.onLongPressCancel
}}
...attributes
>
{{#if this.show}}
{{#if this.pane.selectingMessages}}
<Input
@type="checkbox"
class="chat-message-selector"
@checked={{@message.selected}}
{{on "click" this.toggleChecked}}
/>
{{/if}}
{{#if this.deletedAndCollapsed}}
<div class="chat-message-text -deleted">
<DButton
@action={{this.expand}}
@translatedLabel={{this.deletedMessageLabel}}
class="btn-flat chat-message-expand"
/>
</div>
{{else if this.hiddenAndCollapsed}}
<div class="chat-message-text -hidden">
<DButton
@action={{this.expand}}
@label="chat.hidden"
class="btn-flat chat-message-expand"
/>
</div>
{{else}}
<div class="chat-message">
{{#unless this.hideReplyToInfo}}
<ChatMessageInReplyToIndicator @message={{@message}} />
{{/unless}}
{{#if this.hideUserInfo}}
<ChatMessageLeftGutter @message={{@message}} />
{{else}}
<ChatMessageAvatar @message={{@message}} />
{{/if}}
<div class="chat-message-content">
<ChatMessageInfo
@message={{@message}}
@show={{not this.hideUserInfo}}
/>
<ChatMessageText
@cooked={{@message.cooked}}
@uploads={{@message.uploads}}
@edited={{@message.edited}}
>
{{#if @message.reactions.length}}
<div class="chat-message-reaction-list">
{{#each @message.reactions as |reaction|}}
<ChatMessageReaction
@reaction={{reaction}}
@onReaction={{this.messageInteractor.react}}
@message={{@message}}
@showTooltip={{true}}
/>
{{/each}}
{{#if this.shouldRenderOpenEmojiPickerButton}}
<DButton
@action={{this.messageInteractor.openEmojiPicker}}
@icon="discourse-emojis"
@title="chat.react"
@forwardEvent={{true}}
class="chat-message-react-btn"
/>
{{/if}}
</div>
{{/if}}
</ChatMessageText>
<ChatMessageError
@message={{@message}}
@onRetry={{@resendStagedMessage}}
/>
</div>
{{#if this.showThreadIndicator}}
<ChatMessageThreadIndicator @message={{@message}} />
{{/if}}
</div>
{{/if}}
{{/if}}
</div>
{{/if}}
</template>
}

View File

@ -3,12 +3,6 @@ import I18n from "I18n";
import { inject as service } from "@ember/service";
export default class ChatRetentionReminderText extends Component {
<template>
<span class="chat-retention-reminder-text">
{{this.text}}
</span>
</template>
@service siteSettings;
get text() {
@ -36,4 +30,10 @@ export default class ChatRetentionReminderText extends Component {
? this.siteSettings.chat_dm_retention_days
: this.siteSettings.chat_channel_retention_days;
}
<template>
<span class="chat-retention-reminder-text">
{{this.text}}
</span>
</template>
}

View File

@ -5,25 +5,6 @@ import { htmlSafe } from "@ember/template";
import concatClass from "discourse/helpers/concat-class";
export default class ChatUserAvatar extends Component {
<template>
<div
class={{concatClass "chat-user-avatar" (if this.isOnline "is-online")}}
data-username={{@user.username}}
>
{{#if this.interactive}}
<div
role="button"
class="chat-user-avatar__container clickable"
data-user-card={{@user.username}}
>
{{this.avatar}}
</div>
{{else}}
{{this.avatar}}
{{/if}}
</div>
</template>
@service chat;
get avatar() {
@ -55,4 +36,23 @@ export default class ChatUserAvatar extends Component {
)
);
}
<template>
<div
class={{concatClass "chat-user-avatar" (if this.isOnline "is-online")}}
data-username={{@user.username}}
>
{{#if this.interactive}}
<div
role="button"
class="chat-user-avatar__container clickable"
data-user-card={{@user.username}}
>
{{this.avatar}}
</div>
{{else}}
{{this.avatar}}
{{/if}}
</div>
</template>
}

View File

@ -2,6 +2,10 @@ import Component from "@glimmer/component";
import ChatFormRow from "discourse/plugins/chat/discourse/components/chat/form/row";
export default class ChatFormSection extends Component {
get yieldableArgs() {
return { row: ChatFormRow };
}
<template>
<div class="chat-form__section" ...attributes>
{{#if @title}}
@ -15,8 +19,4 @@ export default class ChatFormSection extends Component {
</div>
</div>
</template>
get yieldableArgs() {
return { row: ChatFormRow };
}
}

View File

@ -8,24 +8,6 @@ import concatClass from "discourse/helpers/concat-class";
import I18n from "I18n";
export default class ChatHeaderIcon extends Component {
<template>
<a
href={{this.href}}
tabindex="0"
class={{concatClass "icon" "btn-flat" (if this.isActive "active")}}
title={{this.title}}
>
{{~icon this.icon~}}
{{#if this.showUnreadIndicator}}
<ChatHeaderIconUnreadIndicator
@urgentCount={{@urgentCount}}
@unreadCount={{@unreadCount}}
@indicatorPreference={{@indicatorPreference}}
/>
{{/if}}
</a>
</template>
@service currentUser;
@service site;
@service chatStateManager;
@ -96,4 +78,22 @@ export default class ChatHeaderIcon extends Component {
return getURL(this.chatStateManager.lastKnownChatURL || "/chat");
}
<template>
<a
href={{this.href}}
tabindex="0"
class={{concatClass "icon" "btn-flat" (if this.isActive "active")}}
title={{this.title}}
>
{{~icon this.icon~}}
{{#if this.showUnreadIndicator}}
<ChatHeaderIconUnreadIndicator
@urgentCount={{@urgentCount}}
@unreadCount={{@unreadCount}}
@indicatorPreference={{@indicatorPreference}}
/>
{{/if}}
</a>
</template>
}

View File

@ -9,18 +9,6 @@ import {
const MAX_UNREAD_COUNT = 99;
export default class ChatHeaderIconUnreadIndicator extends Component {
<template>
{{#if this.showUrgentIndicator}}
<div class="chat-channel-unread-indicator -urgent">
<div class="chat-channel-unread-indicator__number">
{{this.unreadCountLabel}}
</div>
</div>
{{else if this.showUnreadIndicator}}
<div class="chat-channel-unread-indicator"></div>
{{/if}}
</template>
@service chatTrackingStateManager;
@service currentUser;
@ -78,4 +66,16 @@ export default class ChatHeaderIconUnreadIndicator extends Component {
return preferences.includes(this.indicatorPreference);
}
<template>
{{#if this.showUrgentIndicator}}
<div class="chat-channel-unread-indicator -urgent">
<div class="chat-channel-unread-indicator__number">
{{this.unreadCountLabel}}
</div>
</div>
{{else if this.showUnreadIndicator}}
<div class="chat-channel-unread-indicator"></div>
{{/if}}
</template>
}

View File

@ -12,49 +12,6 @@ import not from "truth-helpers/helpers/not";
import I18n from "I18n";
export default class ChatSelectionManager extends Component {
<template>
<div
class="chat-selection-management"
data-last-copy-successful={{this.lastCopySuccessful}}
>
<div class="chat-selection-management__buttons">
<DButton
@icon="quote-left"
@label="chat.selection.quote_selection"
@disabled={{not this.anyMessagesSelected}}
@action={{this.quoteMessages}}
id="chat-quote-btn"
/>
<DButton
@icon="copy"
@label="chat.selection.copy"
@disabled={{not this.anyMessagesSelected}}
@action={{this.copyMessages}}
id="chat-copy-btn"
/>
{{#if this.enableMove}}
<DButton
@icon="sign-out-alt"
@label="chat.selection.move_selection_to_channel"
@disabled={{not this.anyMessagesSelected}}
@action={{this.openMoveMessageModal}}
id="chat-move-to-channel-btn"
/>
{{/if}}
<DButton
@icon="times"
@label="chat.selection.cancel"
@action={{@pane.cancelSelecting}}
id="chat-cancel-selection-btn"
class="btn-secondary cancel-btn"
/>
</div>
</div>
</template>
@service("composer") topicComposer;
@service router;
@service modal;
@ -150,4 +107,47 @@ export default class ChatSelectionManager extends Component {
popupAjaxError(error);
}
}
<template>
<div
class="chat-selection-management"
data-last-copy-successful={{this.lastCopySuccessful}}
>
<div class="chat-selection-management__buttons">
<DButton
@icon="quote-left"
@label="chat.selection.quote_selection"
@disabled={{not this.anyMessagesSelected}}
@action={{this.quoteMessages}}
id="chat-quote-btn"
/>
<DButton
@icon="copy"
@label="chat.selection.copy"
@disabled={{not this.anyMessagesSelected}}
@action={{this.copyMessages}}
id="chat-copy-btn"
/>
{{#if this.enableMove}}
<DButton
@icon="sign-out-alt"
@label="chat.selection.move_selection_to_channel"
@disabled={{not this.anyMessagesSelected}}
@action={{this.openMoveMessageModal}}
id="chat-move-to-channel-btn"
/>
{{/if}}
<DButton
@icon="times"
@label="chat.selection.cancel"
@action={{@pane.cancelSelecting}}
id="chat-cancel-selection-btn"
class="btn-secondary cancel-btn"
/>
</div>
</div>
</template>
}

View File

@ -8,6 +8,25 @@ import { modifier } from "ember-modifier";
import { tracked } from "@glimmer/tracking";
export default class DcFilterInput extends Component {
@tracked isFocused = false;
focusState = modifier((element) => {
const focusInHandler = () => {
this.isFocused = true;
};
const focusOutHandler = () => {
this.isFocused = false;
};
element.addEventListener("focusin", focusInHandler);
element.addEventListener("focusout", focusOutHandler);
return () => {
element.removeEventListener("focusin", focusInHandler);
element.removeEventListener("focusout", focusOutHandler);
};
});
<template>
{{! template-lint-disable modifier-name-case }}
<div
@ -36,23 +55,4 @@ export default class DcFilterInput extends Component {
{{/if}}
</div>
</template>
@tracked isFocused = false;
focusState = modifier((element) => {
const focusInHandler = () => {
this.isFocused = true;
};
const focusOutHandler = () => {
this.isFocused = false;
};
element.addEventListener("focusin", focusInHandler);
element.addEventListener("focusout", focusOutHandler);
return () => {
element.removeEventListener("focusin", focusInHandler);
element.removeEventListener("focusout", focusOutHandler);
};
});
}