mirror of
https://github.com/discourse/discourse.git
synced 2024-11-25 09:42:07 +08:00
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:
parent
8fd2980685
commit
6e5d4ee492
|
@ -102,36 +102,7 @@
|
|||
/>
|
||||
</div>
|
||||
|
||||
<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}}
|
||||
>
|
||||
<ComposerEditor>
|
||||
<div class="composer-fields">
|
||||
<PluginOutlet
|
||||
@name="before-composer-fields"
|
||||
|
|
|
@ -1,38 +1,56 @@
|
|||
<DEditor
|
||||
@value={{this.composer.reply}}
|
||||
@placeholder={{this.replyPlaceholder}}
|
||||
@previewUpdated={{action "previewUpdated"}}
|
||||
@markdownOptions={{this.markdownOptions}}
|
||||
@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.showFormTemplateForm}}
|
||||
<div class="d-editor">
|
||||
<div class="d-editor-container">
|
||||
<div class="d-editor-textarea-column">
|
||||
{{yield}}
|
||||
|
||||
{{#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
|
||||
@fileInputId="file-uploader"
|
||||
@fileInputId={{this.fileUploadElementId}}
|
||||
@allowMultiple={{true}}
|
||||
name="file-uploader"
|
||||
/>
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,81 +1,63 @@
|
|||
<div class="d-editor-container">
|
||||
<div class="d-editor-textarea-column">
|
||||
{{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}} />
|
||||
<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
|
||||
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>
|
||||
{{/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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Component from "@ember/component";
|
||||
import { action, computed } from "@ember/object";
|
||||
import { action } from "@ember/object";
|
||||
import { getOwner } from "@ember/owner";
|
||||
import { schedule, scheduleOnce } from "@ember/runloop";
|
||||
import { service } from "@ember/service";
|
||||
|
@ -82,30 +82,6 @@ export default class DEditor extends Component {
|
|||
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")
|
||||
placeholderTranslated(placeholder) {
|
||||
if (placeholder) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(<template>
|
||||
<ComposerEditor
|
||||
@composer={{model}}
|
||||
@afterRefresh={{noop}}
|
||||
@cannotSeeMention={{expectation}}
|
||||
/>
|
||||
</template>);
|
||||
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(<template><ComposerEditor /></template>);
|
||||
|
||||
await fillIn("textarea", "@user-no @user-ok @user-nope");
|
||||
});
|
||||
|
||||
test("preview sanitizes HTML", async function (assert) {
|
||||
const model = {};
|
||||
const noop = () => {};
|
||||
|
||||
await render(<template>
|
||||
<ComposerEditor @composer={{model}} @afterRefresh={{noop}} />
|
||||
</template>);
|
||||
await render(<template><ComposerEditor /></template>);
|
||||
|
||||
await fillIn(".d-editor-input", `"><svg onload="prompt(/xss/)"></svg>`);
|
||||
assert.dom(".d-editor-preview").hasHtml('<p>"><svg></svg></p>');
|
||||
|
|
|
@ -421,10 +421,6 @@ html.composer-open {
|
|||
}
|
||||
}
|
||||
|
||||
#file-uploader {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.composer-select-form-template {
|
||||
margin-bottom: 8px;
|
||||
width: 100%;
|
||||
|
|
Loading…
Reference in New Issue
Block a user