DEV: Modernize composer-messages implementation (#35627)

Replaces resolver-based template lookups with simple components.

Added a value transformer for plugins to modify the components if
needed. This does not appear to be a common or documented pattern, so
this change does not include backwards-compatibility for plugins which
may have been adding their own messages in the `templates` directory
This commit is contained in:
David Taylor
2025-10-27 12:42:38 +00:00
committed by GitHub
parent 65282022a1
commit 09e8a87316
12 changed files with 129 additions and 101 deletions
@@ -0,0 +1,51 @@
import concatClass from "discourse/helpers/concat-class";
import { applyMutableValueTransformer } from "discourse/lib/transformer";
import DominatingTopicComposerMessage from "./composer-messages/dominating-topic";
import EducationComposerMessage from "./composer-messages/education";
import GetARoomComposerMessage from "./composer-messages/get-a-room";
import GroupMentionedComposerMessage from "./composer-messages/group-mentioned";
import SimilarTopicsComposerMessage from "./composer-messages/similar-topics";
const COMPOSER_MESSAGES = {
"dominating-topic": DominatingTopicComposerMessage,
education: EducationComposerMessage,
"get-a-room": GetARoomComposerMessage,
"group-mentioned": GroupMentionedComposerMessage,
"similar-topics": SimilarTopicsComposerMessage,
};
function getComposerMessageComponent(templateName) {
const resolvedMessages = { ...COMPOSER_MESSAGES };
applyMutableValueTransformer("composer-message-components", resolvedMessages);
const result = resolvedMessages[templateName];
if (!result) {
// eslint-disable-next-line no-console
console.error(
`Composer message component not found for template name: ${templateName}`
);
}
return result;
}
const ComposerMessage = <template>
<div class={{concatClass "composer-popup" @message.extraClass}}>
{{#let
(getComposerMessageComponent @message.templateName)
as |MessageComponent|
}}
{{#if MessageComponent}}
<MessageComponent
@message={{@message}}
@closeMessage={{@closeMessage}}
@shareModal={{@shareModal}}
@switchPM={{this.switchPM}}
/>
{{/if}}
{{/let}}
</div>
</template>;
export default ComposerMessage;
@@ -1,13 +0,0 @@
/* eslint-disable ember/no-classic-components */
import Component from "@ember/component";
import { getOwner } from "@ember/owner";
import { classNameBindings } from "@ember-decorators/component";
import discourseComputed from "discourse/lib/decorators";
@classNameBindings(":composer-popup", "message.extraClass")
export default class ComposerMessage extends Component {
@discourseComputed("message.templateName")
layout(templateName) {
return getOwner(this).lookup(`template:composer/${templateName}`);
}
}
@@ -1,24 +1,25 @@
import Component from "@glimmer/component";
import { fn } from "@ember/helper";
import { service } from "@ember/service";
import { htmlSafe } from "@ember/template";
import RouteTemplate from "ember-route-template";
import ComposerTipCloseButton from "discourse/components/composer-tip-close-button";
import DButton from "discourse/components/d-button";
export default RouteTemplate(
export default class DominatingTopicComposerMessage extends Component {
@service currentUser;
<template>
<ComposerTipCloseButton
@action={{fn @controller.closeMessage @controller.message}}
/>
<ComposerTipCloseButton @action={{fn @closeMessage @message}} />
{{htmlSafe @controller.message.body}}
{{htmlSafe @message.body}}
{{#if @controller.currentUser.can_invite_to_forum}}
{{#if this.currentUser.can_invite_to_forum}}
<DButton
@label="footer_nav.share"
@icon="link"
@action={{@controller.shareModal}}
@action={{@shareModal}}
class="btn-primary"
/>
{{/if}}
</template>
);
}
@@ -0,0 +1,15 @@
import { fn } from "@ember/helper";
import { htmlSafe } from "@ember/template";
import ComposerTipCloseButton from "discourse/components/composer-tip-close-button";
const EducationComposerMessage = <template>
<ComposerTipCloseButton @action={{fn @closeMessage @message}} />
{{#if @message.title}}
<h3>{{@message.title}}</h3>
{{/if}}
{{htmlSafe @message.body}}
</template>;
export default EducationComposerMessage;
@@ -0,0 +1,19 @@
import { fn } from "@ember/helper";
import { htmlSafe } from "@ember/template";
import ComposerTipCloseButton from "discourse/components/composer-tip-close-button";
import DButton from "discourse/components/d-button";
const GetARoomComposerMessage = <template>
<ComposerTipCloseButton @action={{fn @closeMessage @message}} />
{{htmlSafe @message.body}}
<DButton
@label="user.private_message"
@icon="envelope"
@action={{fn @switchPM @message}}
class="btn-primary"
/>
</template>;
export default GetARoomComposerMessage;
@@ -0,0 +1,13 @@
import { fn } from "@ember/helper";
import { htmlSafe } from "@ember/template";
import ComposerTipCloseButton from "discourse/components/composer-tip-close-button";
const GroupMentionedComposerMessage = <template>
<ComposerTipCloseButton @action={{fn @closeMessage @message}} />
<p>
{{htmlSafe @message.body}}
</p>
</template>;
export default GroupMentionedComposerMessage;
@@ -0,0 +1,20 @@
import { fn } from "@ember/helper";
import ComposerTipCloseButton from "discourse/components/composer-tip-close-button";
import Topic from "discourse/components/search-menu/results/type/topic";
import { i18n } from "discourse-i18n";
const SimilarTopicsComposerMessage = <template>
<ComposerTipCloseButton @action={{fn @closeMessage @message}} />
<h3>{{i18n "composer.similar_topics"}}</h3>
<ul class="topics">
{{#each @message.similarTopics as |topic|}}
<div class="similar-topic">
<Topic @result={{topic}} @withTopicUrl={{true}} />
</div>
{{/each}}
</ul>
</template>;
export default SimilarTopicsComposerMessage;
@@ -21,6 +21,7 @@ export const VALUE_TRANSFORMERS = Object.freeze([
"category-text-color",
"composer-editor-quoted-post-avatar-template",
"composer-editor-reply-placeholder",
"composer-message-components",
"composer-reply-options-user-link-name",
"composer-reply-options-user-avatar-template",
"composer-save-button-label",
@@ -1,18 +0,0 @@
import { fn } from "@ember/helper";
import { htmlSafe } from "@ember/template";
import RouteTemplate from "ember-route-template";
import ComposerTipCloseButton from "discourse/components/composer-tip-close-button";
export default RouteTemplate(
<template>
<ComposerTipCloseButton
@action={{fn @controller.closeMessage @controller.message}}
/>
{{#if @controller.message.title}}
<h3>{{@controller.message.title}}</h3>
{{/if}}
{{htmlSafe @controller.message.body}}
</template>
);
@@ -1,22 +0,0 @@
import { fn } from "@ember/helper";
import { htmlSafe } from "@ember/template";
import RouteTemplate from "ember-route-template";
import ComposerTipCloseButton from "discourse/components/composer-tip-close-button";
import DButton from "discourse/components/d-button";
export default RouteTemplate(
<template>
<ComposerTipCloseButton
@action={{fn @controller.closeMessage @controller.message}}
/>
{{htmlSafe @controller.message.body}}
<DButton
@label="user.private_message"
@icon="envelope"
@action={{fn @controller.switchPM @controller.message}}
class="btn-primary"
/>
</template>
);
@@ -1,16 +0,0 @@
import { fn } from "@ember/helper";
import { htmlSafe } from "@ember/template";
import RouteTemplate from "ember-route-template";
import ComposerTipCloseButton from "discourse/components/composer-tip-close-button";
export default RouteTemplate(
<template>
<ComposerTipCloseButton
@action={{fn @controller.closeMessage @controller.message}}
/>
<p>
{{htmlSafe @controller.message.body}}
</p>
</template>
);
@@ -1,23 +0,0 @@
import { fn } from "@ember/helper";
import RouteTemplate from "ember-route-template";
import ComposerTipCloseButton from "discourse/components/composer-tip-close-button";
import Topic from "discourse/components/search-menu/results/type/topic";
import { i18n } from "discourse-i18n";
export default RouteTemplate(
<template>
<ComposerTipCloseButton
@action={{fn @controller.closeMessage @controller.message}}
/>
<h3>{{i18n "composer.similar_topics"}}</h3>
<ul class="topics">
{{#each @controller.message.similarTopics as |topic|}}
<div class="similar-topic">
<Topic @result={{topic}} @withTopicUrl={{true}} />
</div>
{{/each}}
</ul>
</template>
);