From 6e5d4ee4923a60bfc0c248d177b33262051642df Mon Sep 17 00:00:00 2001 From: Renato Atilio Date: Thu, 21 Nov 2024 13:29:12 -0300 Subject: [PATCH] DEV: refactor `composer` references on composer-container/-editor (#29629) Most of it is removing the ComposerContainer > ComposerEditor indirect references to the composer service, so ComposerEditor now deals with the service directly. Form template was moved from DEditor to ComposerEditor. --- .../app/components/composer-container.hbs | 31 +---- .../app/components/composer-editor.hbs | 84 +++++++----- .../app/components/composer-editor.js | 129 ++++++++++++------ .../discourse/app/components/d-editor.hbs | 128 ++++++++--------- .../discourse/app/components/d-editor.js | 26 +--- .../discourse/app/lib/uppy/composer-upload.js | 4 +- .../components/composer-editor-test.gjs | 28 ++-- .../stylesheets/common/base/compose.scss | 4 - 8 files changed, 214 insertions(+), 220 deletions(-) diff --git a/app/assets/javascripts/discourse/app/components/composer-container.hbs b/app/assets/javascripts/discourse/app/components/composer-container.hbs index edc15d9b4a6..21782830dca 100644 --- a/app/assets/javascripts/discourse/app/components/composer-container.hbs +++ b/app/assets/javascripts/discourse/app/components/composer-container.hbs @@ -102,36 +102,7 @@ /> - +
- {{yield}} - +{{#if this.showFormTemplateForm}} +
+
+
+ {{yield}} -{{#if this.allowUpload}} + {{#if (gt this.composer.formTemplateIds.length 1)}} + + {{/if}} +
+ + +
+
+
+{{else}} + + {{yield}} + +{{/if}} + +{{#if this.composer.allowUpload}} diff --git a/app/assets/javascripts/discourse/app/components/composer-editor.js b/app/assets/javascripts/discourse/app/components/composer-editor.js index 743081a0ac5..d2fc19d9bac 100644 --- a/app/assets/javascripts/discourse/app/components/composer-editor.js +++ b/app/assets/javascripts/discourse/app/components/composer-editor.js @@ -1,8 +1,8 @@ import Component from "@ember/component"; import EmberObject, { action, computed } from "@ember/object"; -import { alias } from "@ember/object/computed"; import { getOwner } from "@ember/owner"; import { next, schedule, throttle } from "@ember/runloop"; +import { service } from "@ember/service"; import { classNameBindings } from "@ember-decorators/component"; import { observes, on } from "@ember-decorators/object"; import { BasePlugin } from "@uppy/core"; @@ -87,14 +87,15 @@ export function addApiImageWrapperButtonClickEvent(fn) { const DEBOUNCE_FETCH_MS = 450; const DEBOUNCE_JIT_MS = 2000; -@classNameBindings("showToolbar:toolbar-visible", ":wmd-controls") +@classNameBindings("composer.showToolbar:toolbar-visible", ":wmd-controls") export default class ComposerEditor extends Component { + @service composer; + composerEventPrefix = "composer"; shouldBuildScrollMap = true; scrollMap = null; - processPreview = true; - @alias("composer") composerModel; + fileUploadElementId = "file-uploader"; init() { super.init(...arguments); @@ -103,14 +104,19 @@ export default class ComposerEditor extends Component { this.uppyComposerUpload = new UppyComposerUpload(getOwner(this), { composerEventPrefix: this.composerEventPrefix, - composerModel: this.composerModel, + composerModel: this.composer.model, uploadMarkdownResolvers, uploadPreProcessors, uploadHandlers, + fileUploadElementId: this.fileUploadElementId, }); } - @discourseComputed("composer.requiredCategoryMissing") + get topic() { + return this.composer.get("model.topic"); + } + + @discourseComputed("composer.model.requiredCategoryMissing") replyPlaceholder(requiredCategoryMissing) { if (requiredCategoryMissing) { return "composer.reply_placeholder_choose_category"; @@ -130,9 +136,9 @@ export default class ComposerEditor extends Component { return this.currentUser && this.currentUser.link_posting_access !== "none"; } - @observes("focusTarget") + @observes("composer.focusTarget") setFocus() { - if (this.focusTarget === "editor") { + if (this.composer.focusTarget === "editor") { putCursorAtEnd(this.element.querySelector("textarea")); } } @@ -193,11 +199,11 @@ export default class ComposerEditor extends Component { this._registerImageAltTextButtonClick(preview); // Focus on the body unless we have a title - if (!this.get("composer.canEditTitle")) { + if (!this.get("composer.model.canEditTitle")) { putCursorAtEnd(input); } - if (this.allowUpload) { + if (this.composer.allowUpload) { this.uppyComposerUpload.setup(this.element); } @@ -205,11 +211,11 @@ export default class ComposerEditor extends Component { } @discourseComputed( - "composer.reply", - "composer.replyLength", - "composer.missingReplyCharacters", - "composer.minimumPostLength", - "lastValidatedAt" + "composer.model.reply", + "composer.model.replyLength", + "composer.model.missingReplyCharacters", + "composer.model.minimumPostLength", + "composer.lastValidatedAt" ) validation( reply, @@ -254,9 +260,9 @@ export default class ComposerEditor extends Component { @computed("composer.{creatingTopic,editingFirstPost,creatingSharedDraft}") get _isNewTopic() { return ( - this.composer.creatingTopic || - this.composer.editingFirstPost || - this.composer.creatingSharedDraft + this.composer.model.creatingTopic || + this.composer.model.editingFirstPost || + this.composer.model.creatingSharedDraft ); } @@ -442,8 +448,8 @@ export default class ComposerEditor extends Component { _renderUnseenMentions(preview, unseen) { fetchUnseenMentions({ names: unseen, - topicId: this.get("composer.topic.id"), - allowedNames: this.get("composer.targetRecipients")?.split(","), + topicId: this.get("composer.model.topic.id"), + allowedNames: this.get("composer.model.targetRecipients")?.split(","), }).then((response) => { linkSeenMentions(preview, this.siteSettings); this._warnMentionedGroups(preview); @@ -510,7 +516,7 @@ export default class ComposerEditor extends Component { } this.warnedGroupMentions.push(name); - this.groupsMentioned({ + this.composer.groupsMentioned({ name, userCount: mention.dataset.mentionableUserCount, maxMentions: mention.dataset.maxMentions, @@ -523,7 +529,7 @@ export default class ComposerEditor extends Component { // previously we would warn after @bob even if you were about to mention @bob2 @debounce(DEBOUNCE_JIT_MS) _warnCannotSeeMention(preview) { - if (this.composer.draftKey === Composer.NEW_PRIVATE_MESSAGE_KEY) { + if (this.composer.model?.draftKey === Composer.NEW_PRIVATE_MESSAGE_KEY) { return; } @@ -534,7 +540,7 @@ export default class ComposerEditor extends Component { } this.warnedCannotSeeMentions.push(name); - this.cannotSeeMention({ + this.composer.cannotSeeMention({ name, reason: mention.dataset.reason, }); @@ -549,7 +555,7 @@ export default class ComposerEditor extends Component { } this.warnedCannotSeeMentions.push(name); - this.cannotSeeMention({ + this.composer.cannotSeeMention({ name, reason: mention.dataset.reason, notifiedCount: mention.dataset.notifiedUserCount, @@ -563,7 +569,7 @@ export default class ComposerEditor extends Component { return; } - this.hereMention(hereCount); + this.composer.hereMention(hereCount); } @bind @@ -578,8 +584,9 @@ export default class ComposerEditor extends Component { ); const scale = event.target.dataset.scale; - const matchingPlaceholder = - this.get("composer.reply").match(IMAGE_MARKDOWN_REGEX); + const matchingPlaceholder = this.get("composer.model.reply").match( + IMAGE_MARKDOWN_REGEX + ); if (matchingPlaceholder) { const match = matchingPlaceholder[index]; @@ -624,8 +631,9 @@ export default class ComposerEditor extends Component { commitAltText(buttonWrapper) { const index = parseInt(buttonWrapper.getAttribute("data-image-index"), 10); - const matchingPlaceholder = - this.get("composer.reply").match(IMAGE_MARKDOWN_REGEX); + const matchingPlaceholder = this.get("composer.model.reply").match( + IMAGE_MARKDOWN_REGEX + ); const match = matchingPlaceholder[index]; const input = buttonWrapper.querySelector("input.alt-text-input"); const replacement = match.replace( @@ -717,8 +725,9 @@ export default class ComposerEditor extends Component { event.target.closest(".button-wrapper").dataset.imageIndex, 10 ); - const matchingPlaceholder = - this.get("composer.reply").match(IMAGE_MARKDOWN_REGEX); + const matchingPlaceholder = this.get("composer.model.reply").match( + IMAGE_MARKDOWN_REGEX + ); this.appEvents.trigger( `${this.composerEventPrefix}:replace-text`, matchingPlaceholder[index], @@ -737,7 +746,7 @@ export default class ComposerEditor extends Component { event.target.closest(".button-wrapper").dataset.imageIndex, 10 ); - const reply = this.get("composer.reply"); + const reply = this.get("composer.model.reply"); const matches = reply.match(IMAGE_MARKDOWN_REGEX); const closingIndex = index + parseInt(event.target.dataset.imageCount, 10) - 1; @@ -757,6 +766,10 @@ export default class ComposerEditor extends Component { } _registerImageAltTextButtonClick(preview) { + if (!preview) { + return; + } + preview.addEventListener("click", this._handleAltTextCancelButtonClick); preview.addEventListener("click", this._handleAltTextEditButtonClick); preview.addEventListener("click", this._handleAltTextOkButtonClick); @@ -775,7 +788,7 @@ export default class ComposerEditor extends Component { const input = this.element.querySelector(".d-editor-input"); const preview = this.element.querySelector(".d-editor-preview-wrapper"); - if (this.allowUpload) { + if (this.composer.allowUpload) { this.uppyComposerUpload.teardown(); } @@ -811,11 +824,11 @@ export default class ComposerEditor extends Component { onExpandPopupMenuOptions(toolbarEvent) { const selected = toolbarEvent.selected; toolbarEvent.selectText(selected.start, selected.end - selected.start); - this.storeToolbarState(toolbarEvent); + this.composer.storeToolbarState(toolbarEvent); } showPreview() { - this.send("togglePreview"); + this.composer.togglePreview(); } _isInQuote(element) { @@ -848,16 +861,20 @@ export default class ComposerEditor extends Component { id: "quote", group: "fontStyles", icon: "far-comment", - sendAction: this.importQuote, + sendAction: this.composer.importQuote, title: "composer.quote_post_title", unshift: true, }); - if (this.allowUpload && this.uploadIcon && this.site.desktopView) { + if ( + this.composer.allowUpload && + this.composer.uploadIcon && + this.site.desktopView + ) { toolbar.addButton({ id: "upload", group: "insertions", - icon: this.uploadIcon, + icon: this.composer.uploadIcon, title: "upload", sendAction: this.showUploadModal, }); @@ -884,6 +901,40 @@ export default class ComposerEditor extends Component { this._decorateCookedElement(preview); } - this.afterRefresh(preview); + this.composer.afterRefresh(preview); + } + + @computed("composer.formTemplateIds") + get selectedFormTemplateId() { + if (this._selectedFormTemplateId) { + return this._selectedFormTemplateId; + } + + return ( + this.composer.model.formTemplateId || this.composer.formTemplateIds?.[0] + ); + } + + set selectedFormTemplateId(value) { + this._selectedFormTemplateId = value; + } + + @action + updateSelectedFormTemplateId(formTemplateId) { + this.selectedFormTemplateId = formTemplateId; + } + + @discourseComputed( + "composer.formTemplateIds", + "composer.model.replyingToTopic", + "composer.model.editingPost" + ) + showFormTemplateForm(formTemplateIds, replyingToTopic, editingPost) { + return formTemplateIds?.length > 0 && !replyingToTopic && !editingPost; + } + + @action + showUploadModal() { + document.getElementById(this.fileUploadElementId).click(); } } diff --git a/app/assets/javascripts/discourse/app/components/d-editor.hbs b/app/assets/javascripts/discourse/app/components/d-editor.hbs index 95d5f66a846..f509d8e73a9 100644 --- a/app/assets/javascripts/discourse/app/components/d-editor.hbs +++ b/app/assets/javascripts/discourse/app/components/d-editor.hbs @@ -1,81 +1,63 @@
{{yield}} - {{#if this.showFormTemplateForm}} - {{#if (gt @formTemplateIds.length 1)}} - - {{/if}} -
- - - {{else}} -
- - - - - +
+ - {{/if}} + + + + + +
0 && !replyingToTopic && !editingPost; - } - @discourseComputed("placeholder") placeholderTranslated(placeholder) { if (placeholder) { diff --git a/app/assets/javascripts/discourse/app/lib/uppy/composer-upload.js b/app/assets/javascripts/discourse/app/lib/uppy/composer-upload.js index 62ef632d87a..96779dd9d0c 100644 --- a/app/assets/javascripts/discourse/app/lib/uppy/composer-upload.js +++ b/app/assets/javascripts/discourse/app/lib/uppy/composer-upload.js @@ -44,7 +44,7 @@ export default class UppyComposerUpload { uploadType = "composer"; editorInputClass = ".d-editor-input"; mobileFileUploaderId = "mobile-file-upload"; - fileUploadElementId = "file-uploader"; + fileUploadElementId; editorClass = ".d-editor"; composerEventPrefix; @@ -73,6 +73,7 @@ export default class UppyComposerUpload { uploadMarkdownResolvers, uploadPreProcessors, uploadHandlers, + fileUploadElementId, } ) { setOwner(this, owner); @@ -82,6 +83,7 @@ export default class UppyComposerUpload { this.uploadMarkdownResolvers = uploadMarkdownResolvers; this.uploadPreProcessors = uploadPreProcessors; this.uploadHandlers = uploadHandlers; + this.fileUploadElementId = fileUploadElementId; } @bind diff --git a/app/assets/javascripts/discourse/tests/integration/components/composer-editor-test.gjs b/app/assets/javascripts/discourse/tests/integration/components/composer-editor-test.gjs index 68aff075d92..ecbbc001a67 100644 --- a/app/assets/javascripts/discourse/tests/integration/components/composer-editor-test.gjs +++ b/app/assets/javascripts/discourse/tests/integration/components/composer-editor-test.gjs @@ -8,8 +8,6 @@ module("Integration | Component | ComposerEditor", function (hooks) { setupRenderingTest(hooks); test("warns about users that will not see a mention", async function (assert) { - const model = {}; - const noop = () => {}; const expectation = (warning) => { if (warning.name === "user-no") { assert.deepEqual(warning, { name: "user-no", reason: "a reason" }); @@ -31,24 +29,24 @@ module("Integration | Component | ComposerEditor", function (hooks) { }); }); - await render(); + const originalComposerService = this.owner.lookup("service:composer"); + const composerMockClass = class ComposerMock extends originalComposerService.constructor { + cannotSeeMention() { + expectation(...arguments); + } + }; + this.owner.unregister("service:composer"); + this.owner.register("service:composer", new composerMockClass(this.owner), { + instantiate: false, + }); + + await render(); await fillIn("textarea", "@user-no @user-ok @user-nope"); }); test("preview sanitizes HTML", async function (assert) { - const model = {}; - const noop = () => {}; - - await render(); + await render(); await fillIn(".d-editor-input", `">`); assert.dom(".d-editor-preview").hasHtml('

">

'); diff --git a/app/assets/stylesheets/common/base/compose.scss b/app/assets/stylesheets/common/base/compose.scss index 6f34c1ba75e..9bfae4b16d8 100644 --- a/app/assets/stylesheets/common/base/compose.scss +++ b/app/assets/stylesheets/common/base/compose.scss @@ -421,10 +421,6 @@ html.composer-open { } } - #file-uploader { - display: none; - } - .composer-select-form-template { margin-bottom: 8px; width: 100%;