Compare commits
4 Commits
main
...
composer-n
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0537e5086f | ||
|
|
e7764b5865 | ||
|
|
32815293e5 | ||
|
|
887af6d074 |
@ -101,6 +101,12 @@ const DEPRECATED_MODULES = new Map(
|
|||||||
dropFrom: "3.0.0",
|
dropFrom: "3.0.0",
|
||||||
silent: true,
|
silent: true,
|
||||||
},
|
},
|
||||||
|
"controller:composer": {
|
||||||
|
newName: "service:composer",
|
||||||
|
since: "3.1.0.beta3",
|
||||||
|
dropFrom: "3.2.0",
|
||||||
|
silent: true,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,422 @@
|
|||||||
|
<ComposerBody
|
||||||
|
@composer={{this.composer.model}}
|
||||||
|
@showPreview={{this.composer.showPreview}}
|
||||||
|
@openIfDraft={{this.composer.openIfDraft}}
|
||||||
|
@typed={{this.composer.typed}}
|
||||||
|
@cancelled={{this.composer.cancelled}}
|
||||||
|
@save={{this.composer.save}}
|
||||||
|
>
|
||||||
|
<div class="grippie"></div>
|
||||||
|
|
||||||
|
{{#if this.composer.visible}}
|
||||||
|
<ComposerMessages
|
||||||
|
@composer={{this.composer.model}}
|
||||||
|
@messageCount={{this.composer.messageCount}}
|
||||||
|
@addLinkLookup={{this.composer.addLinkLookup}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{{#if this.composer.showFullScreenPrompt}}
|
||||||
|
<ComposerFullscreenPrompt
|
||||||
|
@removeFullScreenExitPrompt={{this.composer.removeFullScreenExitPrompt}}
|
||||||
|
/>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if this.composer.model.viewOpenOrFullscreen}}
|
||||||
|
<div
|
||||||
|
role="form"
|
||||||
|
aria-label={{I18n this.composer.saveLabel}}
|
||||||
|
class="reply-area
|
||||||
|
{{if this.composer.canEditTags 'with-tags' 'without-tags'}}"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<PluginOutlet
|
||||||
|
@name="composer-open"
|
||||||
|
@connectorTagName="div"
|
||||||
|
@outletArgs={{hash model=this.composer.model}}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div class="reply-to">
|
||||||
|
{{#unless this.composer.model.viewFullscreen}}
|
||||||
|
<div class="reply-details">
|
||||||
|
<ComposerActionTitle
|
||||||
|
@model={{this.composer.model}}
|
||||||
|
@openComposer={{this.composer.openComposer}}
|
||||||
|
@closeComposer={{this.composer.closeComposer}}
|
||||||
|
@canWhisper={{this.composer.canWhisper}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PluginOutlet
|
||||||
|
@name="composer-action-after"
|
||||||
|
@outletArgs={{hash model=this.composer.model}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{{#unless this.composer.site.mobileView}}
|
||||||
|
{{#if this.composer.model.unlistTopic}}
|
||||||
|
<span class="unlist">({{i18n "composer.unlist"}})</span>
|
||||||
|
{{/if}}
|
||||||
|
{{#if this.composer.isWhispering}}
|
||||||
|
{{#if this.composer.model.noBump}}
|
||||||
|
<span class="no-bump">{{d-icon "anchor"}}</span>
|
||||||
|
{{/if}}
|
||||||
|
{{/if}}
|
||||||
|
{{/unless}}
|
||||||
|
|
||||||
|
{{#if this.composer.canEdit}}
|
||||||
|
<LinkToInput
|
||||||
|
@onClick={{this.composer.displayEditReason}}
|
||||||
|
@showInput={{this.composer.showEditReason}}
|
||||||
|
@icon="info-circle"
|
||||||
|
@class="display-edit-reason"
|
||||||
|
>
|
||||||
|
<TextField
|
||||||
|
@value={{this.composer.editReason}}
|
||||||
|
@id="edit-reason"
|
||||||
|
@maxlength="255"
|
||||||
|
@placeholderKey="composer.edit_reason_placeholder"
|
||||||
|
/>
|
||||||
|
</LinkToInput>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{/unless}}
|
||||||
|
|
||||||
|
<PluginOutlet
|
||||||
|
@name="before-composer-controls"
|
||||||
|
@outletArgs={{hash model=this.composer.model}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ComposerToggles
|
||||||
|
@composeState={{this.composer.model.composeState}}
|
||||||
|
@showToolbar={{this.composer.showToolbar}}
|
||||||
|
@toggleComposer={{this.composer.toggle}}
|
||||||
|
@toggleToolbar={{this.composer.toggleToolbar}}
|
||||||
|
@toggleFullscreen={{this.composer.fullscreenComposer}}
|
||||||
|
@disableTextarea={{this.composer.disableTextarea}}
|
||||||
|
/>
|
||||||
|
</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}}
|
||||||
|
>
|
||||||
|
<div class="composer-fields">
|
||||||
|
<PluginOutlet
|
||||||
|
@name="before-composer-fields"
|
||||||
|
@outletArgs={{hash model=this.composer.model}}
|
||||||
|
/>
|
||||||
|
{{#unless this.composer.model.viewFullscreen}}
|
||||||
|
{{#if this.composer.model.canEditTitle}}
|
||||||
|
{{#if this.composer.model.creatingPrivateMessage}}
|
||||||
|
<div class="user-selector">
|
||||||
|
<ComposerUserSelector
|
||||||
|
@topicId={{this.composer.topicModel.id}}
|
||||||
|
@recipients={{this.composer.model.targetRecipients}}
|
||||||
|
@hasGroups={{this.composer.model.hasTargetGroups}}
|
||||||
|
@focusTarget={{this.composer.focusTarget}}
|
||||||
|
@class={{concat
|
||||||
|
"users-input"
|
||||||
|
(if this.composer.showWarning " can-warn")
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{{#if this.composer.showWarning}}
|
||||||
|
<label class="add-warning">
|
||||||
|
<Input
|
||||||
|
@type="checkbox"
|
||||||
|
@checked={{this.composer.model.isWarning}}
|
||||||
|
/>
|
||||||
|
<span>{{i18n "composer.add_warning"}}</span>
|
||||||
|
</label>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="title-and-category
|
||||||
|
{{if this.composer.showPreview 'with-preview'}}"
|
||||||
|
>
|
||||||
|
<ComposerTitle
|
||||||
|
@composer={{this.composer.model}}
|
||||||
|
@lastValidatedAt={{this.composer.lastValidatedAt}}
|
||||||
|
@focusTarget={{this.composer.focusTarget}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{{#if this.composer.model.showCategoryChooser}}
|
||||||
|
<div class="category-input">
|
||||||
|
<CategoryChooser
|
||||||
|
@value={{this.composer.model.categoryId}}
|
||||||
|
@onChange={{action
|
||||||
|
(mut this.composer.model.categoryId)
|
||||||
|
}}
|
||||||
|
@options={{hash
|
||||||
|
disabled=this.composer.disableCategoryChooser
|
||||||
|
scopedCategoryId=this.composer.scopedCategoryId
|
||||||
|
prioritizedCategoryId=this.composer.prioritizedCategoryId
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<PopupInputTip
|
||||||
|
@validation={{this.composer.categoryValidation}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if this.composer.canEditTags}}
|
||||||
|
<MiniTagChooser
|
||||||
|
@value={{this.composer.model.tags}}
|
||||||
|
@onChange={{action (mut this.composer.model.tags)}}
|
||||||
|
@options={{hash
|
||||||
|
disabled=this.composer.disableTagsChooser
|
||||||
|
categoryId=this.composer.model.categoryId
|
||||||
|
minimum=this.composer.model.minimumRequiredTags
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<PopupInputTip
|
||||||
|
@validation={{this.composer.tagValidation}}
|
||||||
|
/>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<PluginOutlet
|
||||||
|
@name="after-title-and-category"
|
||||||
|
@outletArgs={{hash
|
||||||
|
model=this.composer.model
|
||||||
|
tagValidation=this.composer.tagValidation
|
||||||
|
canEditTags=this.composer.canEditTags
|
||||||
|
disabled=this.composer.disableTagsChooser
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<span>
|
||||||
|
<PluginOutlet
|
||||||
|
@name="composer-fields"
|
||||||
|
@connectorTagName="div"
|
||||||
|
@outletArgs={{hash
|
||||||
|
model=this.composer.model
|
||||||
|
showPreview=this.composer.showPreview
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
{{/unless}}
|
||||||
|
</div>
|
||||||
|
</ComposerEditor>
|
||||||
|
|
||||||
|
<span>
|
||||||
|
<PluginOutlet
|
||||||
|
@name="composer-after-composer-editor"
|
||||||
|
@outletArgs={{hash model=this.composer.model}}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div class="submit-panel">
|
||||||
|
<span>
|
||||||
|
<PluginOutlet
|
||||||
|
@name="composer-fields-below"
|
||||||
|
@connectorTagName="div"
|
||||||
|
@outletArgs={{hash model=this.composer.model}}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div class="save-or-cancel">
|
||||||
|
<ComposerSaveButton
|
||||||
|
@action={{this.composer.save}}
|
||||||
|
@icon={{this.composer.saveIcon}}
|
||||||
|
@label={{this.composer.saveLabel}}
|
||||||
|
@forwardEvent={{true}}
|
||||||
|
@disableSubmit={{this.composer.disableSubmit}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{{#if this.composer.site.mobileView}}
|
||||||
|
<a
|
||||||
|
href
|
||||||
|
{{on "click" this.composer.cancel}}
|
||||||
|
title={{i18n "cancel"}}
|
||||||
|
class="cancel"
|
||||||
|
>
|
||||||
|
{{#if this.composer.canEdit}}
|
||||||
|
{{d-icon "times"}}
|
||||||
|
{{else}}
|
||||||
|
{{d-icon "far-trash-alt"}}
|
||||||
|
{{/if}}
|
||||||
|
</a>
|
||||||
|
{{else}}
|
||||||
|
<a href {{on "click" this.composer.cancel}} class="cancel">{{i18n
|
||||||
|
"close"
|
||||||
|
}}</a>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if this.composer.site.mobileView}}
|
||||||
|
{{#if this.composer.whisperOrUnlistTopic}}
|
||||||
|
<span class="whisper">
|
||||||
|
{{d-icon "far-eye-slash"}}
|
||||||
|
</span>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if this.composer.model.noBump}}
|
||||||
|
<span class="no-bump">{{d-icon "anchor"}}</span>
|
||||||
|
{{/if}}
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if
|
||||||
|
(or this.composer.isUploading this.composer.isProcessingUpload)
|
||||||
|
}}
|
||||||
|
<div id="file-uploading">
|
||||||
|
{{#if this.composer.isProcessingUpload}}
|
||||||
|
{{loading-spinner size="small"}}<span>{{i18n
|
||||||
|
"upload_selector.processing"
|
||||||
|
}}</span>
|
||||||
|
{{else}}
|
||||||
|
{{loading-spinner size="small"}}<span>{{i18n
|
||||||
|
"upload_selector.uploading"
|
||||||
|
}}
|
||||||
|
{{this.composer.uploadProgress}}%</span>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if this.composer.isCancellable}}
|
||||||
|
<a
|
||||||
|
href
|
||||||
|
id="cancel-file-upload"
|
||||||
|
{{on "click" this.composer.cancelUpload}}
|
||||||
|
>{{d-icon "times"}}</a>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<div
|
||||||
|
class={{if this.composer.isUploading "hidden"}}
|
||||||
|
id="draft-status"
|
||||||
|
>
|
||||||
|
{{#if this.composer.model.draftStatus}}
|
||||||
|
<span
|
||||||
|
class="draft-error"
|
||||||
|
title={{this.composer.model.draftStatus}}
|
||||||
|
>
|
||||||
|
{{#if this.composer.model.draftConflictUser}}
|
||||||
|
{{avatar
|
||||||
|
this.composer.model.draftConflictUser
|
||||||
|
imageSize="small"
|
||||||
|
}}
|
||||||
|
{{d-icon "user-edit"}}
|
||||||
|
{{else}}
|
||||||
|
{{d-icon "exclamation-triangle"}}
|
||||||
|
{{/if}}
|
||||||
|
{{#unless this.composer.site.mobileView}}
|
||||||
|
{{this.composer.model.draftStatus}}
|
||||||
|
{{/unless}}
|
||||||
|
</span>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span>
|
||||||
|
<PluginOutlet
|
||||||
|
@name="composer-after-save-or-cancel"
|
||||||
|
@outletArgs={{hash model=this.composer.model}}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{#if this.composer.site.mobileView}}
|
||||||
|
<span>
|
||||||
|
<PluginOutlet
|
||||||
|
@name="composer-mobile-buttons-bottom"
|
||||||
|
@outletArgs={{hash model=this.composer.model}}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{{#if this.composer.allowUpload}}
|
||||||
|
<a
|
||||||
|
id="mobile-file-upload"
|
||||||
|
class="btn btn-default no-text mobile-file-upload
|
||||||
|
{{if this.composer.isUploading 'hidden'}}"
|
||||||
|
aria-label={{i18n "composer.upload_title"}}
|
||||||
|
>
|
||||||
|
{{d-icon this.composer.uploadIcon}}
|
||||||
|
</a>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<a
|
||||||
|
href
|
||||||
|
class="btn btn-default no-text mobile-preview"
|
||||||
|
title={{i18n "composer.show_preview"}}
|
||||||
|
{{on "click" this.composer.togglePreview}}
|
||||||
|
aria-label={{i18n "preview"}}
|
||||||
|
>
|
||||||
|
{{d-icon "desktop"}}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
{{#if this.composer.showPreview}}
|
||||||
|
<DButton
|
||||||
|
@action={{this.composer.togglePreview}}
|
||||||
|
@class="hide-preview"
|
||||||
|
@ariaLabel="composer.hide_preview"
|
||||||
|
@icon="pencil-alt"
|
||||||
|
/>
|
||||||
|
{{/if}}
|
||||||
|
{{else}}
|
||||||
|
<DButton
|
||||||
|
@action={{this.composer.togglePreview}}
|
||||||
|
@translatedTitle={{this.composer.toggleText}}
|
||||||
|
@icon="angle-double-left"
|
||||||
|
@class={{concat
|
||||||
|
"btn-flat btn-mini-toggle toggle-preview "
|
||||||
|
(unless this.composer.showPreview "active")
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<div class="saving-text">
|
||||||
|
{{#if this.composer.model.createdPost}}
|
||||||
|
{{i18n "composer.saved"}}
|
||||||
|
<a
|
||||||
|
href={{this.composer.createdPost.url}}
|
||||||
|
{{on "click" this.composer.viewNewReply}}
|
||||||
|
class="permalink"
|
||||||
|
>{{i18n "composer.view_new_post"}}</a>
|
||||||
|
{{else}}
|
||||||
|
{{i18n "composer.saving"}}
|
||||||
|
{{loading-spinner size="small"}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="draft-text">
|
||||||
|
{{#if this.composer.model.topic}}
|
||||||
|
{{d-icon "share"}}
|
||||||
|
{{html-safe this.composer.draftTitle}}
|
||||||
|
{{else}}
|
||||||
|
{{i18n "composer.saved_draft"}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ComposerToggles
|
||||||
|
@composeState={{this.composer.model.composeState}}
|
||||||
|
@toggleFullscreen={{this.composer.openIfDraft}}
|
||||||
|
@toggleComposer={{this.composer.toggle}}
|
||||||
|
@toggleToolbar={{this.composer.toggleToolbar}}
|
||||||
|
/>
|
||||||
|
{{/if}}
|
||||||
|
{{/if}}
|
||||||
|
</ComposerBody>
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
import Component from "@glimmer/component";
|
||||||
|
import { inject as service } from "@ember/service";
|
||||||
|
|
||||||
|
export default class ComposerContainer extends Component {
|
||||||
|
@service composer;
|
||||||
|
}
|
||||||
@ -28,7 +28,7 @@ export default Component.extend({
|
|||||||
|
|
||||||
dismiss() {
|
dismiss() {
|
||||||
this.set("shownAt", null);
|
this.set("shownAt", null);
|
||||||
const composer = getOwner(this).lookup("controller:composer");
|
const composer = getOwner(this).lookup("service:composer");
|
||||||
composer.clearLastValidatedAt();
|
composer.clearLastValidatedAt();
|
||||||
this.element.previousElementSibling?.focus();
|
this.element.previousElementSibling?.focus();
|
||||||
},
|
},
|
||||||
|
|||||||
@ -68,7 +68,7 @@ export default class SidebarUserCommunitySection extends SidebarCommonCommunityS
|
|||||||
}
|
}
|
||||||
|
|
||||||
next(() => {
|
next(() => {
|
||||||
getOwner(this).lookup("controller:composer").open(composerArgs);
|
getOwner(this).lookup("service:composer").open(composerArgs);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -100,7 +100,7 @@ export default Component.extend(LoadMore, {
|
|||||||
},
|
},
|
||||||
|
|
||||||
resumeDraft(item) {
|
resumeDraft(item) {
|
||||||
const composer = getOwner(this).lookup("controller:composer");
|
const composer = getOwner(this).lookup("service:composer");
|
||||||
if (composer.get("model.viewOpen")) {
|
if (composer.get("model.viewOpen")) {
|
||||||
composer.close();
|
composer.close();
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -22,6 +22,7 @@ import { Promise } from "rsvp";
|
|||||||
import { search as searchCategoryTag } from "discourse/lib/category-tag-search";
|
import { search as searchCategoryTag } from "discourse/lib/category-tag-search";
|
||||||
import showModal from "discourse/lib/show-modal";
|
import showModal from "discourse/lib/show-modal";
|
||||||
import userSearch from "discourse/lib/user-search";
|
import userSearch from "discourse/lib/user-search";
|
||||||
|
import { inject as service } from "@ember/service";
|
||||||
|
|
||||||
const SortOrders = [
|
const SortOrders = [
|
||||||
{ name: I18n.t("search.relevance"), id: 0 },
|
{ name: I18n.t("search.relevance"), id: 0 },
|
||||||
@ -39,7 +40,7 @@ const PAGE_LIMIT = 10;
|
|||||||
|
|
||||||
export default Controller.extend({
|
export default Controller.extend({
|
||||||
application: controller(),
|
application: controller(),
|
||||||
composer: controller(),
|
composer: service(),
|
||||||
bulkSelectEnabled: null,
|
bulkSelectEnabled: null,
|
||||||
|
|
||||||
loading: false,
|
loading: false,
|
||||||
|
|||||||
@ -47,7 +47,7 @@ export function registerCustomPostMessageCallback(type, callback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default Controller.extend(bufferedProperty("model"), {
|
export default Controller.extend(bufferedProperty("model"), {
|
||||||
composer: controller(),
|
composer: service(),
|
||||||
application: controller(),
|
application: controller(),
|
||||||
dialog: service(),
|
dialog: service(),
|
||||||
documentTitle: service(),
|
documentTitle: service(),
|
||||||
|
|||||||
@ -445,14 +445,14 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.container.lookup("controller:composer").open({
|
this.container.lookup("service:composer").open({
|
||||||
action: Composer.CREATE_TOPIC,
|
action: Composer.CREATE_TOPIC,
|
||||||
draftKey: Composer.NEW_TOPIC_KEY,
|
draftKey: Composer.NEW_TOPIC_KEY,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
focusComposer(event) {
|
focusComposer(event) {
|
||||||
const composer = this.container.lookup("controller:composer");
|
const composer = this.container.lookup("service:composer");
|
||||||
if (event) {
|
if (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
@ -461,7 +461,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
fullscreenComposer() {
|
fullscreenComposer() {
|
||||||
const composer = this.container.lookup("controller:composer");
|
const composer = this.container.lookup("service:composer");
|
||||||
if (composer.get("model")) {
|
if (composer.get("model")) {
|
||||||
composer.toggleFullscreen();
|
composer.toggleFullscreen();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -58,7 +58,7 @@ import { addPluginReviewableParam } from "discourse/components/reviewable-item";
|
|||||||
import {
|
import {
|
||||||
addComposerSaveErrorCallback,
|
addComposerSaveErrorCallback,
|
||||||
addPopupMenuOptionsCallback,
|
addPopupMenuOptionsCallback,
|
||||||
} from "discourse/controllers/composer";
|
} from "discourse/services/composer";
|
||||||
import { addPostClassesCallback } from "discourse/widgets/post";
|
import { addPostClassesCallback } from "discourse/widgets/post";
|
||||||
import {
|
import {
|
||||||
addGroupPostSmallActionCode,
|
addGroupPostSmallActionCode,
|
||||||
|
|||||||
@ -410,7 +410,7 @@ const DiscourseURL = EmberObject.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
get isComposerOpen() {
|
get isComposerOpen() {
|
||||||
return this.controllerFor("composer")?.visible;
|
return this.container.lookup("service:composer")?.visible;
|
||||||
},
|
},
|
||||||
|
|
||||||
get router() {
|
get router() {
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
// This mixin allows a route to open the composer
|
// This mixin allows a route to open the composer
|
||||||
import Composer from "discourse/models/composer";
|
import Composer from "discourse/models/composer";
|
||||||
import Mixin from "@ember/object/mixin";
|
import Mixin from "@ember/object/mixin";
|
||||||
|
import { getOwner } from "discourse-common/lib/get-owner";
|
||||||
|
|
||||||
export default Mixin.create({
|
export default Mixin.create({
|
||||||
openComposer(controller) {
|
openComposer(controller) {
|
||||||
@ -13,13 +14,15 @@ export default Mixin.create({
|
|||||||
categoryId = null;
|
categoryId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.controllerFor("composer").open({
|
getOwner(this)
|
||||||
prioritizedCategoryId: categoryId,
|
.lookup("service:composer")
|
||||||
topicCategoryId: categoryId,
|
.open({
|
||||||
action: Composer.CREATE_TOPIC,
|
prioritizedCategoryId: categoryId,
|
||||||
draftKey: controller.get("model.draft_key") || Composer.NEW_TOPIC_KEY,
|
topicCategoryId: categoryId,
|
||||||
draftSequence: controller.get("model.draft_sequence") || 0,
|
action: Composer.CREATE_TOPIC,
|
||||||
});
|
draftKey: controller.get("model.draft_key") || Composer.NEW_TOPIC_KEY,
|
||||||
|
draftSequence: controller.get("model.draft_sequence") || 0,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
openComposerWithTopicParams(
|
openComposerWithTopicParams(
|
||||||
@ -29,15 +32,17 @@ export default Mixin.create({
|
|||||||
topicCategoryId,
|
topicCategoryId,
|
||||||
topicTags
|
topicTags
|
||||||
) {
|
) {
|
||||||
this.controllerFor("composer").open({
|
getOwner(this)
|
||||||
action: Composer.CREATE_TOPIC,
|
.lookup("service:composer")
|
||||||
topicTitle,
|
.open({
|
||||||
topicBody,
|
action: Composer.CREATE_TOPIC,
|
||||||
topicCategoryId,
|
topicTitle,
|
||||||
topicTags,
|
topicBody,
|
||||||
draftKey: controller.get("model.draft_key") || Composer.NEW_TOPIC_KEY,
|
topicCategoryId,
|
||||||
draftSequence: controller.get("model.draft_sequence"),
|
topicTags,
|
||||||
});
|
draftKey: controller.get("model.draft_key") || Composer.NEW_TOPIC_KEY,
|
||||||
|
draftSequence: controller.get("model.draft_sequence"),
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
openComposerWithMessageParams({
|
openComposerWithMessageParams({
|
||||||
@ -46,7 +51,7 @@ export default Mixin.create({
|
|||||||
topicBody = "",
|
topicBody = "",
|
||||||
hasGroups = false,
|
hasGroups = false,
|
||||||
} = {}) {
|
} = {}) {
|
||||||
this.controllerFor("composer").open({
|
getOwner(this).lookup("service:composer").open({
|
||||||
action: Composer.PRIVATE_MESSAGE,
|
action: Composer.PRIVATE_MESSAGE,
|
||||||
recipients,
|
recipients,
|
||||||
topicTitle,
|
topicTitle,
|
||||||
|
|||||||
@ -39,6 +39,7 @@ const ApplicationRoute = DiscourseRoute.extend(OpenComposer, {
|
|||||||
shortSiteDescription: setting("short_site_description"),
|
shortSiteDescription: setting("short_site_description"),
|
||||||
documentTitle: service(),
|
documentTitle: service(),
|
||||||
dialog: service(),
|
dialog: service(),
|
||||||
|
composer: service(),
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
toggleAnonymous() {
|
toggleAnonymous() {
|
||||||
@ -72,13 +73,6 @@ const ApplicationRoute = DiscourseRoute.extend(OpenComposer, {
|
|||||||
this.documentTitle.setTitle(tokens.join(" - "));
|
this.documentTitle.setTitle(tokens.join(" - "));
|
||||||
},
|
},
|
||||||
|
|
||||||
postWasEnqueued(details) {
|
|
||||||
showModal("post-enqueued", {
|
|
||||||
model: details,
|
|
||||||
title: "review.approval.title",
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
composePrivateMessage(user, post) {
|
composePrivateMessage(user, post) {
|
||||||
const recipients = user ? user.get("username") : "";
|
const recipients = user ? user.get("username") : "";
|
||||||
const reply = post
|
const reply = post
|
||||||
@ -91,7 +85,7 @@ const ApplicationRoute = DiscourseRoute.extend(OpenComposer, {
|
|||||||
: null;
|
: null;
|
||||||
|
|
||||||
// used only once, one less dependency
|
// used only once, one less dependency
|
||||||
return this.controllerFor("composer").open({
|
return this.composer.open({
|
||||||
action: Composer.PRIVATE_MESSAGE,
|
action: Composer.PRIVATE_MESSAGE,
|
||||||
recipients,
|
recipients,
|
||||||
archetypeId: "private_message",
|
archetypeId: "private_message",
|
||||||
@ -258,7 +252,6 @@ const ApplicationRoute = DiscourseRoute.extend(OpenComposer, {
|
|||||||
this.render("application");
|
this.render("application");
|
||||||
this.render("user-card", { into: "application", outlet: "user-card" });
|
this.render("user-card", { into: "application", outlet: "user-card" });
|
||||||
this.render("modal", { into: "application", outlet: "modal" });
|
this.render("modal", { into: "application", outlet: "modal" });
|
||||||
this.render("composer", { into: "application", outlet: "composer" });
|
|
||||||
},
|
},
|
||||||
|
|
||||||
handleShowLogin() {
|
handleShowLogin() {
|
||||||
|
|||||||
@ -16,11 +16,13 @@ import PermissionType from "discourse/models/permission-type";
|
|||||||
import TopicList from "discourse/models/topic-list";
|
import TopicList from "discourse/models/topic-list";
|
||||||
import { action } from "@ember/object";
|
import { action } from "@ember/object";
|
||||||
import PreloadStore from "discourse/lib/preload-store";
|
import PreloadStore from "discourse/lib/preload-store";
|
||||||
|
import { inject as service } from "@ember/service";
|
||||||
|
|
||||||
// A helper function to create a category route with parameters
|
// A helper function to create a category route with parameters
|
||||||
export default (filterArg, params) => {
|
export default (filterArg, params) => {
|
||||||
return DiscourseRoute.extend({
|
return DiscourseRoute.extend({
|
||||||
queryParams,
|
queryParams,
|
||||||
|
composer: service(),
|
||||||
|
|
||||||
model(modelParams) {
|
model(modelParams) {
|
||||||
const category = Category.findBySlugPathWithID(
|
const category = Category.findBySlugPathWithID(
|
||||||
@ -209,7 +211,7 @@ export default (filterArg, params) => {
|
|||||||
deactivate() {
|
deactivate() {
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
|
|
||||||
this.controllerFor("composer").set("prioritizedCategoryId", null);
|
this.composer.set("prioritizedCategoryId", null);
|
||||||
this.searchService.set("searchContext", null);
|
this.searchService.set("searchContext", null);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import Draft from "discourse/models/draft";
|
|||||||
import Route from "@ember/routing/route";
|
import Route from "@ember/routing/route";
|
||||||
import { once } from "@ember/runloop";
|
import { once } from "@ember/runloop";
|
||||||
import { seenUser } from "discourse/lib/user-presence";
|
import { seenUser } from "discourse/lib/user-presence";
|
||||||
|
import { getOwner } from "discourse-common/lib/get-owner";
|
||||||
|
|
||||||
const DiscourseRoute = Route.extend({
|
const DiscourseRoute = Route.extend({
|
||||||
showFooter: false,
|
showFooter: false,
|
||||||
@ -53,7 +54,7 @@ const DiscourseRoute = Route.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
openTopicDraft() {
|
openTopicDraft() {
|
||||||
const composer = this.controllerFor("composer");
|
const composer = getOwner(this).lookup("service:composer");
|
||||||
|
|
||||||
if (
|
if (
|
||||||
composer.get("model.action") === Composer.CREATE_TOPIC &&
|
composer.get("model.action") === Composer.CREATE_TOPIC &&
|
||||||
|
|||||||
@ -18,11 +18,13 @@ import { setTopicList } from "discourse/lib/topic-list-tracker";
|
|||||||
import showModal from "discourse/lib/show-modal";
|
import showModal from "discourse/lib/show-modal";
|
||||||
import { action } from "@ember/object";
|
import { action } from "@ember/object";
|
||||||
import PreloadStore from "discourse/lib/preload-store";
|
import PreloadStore from "discourse/lib/preload-store";
|
||||||
|
import { inject as service } from "@ember/service";
|
||||||
|
|
||||||
const NONE = "none";
|
const NONE = "none";
|
||||||
const ALL = "all";
|
const ALL = "all";
|
||||||
|
|
||||||
export default DiscourseRoute.extend(FilterModeMixin, {
|
export default DiscourseRoute.extend(FilterModeMixin, {
|
||||||
|
composer: service(),
|
||||||
navMode: "latest",
|
navMode: "latest",
|
||||||
|
|
||||||
queryParams,
|
queryParams,
|
||||||
@ -214,8 +216,7 @@ export default DiscourseRoute.extend(FilterModeMixin, {
|
|||||||
this.openTopicDraft();
|
this.openTopicDraft();
|
||||||
} else {
|
} else {
|
||||||
const controller = this.controllerFor("tag.show");
|
const controller = this.controllerFor("tag.show");
|
||||||
const composerController = this.controllerFor("composer");
|
this.composer
|
||||||
composerController
|
|
||||||
.open({
|
.open({
|
||||||
categoryId: controller.category?.id,
|
categoryId: controller.category?.id,
|
||||||
action: Composer.CREATE_TOPIC,
|
action: Composer.CREATE_TOPIC,
|
||||||
@ -223,8 +224,8 @@ export default DiscourseRoute.extend(FilterModeMixin, {
|
|||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// Pre-fill the tags input field
|
// Pre-fill the tags input field
|
||||||
if (composerController.canEditTags && controller.tag?.id) {
|
if (this.composer.canEditTags && controller.tag?.id) {
|
||||||
const composerModel = this.controllerFor("composer").model;
|
const composerModel = this.composer.model;
|
||||||
composerModel.set("tags", this._controllerTags(controller));
|
composerModel.set("tags", this._controllerTags(controller));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -5,9 +5,12 @@ import { isEmpty } from "@ember/utils";
|
|||||||
import { isTesting } from "discourse-common/config/environment";
|
import { isTesting } from "discourse-common/config/environment";
|
||||||
import { schedule } from "@ember/runloop";
|
import { schedule } from "@ember/runloop";
|
||||||
import { action } from "@ember/object";
|
import { action } from "@ember/object";
|
||||||
|
import { inject as service } from "@ember/service";
|
||||||
|
|
||||||
// This route is used for retrieving a topic based on params
|
// This route is used for retrieving a topic based on params
|
||||||
export default DiscourseRoute.extend({
|
export default DiscourseRoute.extend({
|
||||||
|
composer: service(),
|
||||||
|
|
||||||
// Avoid default model hook
|
// Avoid default model hook
|
||||||
model(params) {
|
model(params) {
|
||||||
params = params || {};
|
params = params || {};
|
||||||
@ -56,7 +59,6 @@ export default DiscourseRoute.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const topicController = this.controllerFor("topic");
|
const topicController = this.controllerFor("topic");
|
||||||
const composerController = this.controllerFor("composer");
|
|
||||||
const topic = this.modelFor("topic");
|
const topic = this.modelFor("topic");
|
||||||
const postStream = topic.postStream;
|
const postStream = topic.postStream;
|
||||||
|
|
||||||
@ -105,7 +107,7 @@ export default DiscourseRoute.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!isEmpty(topic.draft)) {
|
if (!isEmpty(topic.draft)) {
|
||||||
composerController.open({
|
this.composer.open({
|
||||||
draft: Draft.getLocal(topic.draft_key, topic.draft),
|
draft: Draft.getLocal(topic.draft_key, topic.draft),
|
||||||
draftKey: topic.draft_key,
|
draftKey: topic.draft_key,
|
||||||
draftSequence: topic.draft_sequence,
|
draftSequence: topic.draft_sequence,
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import PostFlag from "discourse/lib/flag-targets/post-flag";
|
|||||||
const SCROLL_DELAY = 500;
|
const SCROLL_DELAY = 500;
|
||||||
|
|
||||||
const TopicRoute = DiscourseRoute.extend({
|
const TopicRoute = DiscourseRoute.extend({
|
||||||
|
composer: service(),
|
||||||
screenTrack: service(),
|
screenTrack: service(),
|
||||||
|
|
||||||
scheduledReplace: null,
|
scheduledReplace: null,
|
||||||
@ -333,7 +334,7 @@ const TopicRoute = DiscourseRoute.extend({
|
|||||||
postStream.cancelFilter();
|
postStream.cancelFilter();
|
||||||
|
|
||||||
topicController.set("multiSelect", false);
|
topicController.set("multiSelect", false);
|
||||||
this.controllerFor("composer").set("topic", null);
|
this.composer.set("topic", null);
|
||||||
this.screenTrack.stop();
|
this.screenTrack.stop();
|
||||||
|
|
||||||
this.appEvents.trigger("header:hide-topic");
|
this.appEvents.trigger("header:hide-topic");
|
||||||
@ -357,7 +358,7 @@ const TopicRoute = DiscourseRoute.extend({
|
|||||||
controller.set("multiSelect", false);
|
controller.set("multiSelect", false);
|
||||||
controller.get("quoteState").clear();
|
controller.get("quoteState").clear();
|
||||||
|
|
||||||
this.controllerFor("composer").set("topic", model);
|
this.composer.set("topic", model);
|
||||||
this.topicTrackingState.trackIncoming("all");
|
this.topicTrackingState.trackIncoming("all");
|
||||||
|
|
||||||
// We reset screen tracking every time a topic is entered
|
// We reset screen tracking every time a topic is entered
|
||||||
|
|||||||
@ -2,8 +2,11 @@ import Composer from "discourse/models/composer";
|
|||||||
import DiscourseRoute from "discourse/routes/discourse";
|
import DiscourseRoute from "discourse/routes/discourse";
|
||||||
import Draft from "discourse/models/draft";
|
import Draft from "discourse/models/draft";
|
||||||
import { action } from "@ember/object";
|
import { action } from "@ember/object";
|
||||||
|
import { inject as service } from "@ember/service";
|
||||||
|
|
||||||
export default DiscourseRoute.extend({
|
export default DiscourseRoute.extend({
|
||||||
|
composer: service(),
|
||||||
|
|
||||||
renderTemplate() {
|
renderTemplate() {
|
||||||
this.render("user/messages");
|
this.render("user/messages");
|
||||||
},
|
},
|
||||||
@ -20,11 +23,9 @@ export default DiscourseRoute.extend({
|
|||||||
controller.set("model", model);
|
controller.set("model", model);
|
||||||
|
|
||||||
if (this.currentUser) {
|
if (this.currentUser) {
|
||||||
const composerController = this.controllerFor("composer");
|
|
||||||
|
|
||||||
Draft.get("new_private_message").then((data) => {
|
Draft.get("new_private_message").then((data) => {
|
||||||
if (data.draft) {
|
if (data.draft) {
|
||||||
composerController.open({
|
this.composer.open({
|
||||||
draft: data.draft,
|
draft: data.draft,
|
||||||
draftKey: Composer.NEW_PRIVATE_MESSAGE_KEY,
|
draftKey: Composer.NEW_PRIVATE_MESSAGE_KEY,
|
||||||
ignoreIfChanged: true,
|
ignoreIfChanged: true,
|
||||||
|
|||||||
1648
app/assets/javascripts/discourse/app/services/composer.js
Normal file
1648
app/assets/javascripts/discourse/app/services/composer.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -86,7 +86,7 @@
|
|||||||
{{outlet "modal"}}
|
{{outlet "modal"}}
|
||||||
<DialogHolder />
|
<DialogHolder />
|
||||||
<TopicEntrance />
|
<TopicEntrance />
|
||||||
{{outlet "composer"}}
|
<ComposerContainer />
|
||||||
|
|
||||||
{{#if this.showFooterNav}}
|
{{#if this.showFooterNav}}
|
||||||
<FooterNav />
|
<FooterNav />
|
||||||
|
|||||||
@ -1,404 +0,0 @@
|
|||||||
<ComposerBody
|
|
||||||
@composer={{this.model}}
|
|
||||||
@showPreview={{this.showPreview}}
|
|
||||||
@openIfDraft={{action "openIfDraft"}}
|
|
||||||
@typed={{action "typed"}}
|
|
||||||
@cancelled={{action "cancelled"}}
|
|
||||||
@save={{action "save"}}
|
|
||||||
>
|
|
||||||
<div class="grippie"></div>
|
|
||||||
|
|
||||||
{{#if this.visible}}
|
|
||||||
<ComposerMessages
|
|
||||||
@composer={{this.model}}
|
|
||||||
@messageCount={{this.messageCount}}
|
|
||||||
@addLinkLookup={{action "addLinkLookup"}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{{#if this.showFullScreenPrompt}}
|
|
||||||
<ComposerFullscreenPrompt
|
|
||||||
@removeFullScreenExitPrompt={{action "removeFullScreenExitPrompt"}}
|
|
||||||
/>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if this.model.viewOpenOrFullscreen}}
|
|
||||||
<div
|
|
||||||
role="form"
|
|
||||||
aria-label={{I18n this.saveLabel}}
|
|
||||||
class="reply-area {{if this.canEditTags 'with-tags' 'without-tags'}}"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
<PluginOutlet
|
|
||||||
@name="composer-open"
|
|
||||||
@connectorTagName="div"
|
|
||||||
@outletArgs={{hash model=this.model}}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<div class="reply-to">
|
|
||||||
{{#unless this.model.viewFullscreen}}
|
|
||||||
<div class="reply-details">
|
|
||||||
<ComposerActionTitle
|
|
||||||
@model={{this.model}}
|
|
||||||
@openComposer={{action "openComposer"}}
|
|
||||||
@closeComposer={{action "closeComposer"}}
|
|
||||||
@canWhisper={{this.canWhisper}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<PluginOutlet
|
|
||||||
@name="composer-action-after"
|
|
||||||
@outletArgs={{hash model=this.model}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{{#unless this.site.mobileView}}
|
|
||||||
{{#if this.model.unlistTopic}}
|
|
||||||
<span class="unlist">({{i18n "composer.unlist"}})</span>
|
|
||||||
{{/if}}
|
|
||||||
{{#if this.isWhispering}}
|
|
||||||
{{#if this.model.noBump}}
|
|
||||||
<span class="no-bump">{{d-icon "anchor"}}</span>
|
|
||||||
{{/if}}
|
|
||||||
{{/if}}
|
|
||||||
{{/unless}}
|
|
||||||
|
|
||||||
{{#if this.canEdit}}
|
|
||||||
<LinkToInput
|
|
||||||
@onClick={{action "displayEditReason"}}
|
|
||||||
@showInput={{this.showEditReason}}
|
|
||||||
@icon="info-circle"
|
|
||||||
@class="display-edit-reason"
|
|
||||||
>
|
|
||||||
<TextField
|
|
||||||
@value={{this.editReason}}
|
|
||||||
@id="edit-reason"
|
|
||||||
@maxlength="255"
|
|
||||||
@placeholderKey="composer.edit_reason_placeholder"
|
|
||||||
/>
|
|
||||||
</LinkToInput>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
{{/unless}}
|
|
||||||
|
|
||||||
<PluginOutlet
|
|
||||||
@name="before-composer-controls"
|
|
||||||
@outletArgs={{hash model=this.model}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ComposerToggles
|
|
||||||
@composeState={{this.model.composeState}}
|
|
||||||
@showToolbar={{this.showToolbar}}
|
|
||||||
@toggleComposer={{action "toggle"}}
|
|
||||||
@toggleToolbar={{action "toggleToolbar"}}
|
|
||||||
@toggleFullscreen={{action "fullscreenComposer"}}
|
|
||||||
@disableTextarea={{this.disableTextarea}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ComposerEditor
|
|
||||||
@topic={{this.topic}}
|
|
||||||
@composer={{this.model}}
|
|
||||||
@lastValidatedAt={{this.lastValidatedAt}}
|
|
||||||
@canWhisper={{this.canWhisper}}
|
|
||||||
@storeToolbarState={{action "storeToolbarState"}}
|
|
||||||
@onPopupMenuAction={{action "onPopupMenuAction"}}
|
|
||||||
@showUploadModal={{route-action "showUploadSelector"}}
|
|
||||||
@popupMenuOptions={{this.popupMenuOptions}}
|
|
||||||
@draftStatus={{this.model.draftStatus}}
|
|
||||||
@isUploading={{this.isUploading}}
|
|
||||||
@isProcessingUpload={{this.isProcessingUpload}}
|
|
||||||
@allowUpload={{this.allowUpload}}
|
|
||||||
@uploadIcon={{this.uploadIcon}}
|
|
||||||
@isCancellable={{this.isCancellable}}
|
|
||||||
@uploadProgress={{this.uploadProgress}}
|
|
||||||
@groupsMentioned={{action "groupsMentioned"}}
|
|
||||||
@cannotSeeMention={{action "cannotSeeMention"}}
|
|
||||||
@hereMention={{action "hereMention"}}
|
|
||||||
@importQuote={{action "importQuote"}}
|
|
||||||
@togglePreview={{action "togglePreview"}}
|
|
||||||
@processPreview={{this.showPreview}}
|
|
||||||
@showToolbar={{this.showToolbar}}
|
|
||||||
@afterRefresh={{action "afterRefresh"}}
|
|
||||||
@focusTarget={{this.focusTarget}}
|
|
||||||
@disableTextarea={{this.disableTextarea}}
|
|
||||||
>
|
|
||||||
<div class="composer-fields">
|
|
||||||
<PluginOutlet
|
|
||||||
@name="before-composer-fields"
|
|
||||||
@outletArgs={{hash model=this.model}}
|
|
||||||
/>
|
|
||||||
{{#unless this.model.viewFullscreen}}
|
|
||||||
{{#if this.model.canEditTitle}}
|
|
||||||
{{#if this.model.creatingPrivateMessage}}
|
|
||||||
<div class="user-selector">
|
|
||||||
<ComposerUserSelector
|
|
||||||
@topicId={{this.topicModel.id}}
|
|
||||||
@recipients={{this.model.targetRecipients}}
|
|
||||||
@hasGroups={{this.model.hasTargetGroups}}
|
|
||||||
@focusTarget={{this.focusTarget}}
|
|
||||||
@class={{concat
|
|
||||||
"users-input"
|
|
||||||
(if this.showWarning " can-warn")
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{{#if this.showWarning}}
|
|
||||||
<label class="add-warning">
|
|
||||||
<Input
|
|
||||||
@type="checkbox"
|
|
||||||
@checked={{this.model.isWarning}}
|
|
||||||
/>
|
|
||||||
<span>{{i18n "composer.add_warning"}}</span>
|
|
||||||
</label>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="title-and-category
|
|
||||||
{{if this.showPreview 'with-preview'}}"
|
|
||||||
>
|
|
||||||
<ComposerTitle
|
|
||||||
@composer={{this.model}}
|
|
||||||
@lastValidatedAt={{this.lastValidatedAt}}
|
|
||||||
@focusTarget={{this.focusTarget}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{{#if this.model.showCategoryChooser}}
|
|
||||||
<div class="category-input">
|
|
||||||
<CategoryChooser
|
|
||||||
@value={{this.model.categoryId}}
|
|
||||||
@onChange={{action (mut this.model.categoryId)}}
|
|
||||||
@options={{hash
|
|
||||||
disabled=this.disableCategoryChooser
|
|
||||||
scopedCategoryId=this.scopedCategoryId
|
|
||||||
prioritizedCategoryId=this.prioritizedCategoryId
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<PopupInputTip @validation={{this.categoryValidation}} />
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if this.canEditTags}}
|
|
||||||
<MiniTagChooser
|
|
||||||
@value={{this.model.tags}}
|
|
||||||
@onChange={{action (mut this.model.tags)}}
|
|
||||||
@options={{hash
|
|
||||||
disabled=this.disableTagsChooser
|
|
||||||
categoryId=this.model.categoryId
|
|
||||||
minimum=this.model.minimumRequiredTags
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<PopupInputTip @validation={{this.tagValidation}} />
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
<PluginOutlet
|
|
||||||
@name="after-title-and-category"
|
|
||||||
@outletArgs={{hash
|
|
||||||
model=this.model
|
|
||||||
tagValidation=this.tagValidation
|
|
||||||
canEditTags=this.canEditTags
|
|
||||||
disabled=this.disableTagsChooser
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
<span>
|
|
||||||
<PluginOutlet
|
|
||||||
@name="composer-fields"
|
|
||||||
@connectorTagName="div"
|
|
||||||
@outletArgs={{hash
|
|
||||||
model=this.model
|
|
||||||
showPreview=this.showPreview
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
{{/unless}}
|
|
||||||
</div>
|
|
||||||
</ComposerEditor>
|
|
||||||
|
|
||||||
<span>
|
|
||||||
<PluginOutlet
|
|
||||||
@name="composer-after-composer-editor"
|
|
||||||
@outletArgs={{hash model=this.model}}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<div class="submit-panel">
|
|
||||||
<span>
|
|
||||||
<PluginOutlet
|
|
||||||
@name="composer-fields-below"
|
|
||||||
@connectorTagName="div"
|
|
||||||
@outletArgs={{hash model=this.model}}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<div class="save-or-cancel">
|
|
||||||
<ComposerSaveButton
|
|
||||||
@action={{action "save"}}
|
|
||||||
@icon={{this.saveIcon}}
|
|
||||||
@label={{this.saveLabel}}
|
|
||||||
@forwardEvent={{true}}
|
|
||||||
@disableSubmit={{this.disableSubmit}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{{#if this.site.mobileView}}
|
|
||||||
<a
|
|
||||||
href
|
|
||||||
{{on "click" this.cancel}}
|
|
||||||
title={{i18n "cancel"}}
|
|
||||||
class="cancel"
|
|
||||||
>
|
|
||||||
{{#if this.canEdit}}
|
|
||||||
{{d-icon "times"}}
|
|
||||||
{{else}}
|
|
||||||
{{d-icon "far-trash-alt"}}
|
|
||||||
{{/if}}
|
|
||||||
</a>
|
|
||||||
{{else}}
|
|
||||||
<a href {{on "click" this.cancel}} class="cancel">{{i18n
|
|
||||||
"close"
|
|
||||||
}}</a>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if this.site.mobileView}}
|
|
||||||
{{#if this.whisperOrUnlistTopic}}
|
|
||||||
<span class="whisper">
|
|
||||||
{{d-icon "far-eye-slash"}}
|
|
||||||
</span>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if this.model.noBump}}
|
|
||||||
<span class="no-bump">{{d-icon "anchor"}}</span>
|
|
||||||
{{/if}}
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if (or this.isUploading this.isProcessingUpload)}}
|
|
||||||
<div id="file-uploading">
|
|
||||||
{{#if this.isProcessingUpload}}
|
|
||||||
{{loading-spinner size="small"}}<span>{{i18n
|
|
||||||
"upload_selector.processing"
|
|
||||||
}}</span>
|
|
||||||
{{else}}
|
|
||||||
{{loading-spinner size="small"}}<span>{{i18n
|
|
||||||
"upload_selector.uploading"
|
|
||||||
}}
|
|
||||||
{{this.uploadProgress}}%</span>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if this.isCancellable}}
|
|
||||||
<a
|
|
||||||
href
|
|
||||||
id="cancel-file-upload"
|
|
||||||
{{on "click" this.cancelUpload}}
|
|
||||||
>{{d-icon "times"}}</a>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
<div class={{if this.isUploading "hidden"}} id="draft-status">
|
|
||||||
{{#if this.model.draftStatus}}
|
|
||||||
<span class="draft-error" title={{this.model.draftStatus}}>
|
|
||||||
{{#if this.model.draftConflictUser}}
|
|
||||||
{{avatar this.model.draftConflictUser imageSize="small"}}
|
|
||||||
{{d-icon "user-edit"}}
|
|
||||||
{{else}}
|
|
||||||
{{d-icon "exclamation-triangle"}}
|
|
||||||
{{/if}}
|
|
||||||
{{#unless this.site.mobileView}}
|
|
||||||
{{this.model.draftStatus}}
|
|
||||||
{{/unless}}
|
|
||||||
</span>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<span>
|
|
||||||
<PluginOutlet
|
|
||||||
@name="composer-after-save-or-cancel"
|
|
||||||
@outletArgs={{hash model=this.model}}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{#if this.site.mobileView}}
|
|
||||||
<span>
|
|
||||||
<PluginOutlet
|
|
||||||
@name="composer-mobile-buttons-bottom"
|
|
||||||
@outletArgs={{hash model=this.model}}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
{{#if this.allowUpload}}
|
|
||||||
<a
|
|
||||||
id="mobile-file-upload"
|
|
||||||
class="btn btn-default no-text mobile-file-upload
|
|
||||||
{{if this.isUploading 'hidden'}}"
|
|
||||||
aria-label={{i18n "composer.upload_title"}}
|
|
||||||
>
|
|
||||||
{{d-icon this.uploadIcon}}
|
|
||||||
</a>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
<a
|
|
||||||
href
|
|
||||||
class="btn btn-default no-text mobile-preview"
|
|
||||||
title={{i18n "composer.show_preview"}}
|
|
||||||
{{on "click" this.togglePreview}}
|
|
||||||
aria-label={{i18n "preview"}}
|
|
||||||
>
|
|
||||||
{{d-icon "desktop"}}
|
|
||||||
</a>
|
|
||||||
|
|
||||||
{{#if this.showPreview}}
|
|
||||||
<DButton
|
|
||||||
@action={{action "togglePreview"}}
|
|
||||||
@class="hide-preview"
|
|
||||||
@ariaLabel="composer.hide_preview"
|
|
||||||
@icon="pencil-alt"
|
|
||||||
/>
|
|
||||||
{{/if}}
|
|
||||||
{{else}}
|
|
||||||
<DButton
|
|
||||||
@action={{action "togglePreview"}}
|
|
||||||
@translatedTitle={{this.toggleText}}
|
|
||||||
@icon="angle-double-left"
|
|
||||||
@class={{concat
|
|
||||||
"btn-flat btn-mini-toggle toggle-preview "
|
|
||||||
(unless this.showPreview "active")
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{else}}
|
|
||||||
<div class="saving-text">
|
|
||||||
{{#if this.model.createdPost}}
|
|
||||||
{{i18n "composer.saved"}}
|
|
||||||
<a
|
|
||||||
href={{this.createdPost.url}}
|
|
||||||
{{on "click" this.viewNewReply}}
|
|
||||||
class="permalink"
|
|
||||||
>{{i18n "composer.view_new_post"}}</a>
|
|
||||||
{{else}}
|
|
||||||
{{i18n "composer.saving"}}
|
|
||||||
{{loading-spinner size="small"}}
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="draft-text">
|
|
||||||
{{#if this.model.topic}}
|
|
||||||
{{d-icon "share"}}
|
|
||||||
{{html-safe this.draftTitle}}
|
|
||||||
{{else}}
|
|
||||||
{{i18n "composer.saved_draft"}}
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ComposerToggles
|
|
||||||
@composeState={{this.model.composeState}}
|
|
||||||
@toggleFullscreen={{action "openIfDraft"}}
|
|
||||||
@toggleComposer={{action "toggle"}}
|
|
||||||
@toggleToolbar={{action "toggleToolbar"}}
|
|
||||||
/>
|
|
||||||
{{/if}}
|
|
||||||
{{/if}}
|
|
||||||
</ComposerBody>
|
|
||||||
@ -12,7 +12,7 @@ import I18n from "I18n";
|
|||||||
import selectKit from "discourse/tests/helpers/select-kit-helper";
|
import selectKit from "discourse/tests/helpers/select-kit-helper";
|
||||||
import sinon from "sinon";
|
import sinon from "sinon";
|
||||||
import { test } from "qunit";
|
import { test } from "qunit";
|
||||||
import { toggleCheckDraftPopup } from "discourse/controllers/composer";
|
import { toggleCheckDraftPopup } from "discourse/services/composer";
|
||||||
import userFixtures from "discourse/tests/fixtures/user-fixtures";
|
import userFixtures from "discourse/tests/fixtures/user-fixtures";
|
||||||
import { cloneJSON } from "discourse-common/lib/object";
|
import { cloneJSON } from "discourse-common/lib/object";
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import {
|
|||||||
triggerKeyEvent,
|
triggerKeyEvent,
|
||||||
visit,
|
visit,
|
||||||
} from "@ember/test-helpers";
|
} from "@ember/test-helpers";
|
||||||
import { toggleCheckDraftPopup } from "discourse/controllers/composer";
|
import { toggleCheckDraftPopup } from "discourse/services/composer";
|
||||||
import { cloneJSON } from "discourse-common/lib/object";
|
import { cloneJSON } from "discourse-common/lib/object";
|
||||||
import TopicFixtures from "discourse/tests/fixtures/topic";
|
import TopicFixtures from "discourse/tests/fixtures/topic";
|
||||||
import LinkLookup from "discourse/lib/link-lookup";
|
import LinkLookup from "discourse/lib/link-lookup";
|
||||||
@ -996,7 +996,7 @@ acceptance("Composer", function (needs) {
|
|||||||
await visit("/t/internationalization-localization/280");
|
await visit("/t/internationalization-localization/280");
|
||||||
await click("#topic-footer-buttons .create");
|
await click("#topic-footer-buttons .create");
|
||||||
|
|
||||||
this.container.lookup("controller:composer").set(
|
this.container.lookup("service:composer").set(
|
||||||
"linkLookup",
|
"linkLookup",
|
||||||
new LinkLookup({
|
new LinkLookup({
|
||||||
"github.com": {
|
"github.com": {
|
||||||
@ -1165,7 +1165,7 @@ acceptance("Composer - Focus Open and Closed", function (needs) {
|
|||||||
test("Focusing a composer which is not open with create topic", async function (assert) {
|
test("Focusing a composer which is not open with create topic", async function (assert) {
|
||||||
await visit("/t/internationalization-localization/280");
|
await visit("/t/internationalization-localization/280");
|
||||||
|
|
||||||
const composer = this.container.lookup("controller:composer");
|
const composer = this.container.lookup("service:composer");
|
||||||
await composer.focusComposer({ fallbackToNewTopic: true });
|
await composer.focusComposer({ fallbackToNewTopic: true });
|
||||||
|
|
||||||
await settled();
|
await settled();
|
||||||
@ -1180,7 +1180,7 @@ acceptance("Composer - Focus Open and Closed", function (needs) {
|
|||||||
test("Focusing a composer which is not open with create topic and append text", async function (assert) {
|
test("Focusing a composer which is not open with create topic and append text", async function (assert) {
|
||||||
await visit("/t/internationalization-localization/280");
|
await visit("/t/internationalization-localization/280");
|
||||||
|
|
||||||
const composer = this.container.lookup("controller:composer");
|
const composer = this.container.lookup("service:composer");
|
||||||
await composer.focusComposer({
|
await composer.focusComposer({
|
||||||
fallbackToNewTopic: true,
|
fallbackToNewTopic: true,
|
||||||
insertText: "this is appended",
|
insertText: "this is appended",
|
||||||
@ -1202,7 +1202,7 @@ acceptance("Composer - Focus Open and Closed", function (needs) {
|
|||||||
await visit("/");
|
await visit("/");
|
||||||
await click("#create-topic");
|
await click("#create-topic");
|
||||||
|
|
||||||
const composer = this.container.lookup("controller:composer");
|
const composer = this.container.lookup("service:composer");
|
||||||
await composer.focusComposer();
|
await composer.focusComposer();
|
||||||
|
|
||||||
await settled();
|
await settled();
|
||||||
@ -1217,7 +1217,7 @@ acceptance("Composer - Focus Open and Closed", function (needs) {
|
|||||||
await visit("/");
|
await visit("/");
|
||||||
await click("#create-topic");
|
await click("#create-topic");
|
||||||
|
|
||||||
const composer = this.container.lookup("controller:composer");
|
const composer = this.container.lookup("service:composer");
|
||||||
await composer.focusComposer({ insertText: "this is some appended text" });
|
await composer.focusComposer({ insertText: "this is some appended text" });
|
||||||
|
|
||||||
await settled();
|
await settled();
|
||||||
@ -1239,7 +1239,7 @@ acceptance("Composer - Focus Open and Closed", function (needs) {
|
|||||||
await fillIn(".d-editor-input", "This is a dirty reply");
|
await fillIn(".d-editor-input", "This is a dirty reply");
|
||||||
await click(".toggle-minimize");
|
await click(".toggle-minimize");
|
||||||
|
|
||||||
const composer = this.container.lookup("controller:composer");
|
const composer = this.container.lookup("service:composer");
|
||||||
await composer.focusComposer({ insertText: "this is some appended text" });
|
await composer.focusComposer({ insertText: "this is some appended text" });
|
||||||
|
|
||||||
await settled();
|
await settled();
|
||||||
|
|||||||
@ -542,7 +542,7 @@ acceptance("Tag info", function (needs) {
|
|||||||
test("composer will not set tags if user cannot create them", async function (assert) {
|
test("composer will not set tags if user cannot create them", async function (assert) {
|
||||||
await visit("/tag/planters");
|
await visit("/tag/planters");
|
||||||
await click("#create-topic");
|
await click("#create-topic");
|
||||||
let composer = this.owner.lookup("controller:composer");
|
let composer = this.owner.lookup("service:composer");
|
||||||
assert.strictEqual(composer.get("model").tags, undefined);
|
assert.strictEqual(composer.get("model").tags, undefined);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -611,7 +611,7 @@ acceptance("Tag show - create topic", function (needs) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("composer will not set tags with all/none tags when creating topic", async function (assert) {
|
test("composer will not set tags with all/none tags when creating topic", async function (assert) {
|
||||||
const composer = this.owner.lookup("controller:composer");
|
const composer = this.owner.lookup("service:composer");
|
||||||
|
|
||||||
await visit("/tag/none");
|
await visit("/tag/none");
|
||||||
await click("#create-topic");
|
await click("#create-topic");
|
||||||
@ -623,7 +623,7 @@ acceptance("Tag show - create topic", function (needs) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("composer will set tags from selected tag", async function (assert) {
|
test("composer will set tags from selected tag", async function (assert) {
|
||||||
const composer = this.owner.lookup("controller:composer");
|
const composer = this.owner.lookup("service:composer");
|
||||||
|
|
||||||
await visit("/tag/planters");
|
await visit("/tag/planters");
|
||||||
await click("#create-topic");
|
await click("#create-topic");
|
||||||
|
|||||||
Reference in New Issue
Block a user