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.
This commit is contained in:
Renato Atilio 2024-11-21 13:29:12 -03:00 committed by GitHub
parent 8fd2980685
commit 6e5d4ee492
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 214 additions and 220 deletions

View File

@ -102,36 +102,7 @@
/> />
</div> </div>
<ComposerEditor <ComposerEditor>
@topic={{this.composer.topic}}
@composer={{this.composer.model}}
@lastValidatedAt={{this.composer.lastValidatedAt}}
@canWhisper={{this.composer.canWhisper}}
@storeToolbarState={{this.composer.storeToolbarState}}
@onPopupMenuAction={{this.composer.onPopupMenuAction}}
@showUploadModal={{route-action "showUploadSelector"}}
@popupMenuOptions={{this.composer.popupMenuOptions}}
@draftStatus={{this.composer.model.draftStatus}}
@isUploading={{this.composer.isUploading}}
@isProcessingUpload={{this.composer.isProcessingUpload}}
@allowUpload={{this.composer.allowUpload}}
@uploadIcon={{this.composer.uploadIcon}}
@isCancellable={{this.composer.isCancellable}}
@uploadProgress={{this.composer.uploadProgress}}
@groupsMentioned={{this.composer.groupsMentioned}}
@cannotSeeMention={{this.composer.cannotSeeMention}}
@hereMention={{this.composer.hereMention}}
@importQuote={{this.composer.importQuote}}
@togglePreview={{this.composer.togglePreview}}
@processPreview={{this.composer.showPreview}}
@showToolbar={{this.composer.showToolbar}}
@afterRefresh={{this.composer.afterRefresh}}
@focusTarget={{this.composer.focusTarget}}
@disableTextarea={{this.composer.disableTextarea}}
@formTemplateIds={{this.composer.formTemplateIds}}
@formTemplateInitialValues={{this.composer.formTemplateInitialValues}}
@onSelectFormTemplate={{this.composer.onSelectFormTemplate}}
>
<div class="composer-fields"> <div class="composer-fields">
<PluginOutlet <PluginOutlet
@name="before-composer-fields" @name="before-composer-fields"

View File

@ -1,38 +1,56 @@
<DEditor {{#if this.showFormTemplateForm}}
@value={{this.composer.reply}} <div class="d-editor">
@placeholder={{this.replyPlaceholder}} <div class="d-editor-container">
@previewUpdated={{action "previewUpdated"}} <div class="d-editor-textarea-column">
@markdownOptions={{this.markdownOptions}} {{yield}}
@extraButtons={{action "extraButtons"}}
@importQuote={{this.importQuote}}
@showUploadModal={{this.showUploadModal}}
@togglePreview={{this.togglePreview}}
@processPreview={{this.processPreview}}
@validation={{this.validation}}
@loading={{this.composer.loading}}
@forcePreview={{this.forcePreview}}
@showLink={{this.showLink}}
@composerEvents={{true}}
@onExpandPopupMenuOptions={{action "onExpandPopupMenuOptions"}}
@onPopupMenuAction={{this.onPopupMenuAction}}
@popupMenuOptions={{this.popupMenuOptions}}
@formTemplateId={{this.composer.formTemplateId}}
@formTemplateIds={{this.formTemplateIds}}
@formTemplateInitialValues={{@formTemplateInitialValues}}
@onSelectFormTemplate={{@onSelectFormTemplate}}
@replyingToTopic={{this.composer.replyingToTopic}}
@editingPost={{this.composer.editingPost}}
@disabled={{this.disableTextarea}}
@outletArgs={{hash composer=this.composer editorType="composer"}}
@topicId={{this.composer.topic.id}}
@categoryId={{this.composer.category.id}}
>
{{yield}}
</DEditor>
{{#if this.allowUpload}} {{#if (gt this.composer.formTemplateIds.length 1)}}
<FormTemplateChooser
@filteredIds={{this.composer.formTemplateIds}}
@value={{this.selectedFormTemplateId}}
@onChange={{this.updateSelectedFormTemplateId}}
@options={{hash maximum=1}}
class="composer-select-form-template"
/>
{{/if}}
<form id="form-template-form">
<FormTemplateField::Wrapper
@id={{this.selectedFormTemplateId}}
@initialValues={{this.composer.formTemplateInitialValues}}
@onSelectFormTemplate={{this.composer.onSelectFormTemplate}}
/>
</form>
</div>
</div>
</div>
{{else}}
<DEditor
@value={{this.composer.model.reply}}
@placeholder={{this.replyPlaceholder}}
@previewUpdated={{action "previewUpdated"}}
@markdownOptions={{this.markdownOptions}}
@extraButtons={{action "extraButtons"}}
@importQuote={{this.composer.importQuote}}
@processPreview={{this.composer.showPreview}}
@validation={{this.validation}}
@loading={{this.composer.loading}}
@forcePreview={{this.forcePreview}}
@showLink={{this.showLink}}
@composerEvents={{true}}
@onPopupMenuAction={{this.composer.onPopupMenuAction}}
@popupMenuOptions={{this.composer.popupMenuOptions}}
@disabled={{this.composer.disableTextarea}}
@outletArgs={{hash composer=this.composer.model editorType="composer"}}
@topicId={{this.composer.model.topic.id}}
@categoryId={{this.composer.model.category.id}}
>
{{yield}}
</DEditor>
{{/if}}
{{#if this.composer.allowUpload}}
<PickFilesButton <PickFilesButton
@fileInputId="file-uploader" @fileInputId={{this.fileUploadElementId}}
@allowMultiple={{true}} @allowMultiple={{true}}
name="file-uploader" name="file-uploader"
/> />

View File

@ -1,8 +1,8 @@
import Component from "@ember/component"; import Component from "@ember/component";
import EmberObject, { action, computed } from "@ember/object"; import EmberObject, { action, computed } from "@ember/object";
import { alias } from "@ember/object/computed";
import { getOwner } from "@ember/owner"; import { getOwner } from "@ember/owner";
import { next, schedule, throttle } from "@ember/runloop"; import { next, schedule, throttle } from "@ember/runloop";
import { service } from "@ember/service";
import { classNameBindings } from "@ember-decorators/component"; import { classNameBindings } from "@ember-decorators/component";
import { observes, on } from "@ember-decorators/object"; import { observes, on } from "@ember-decorators/object";
import { BasePlugin } from "@uppy/core"; import { BasePlugin } from "@uppy/core";
@ -87,14 +87,15 @@ export function addApiImageWrapperButtonClickEvent(fn) {
const DEBOUNCE_FETCH_MS = 450; const DEBOUNCE_FETCH_MS = 450;
const DEBOUNCE_JIT_MS = 2000; const DEBOUNCE_JIT_MS = 2000;
@classNameBindings("showToolbar:toolbar-visible", ":wmd-controls") @classNameBindings("composer.showToolbar:toolbar-visible", ":wmd-controls")
export default class ComposerEditor extends Component { export default class ComposerEditor extends Component {
@service composer;
composerEventPrefix = "composer"; composerEventPrefix = "composer";
shouldBuildScrollMap = true; shouldBuildScrollMap = true;
scrollMap = null; scrollMap = null;
processPreview = true;
@alias("composer") composerModel; fileUploadElementId = "file-uploader";
init() { init() {
super.init(...arguments); super.init(...arguments);
@ -103,14 +104,19 @@ export default class ComposerEditor extends Component {
this.uppyComposerUpload = new UppyComposerUpload(getOwner(this), { this.uppyComposerUpload = new UppyComposerUpload(getOwner(this), {
composerEventPrefix: this.composerEventPrefix, composerEventPrefix: this.composerEventPrefix,
composerModel: this.composerModel, composerModel: this.composer.model,
uploadMarkdownResolvers, uploadMarkdownResolvers,
uploadPreProcessors, uploadPreProcessors,
uploadHandlers, uploadHandlers,
fileUploadElementId: this.fileUploadElementId,
}); });
} }
@discourseComputed("composer.requiredCategoryMissing") get topic() {
return this.composer.get("model.topic");
}
@discourseComputed("composer.model.requiredCategoryMissing")
replyPlaceholder(requiredCategoryMissing) { replyPlaceholder(requiredCategoryMissing) {
if (requiredCategoryMissing) { if (requiredCategoryMissing) {
return "composer.reply_placeholder_choose_category"; 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"; return this.currentUser && this.currentUser.link_posting_access !== "none";
} }
@observes("focusTarget") @observes("composer.focusTarget")
setFocus() { setFocus() {
if (this.focusTarget === "editor") { if (this.composer.focusTarget === "editor") {
putCursorAtEnd(this.element.querySelector("textarea")); putCursorAtEnd(this.element.querySelector("textarea"));
} }
} }
@ -193,11 +199,11 @@ export default class ComposerEditor extends Component {
this._registerImageAltTextButtonClick(preview); this._registerImageAltTextButtonClick(preview);
// Focus on the body unless we have a title // Focus on the body unless we have a title
if (!this.get("composer.canEditTitle")) { if (!this.get("composer.model.canEditTitle")) {
putCursorAtEnd(input); putCursorAtEnd(input);
} }
if (this.allowUpload) { if (this.composer.allowUpload) {
this.uppyComposerUpload.setup(this.element); this.uppyComposerUpload.setup(this.element);
} }
@ -205,11 +211,11 @@ export default class ComposerEditor extends Component {
} }
@discourseComputed( @discourseComputed(
"composer.reply", "composer.model.reply",
"composer.replyLength", "composer.model.replyLength",
"composer.missingReplyCharacters", "composer.model.missingReplyCharacters",
"composer.minimumPostLength", "composer.model.minimumPostLength",
"lastValidatedAt" "composer.lastValidatedAt"
) )
validation( validation(
reply, reply,
@ -254,9 +260,9 @@ export default class ComposerEditor extends Component {
@computed("composer.{creatingTopic,editingFirstPost,creatingSharedDraft}") @computed("composer.{creatingTopic,editingFirstPost,creatingSharedDraft}")
get _isNewTopic() { get _isNewTopic() {
return ( return (
this.composer.creatingTopic || this.composer.model.creatingTopic ||
this.composer.editingFirstPost || this.composer.model.editingFirstPost ||
this.composer.creatingSharedDraft this.composer.model.creatingSharedDraft
); );
} }
@ -442,8 +448,8 @@ export default class ComposerEditor extends Component {
_renderUnseenMentions(preview, unseen) { _renderUnseenMentions(preview, unseen) {
fetchUnseenMentions({ fetchUnseenMentions({
names: unseen, names: unseen,
topicId: this.get("composer.topic.id"), topicId: this.get("composer.model.topic.id"),
allowedNames: this.get("composer.targetRecipients")?.split(","), allowedNames: this.get("composer.model.targetRecipients")?.split(","),
}).then((response) => { }).then((response) => {
linkSeenMentions(preview, this.siteSettings); linkSeenMentions(preview, this.siteSettings);
this._warnMentionedGroups(preview); this._warnMentionedGroups(preview);
@ -510,7 +516,7 @@ export default class ComposerEditor extends Component {
} }
this.warnedGroupMentions.push(name); this.warnedGroupMentions.push(name);
this.groupsMentioned({ this.composer.groupsMentioned({
name, name,
userCount: mention.dataset.mentionableUserCount, userCount: mention.dataset.mentionableUserCount,
maxMentions: mention.dataset.maxMentions, 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 // previously we would warn after @bob even if you were about to mention @bob2
@debounce(DEBOUNCE_JIT_MS) @debounce(DEBOUNCE_JIT_MS)
_warnCannotSeeMention(preview) { _warnCannotSeeMention(preview) {
if (this.composer.draftKey === Composer.NEW_PRIVATE_MESSAGE_KEY) { if (this.composer.model?.draftKey === Composer.NEW_PRIVATE_MESSAGE_KEY) {
return; return;
} }
@ -534,7 +540,7 @@ export default class ComposerEditor extends Component {
} }
this.warnedCannotSeeMentions.push(name); this.warnedCannotSeeMentions.push(name);
this.cannotSeeMention({ this.composer.cannotSeeMention({
name, name,
reason: mention.dataset.reason, reason: mention.dataset.reason,
}); });
@ -549,7 +555,7 @@ export default class ComposerEditor extends Component {
} }
this.warnedCannotSeeMentions.push(name); this.warnedCannotSeeMentions.push(name);
this.cannotSeeMention({ this.composer.cannotSeeMention({
name, name,
reason: mention.dataset.reason, reason: mention.dataset.reason,
notifiedCount: mention.dataset.notifiedUserCount, notifiedCount: mention.dataset.notifiedUserCount,
@ -563,7 +569,7 @@ export default class ComposerEditor extends Component {
return; return;
} }
this.hereMention(hereCount); this.composer.hereMention(hereCount);
} }
@bind @bind
@ -578,8 +584,9 @@ export default class ComposerEditor extends Component {
); );
const scale = event.target.dataset.scale; const scale = event.target.dataset.scale;
const matchingPlaceholder = const matchingPlaceholder = this.get("composer.model.reply").match(
this.get("composer.reply").match(IMAGE_MARKDOWN_REGEX); IMAGE_MARKDOWN_REGEX
);
if (matchingPlaceholder) { if (matchingPlaceholder) {
const match = matchingPlaceholder[index]; const match = matchingPlaceholder[index];
@ -624,8 +631,9 @@ export default class ComposerEditor extends Component {
commitAltText(buttonWrapper) { commitAltText(buttonWrapper) {
const index = parseInt(buttonWrapper.getAttribute("data-image-index"), 10); const index = parseInt(buttonWrapper.getAttribute("data-image-index"), 10);
const matchingPlaceholder = const matchingPlaceholder = this.get("composer.model.reply").match(
this.get("composer.reply").match(IMAGE_MARKDOWN_REGEX); IMAGE_MARKDOWN_REGEX
);
const match = matchingPlaceholder[index]; const match = matchingPlaceholder[index];
const input = buttonWrapper.querySelector("input.alt-text-input"); const input = buttonWrapper.querySelector("input.alt-text-input");
const replacement = match.replace( const replacement = match.replace(
@ -717,8 +725,9 @@ export default class ComposerEditor extends Component {
event.target.closest(".button-wrapper").dataset.imageIndex, event.target.closest(".button-wrapper").dataset.imageIndex,
10 10
); );
const matchingPlaceholder = const matchingPlaceholder = this.get("composer.model.reply").match(
this.get("composer.reply").match(IMAGE_MARKDOWN_REGEX); IMAGE_MARKDOWN_REGEX
);
this.appEvents.trigger( this.appEvents.trigger(
`${this.composerEventPrefix}:replace-text`, `${this.composerEventPrefix}:replace-text`,
matchingPlaceholder[index], matchingPlaceholder[index],
@ -737,7 +746,7 @@ export default class ComposerEditor extends Component {
event.target.closest(".button-wrapper").dataset.imageIndex, event.target.closest(".button-wrapper").dataset.imageIndex,
10 10
); );
const reply = this.get("composer.reply"); const reply = this.get("composer.model.reply");
const matches = reply.match(IMAGE_MARKDOWN_REGEX); const matches = reply.match(IMAGE_MARKDOWN_REGEX);
const closingIndex = const closingIndex =
index + parseInt(event.target.dataset.imageCount, 10) - 1; index + parseInt(event.target.dataset.imageCount, 10) - 1;
@ -757,6 +766,10 @@ export default class ComposerEditor extends Component {
} }
_registerImageAltTextButtonClick(preview) { _registerImageAltTextButtonClick(preview) {
if (!preview) {
return;
}
preview.addEventListener("click", this._handleAltTextCancelButtonClick); preview.addEventListener("click", this._handleAltTextCancelButtonClick);
preview.addEventListener("click", this._handleAltTextEditButtonClick); preview.addEventListener("click", this._handleAltTextEditButtonClick);
preview.addEventListener("click", this._handleAltTextOkButtonClick); preview.addEventListener("click", this._handleAltTextOkButtonClick);
@ -775,7 +788,7 @@ export default class ComposerEditor extends Component {
const input = this.element.querySelector(".d-editor-input"); const input = this.element.querySelector(".d-editor-input");
const preview = this.element.querySelector(".d-editor-preview-wrapper"); const preview = this.element.querySelector(".d-editor-preview-wrapper");
if (this.allowUpload) { if (this.composer.allowUpload) {
this.uppyComposerUpload.teardown(); this.uppyComposerUpload.teardown();
} }
@ -811,11 +824,11 @@ export default class ComposerEditor extends Component {
onExpandPopupMenuOptions(toolbarEvent) { onExpandPopupMenuOptions(toolbarEvent) {
const selected = toolbarEvent.selected; const selected = toolbarEvent.selected;
toolbarEvent.selectText(selected.start, selected.end - selected.start); toolbarEvent.selectText(selected.start, selected.end - selected.start);
this.storeToolbarState(toolbarEvent); this.composer.storeToolbarState(toolbarEvent);
} }
showPreview() { showPreview() {
this.send("togglePreview"); this.composer.togglePreview();
} }
_isInQuote(element) { _isInQuote(element) {
@ -848,16 +861,20 @@ export default class ComposerEditor extends Component {
id: "quote", id: "quote",
group: "fontStyles", group: "fontStyles",
icon: "far-comment", icon: "far-comment",
sendAction: this.importQuote, sendAction: this.composer.importQuote,
title: "composer.quote_post_title", title: "composer.quote_post_title",
unshift: true, unshift: true,
}); });
if (this.allowUpload && this.uploadIcon && this.site.desktopView) { if (
this.composer.allowUpload &&
this.composer.uploadIcon &&
this.site.desktopView
) {
toolbar.addButton({ toolbar.addButton({
id: "upload", id: "upload",
group: "insertions", group: "insertions",
icon: this.uploadIcon, icon: this.composer.uploadIcon,
title: "upload", title: "upload",
sendAction: this.showUploadModal, sendAction: this.showUploadModal,
}); });
@ -884,6 +901,40 @@ export default class ComposerEditor extends Component {
this._decorateCookedElement(preview); 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();
} }
} }

View File

@ -1,81 +1,63 @@
<div class="d-editor-container"> <div class="d-editor-container">
<div class="d-editor-textarea-column"> <div class="d-editor-textarea-column">
{{yield}} {{yield}}
{{#if this.showFormTemplateForm}}
{{#if (gt @formTemplateIds.length 1)}}
<FormTemplateChooser
@filteredIds={{@formTemplateIds}}
@value={{this.selectedFormTemplateId}}
@onChange={{this.updateSelectedFormTemplateId}}
@options={{hash maximum=1}}
class="composer-select-form-template"
/>
{{/if}}
<form id="form-template-form">
<FormTemplateField::Wrapper
@id={{this.selectedFormTemplateId}}
@initialValues={{@formTemplateInitialValues}}
@onSelectFormTemplate={{@onSelectFormTemplate}}
/>
</form>
{{else}}
<div
class="d-editor-textarea-wrapper
{{if this.disabled 'disabled'}}
{{if this.isEditorFocused 'in-focus'}}"
>
<div class="d-editor-button-bar" role="toolbar">
{{#each this.toolbar.groups as |group|}}
{{#each group.buttons as |b|}}
{{#if (b.condition this)}}
{{#if b.popupMenu}}
<ToolbarPopupMenuOptions
@content={{this.popupMenuOptions}}
@onChange={{this.onPopupMenuAction}}
@onOpen={{action b.action b}}
@tabindex={{-1}}
@onKeydown={{this.rovingButtonBar}}
@options={{hash icon=b.icon focusAfterOnChange=false}}
class={{b.className}}
/>
{{else}}
<DButton
@action={{fn (action b.action) b}}
@translatedTitle={{b.title}}
@label={{b.label}}
@icon={{b.icon}}
@preventFocus={{b.preventFocus}}
@onKeyDown={{this.rovingButtonBar}}
tabindex={{b.tabindex}}
class={{b.className}}
/>
{{/if}}
{{/if}}
{{/each}}
{{/each}}
</div>
<ConditionalLoadingSpinner @condition={{this.loading}} /> <div
<this.editorComponent class="d-editor-textarea-wrapper
@onSetup={{this.setupEditor}} {{if this.disabled 'disabled'}}
@markdownOptions={{this.markdownOptions}} {{if this.isEditorFocused 'in-focus'}}"
@keymap={{this.keymap}} >
@value={{this.value}} <div class="d-editor-button-bar" role="toolbar">
@placeholder={{this.placeholderTranslated}} {{#each this.toolbar.groups as |group|}}
@disabled={{this.disabled}} {{#each group.buttons as |b|}}
@change={{this.change}} {{#if (b.condition this)}}
@focusIn={{this.handleFocusIn}} {{#if b.popupMenu}}
@focusOut={{this.handleFocusOut}} <ToolbarPopupMenuOptions
@id={{this.textAreaId}} @content={{this.popupMenuOptions}}
/> @onChange={{this.onPopupMenuAction}}
<PopupInputTip @validation={{this.validation}} /> @onOpen={{action b.action b}}
<PluginOutlet @tabindex={{-1}}
@name="after-d-editor" @onKeydown={{this.rovingButtonBar}}
@connectorTagName="div" @options={{hash icon=b.icon focusAfterOnChange=false}}
@outletArgs={{this.outletArgs}} class={{b.className}}
/> />
{{else}}
<DButton
@action={{fn (action b.action) b}}
@translatedTitle={{b.title}}
@label={{b.label}}
@icon={{b.icon}}
@preventFocus={{b.preventFocus}}
@onKeyDown={{this.rovingButtonBar}}
tabindex={{b.tabindex}}
class={{b.className}}
/>
{{/if}}
{{/if}}
{{/each}}
{{/each}}
</div> </div>
{{/if}}
<ConditionalLoadingSpinner @condition={{this.loading}} />
<this.editorComponent
@onSetup={{this.setupEditor}}
@markdownOptions={{this.markdownOptions}}
@keymap={{this.keymap}}
@value={{this.value}}
@placeholder={{this.placeholderTranslated}}
@disabled={{this.disabled}}
@change={{this.change}}
@focusIn={{this.handleFocusIn}}
@focusOut={{this.handleFocusOut}}
@id={{this.textAreaId}}
/>
<PopupInputTip @validation={{this.validation}} />
<PluginOutlet
@name="after-d-editor"
@connectorTagName="div"
@outletArgs={{this.outletArgs}}
/>
</div>
</div> </div>
<div <div

View File

@ -1,5 +1,5 @@
import Component from "@ember/component"; import Component from "@ember/component";
import { action, computed } from "@ember/object"; import { action } from "@ember/object";
import { getOwner } from "@ember/owner"; import { getOwner } from "@ember/owner";
import { schedule, scheduleOnce } from "@ember/runloop"; import { schedule, scheduleOnce } from "@ember/runloop";
import { service } from "@ember/service"; import { service } from "@ember/service";
@ -82,30 +82,6 @@ export default class DEditor extends Component {
this.register = getRegister(this); this.register = getRegister(this);
} }
@computed("formTemplateIds")
get selectedFormTemplateId() {
if (this._selectedFormTemplateId) {
return this._selectedFormTemplateId;
}
return this.formTemplateId || this.formTemplateIds?.[0];
}
set selectedFormTemplateId(value) {
this._selectedFormTemplateId = value;
}
@action
updateSelectedFormTemplateId(formTemplateId) {
this.selectedFormTemplateId = formTemplateId;
}
@discourseComputed("formTemplateIds", "replyingToTopic", "editingPost")
showFormTemplateForm(formTemplateIds, replyingToTopic, editingPost) {
// TODO(@keegan): Remove !editingPost once we add edit/draft support for form templates
return formTemplateIds?.length > 0 && !replyingToTopic && !editingPost;
}
@discourseComputed("placeholder") @discourseComputed("placeholder")
placeholderTranslated(placeholder) { placeholderTranslated(placeholder) {
if (placeholder) { if (placeholder) {

View File

@ -44,7 +44,7 @@ export default class UppyComposerUpload {
uploadType = "composer"; uploadType = "composer";
editorInputClass = ".d-editor-input"; editorInputClass = ".d-editor-input";
mobileFileUploaderId = "mobile-file-upload"; mobileFileUploaderId = "mobile-file-upload";
fileUploadElementId = "file-uploader"; fileUploadElementId;
editorClass = ".d-editor"; editorClass = ".d-editor";
composerEventPrefix; composerEventPrefix;
@ -73,6 +73,7 @@ export default class UppyComposerUpload {
uploadMarkdownResolvers, uploadMarkdownResolvers,
uploadPreProcessors, uploadPreProcessors,
uploadHandlers, uploadHandlers,
fileUploadElementId,
} }
) { ) {
setOwner(this, owner); setOwner(this, owner);
@ -82,6 +83,7 @@ export default class UppyComposerUpload {
this.uploadMarkdownResolvers = uploadMarkdownResolvers; this.uploadMarkdownResolvers = uploadMarkdownResolvers;
this.uploadPreProcessors = uploadPreProcessors; this.uploadPreProcessors = uploadPreProcessors;
this.uploadHandlers = uploadHandlers; this.uploadHandlers = uploadHandlers;
this.fileUploadElementId = fileUploadElementId;
} }
@bind @bind

View File

@ -8,8 +8,6 @@ module("Integration | Component | ComposerEditor", function (hooks) {
setupRenderingTest(hooks); setupRenderingTest(hooks);
test("warns about users that will not see a mention", async function (assert) { test("warns about users that will not see a mention", async function (assert) {
const model = {};
const noop = () => {};
const expectation = (warning) => { const expectation = (warning) => {
if (warning.name === "user-no") { if (warning.name === "user-no") {
assert.deepEqual(warning, { name: "user-no", reason: "a reason" }); assert.deepEqual(warning, { name: "user-no", reason: "a reason" });
@ -31,24 +29,24 @@ module("Integration | Component | ComposerEditor", function (hooks) {
}); });
}); });
await render(<template> const originalComposerService = this.owner.lookup("service:composer");
<ComposerEditor const composerMockClass = class ComposerMock extends originalComposerService.constructor {
@composer={{model}} cannotSeeMention() {
@afterRefresh={{noop}} expectation(...arguments);
@cannotSeeMention={{expectation}} }
/> };
</template>); this.owner.unregister("service:composer");
this.owner.register("service:composer", new composerMockClass(this.owner), {
instantiate: false,
});
await render(<template><ComposerEditor /></template>);
await fillIn("textarea", "@user-no @user-ok @user-nope"); await fillIn("textarea", "@user-no @user-ok @user-nope");
}); });
test("preview sanitizes HTML", async function (assert) { test("preview sanitizes HTML", async function (assert) {
const model = {}; await render(<template><ComposerEditor /></template>);
const noop = () => {};
await render(<template>
<ComposerEditor @composer={{model}} @afterRefresh={{noop}} />
</template>);
await fillIn(".d-editor-input", `"><svg onload="prompt(/xss/)"></svg>`); await fillIn(".d-editor-input", `"><svg onload="prompt(/xss/)"></svg>`);
assert.dom(".d-editor-preview").hasHtml('<p>"&gt;<svg></svg></p>'); assert.dom(".d-editor-preview").hasHtml('<p>"&gt;<svg></svg></p>');

View File

@ -421,10 +421,6 @@ html.composer-open {
} }
} }
#file-uploader {
display: none;
}
.composer-select-form-template { .composer-select-form-template {
margin-bottom: 8px; margin-bottom: 8px;
width: 100%; width: 100%;