mirror of
https://github.com/discourse/discourse.git
synced 2024-11-24 06:15:28 +08:00
DEV: Asyncify most of Composer controller (#17974)
…and fix cases where we were breaking the promise/async chain (by not awaiting or not returning promises)
This commit is contained in:
parent
a252bbf3e8
commit
7b51ac418b
|
@ -6,7 +6,7 @@ import {
|
||||||
authorizesOneOrMoreExtensions,
|
authorizesOneOrMoreExtensions,
|
||||||
uploadIcon,
|
uploadIcon,
|
||||||
} from "discourse/lib/uploads";
|
} from "discourse/lib/uploads";
|
||||||
import { cancel, run, scheduleOnce } from "@ember/runloop";
|
import { cancel, scheduleOnce } from "@ember/runloop";
|
||||||
import {
|
import {
|
||||||
cannotPostAgain,
|
cannotPostAgain,
|
||||||
durationTextFromSeconds,
|
durationTextFromSeconds,
|
||||||
|
@ -424,53 +424,43 @@ export default Controller.extend({
|
||||||
// - openOpts: this object will be passed to this.open if fallbackToNewTopic is
|
// - openOpts: this object will be passed to this.open if fallbackToNewTopic is
|
||||||
// true or topic is provided
|
// true or topic is provided
|
||||||
@action
|
@action
|
||||||
focusComposer(opts = {}) {
|
async focusComposer(opts = {}) {
|
||||||
return this._openComposerForFocus(opts).then(() => {
|
await this._openComposerForFocus(opts);
|
||||||
this._focusAndInsertText(opts.insertText);
|
this._focusAndInsertText(opts.insertText);
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_openComposerForFocus(opts) {
|
async _openComposerForFocus(opts) {
|
||||||
if (this.get("model.viewOpen")) {
|
if (this.get("model.viewOpen")) {
|
||||||
return Promise.resolve();
|
return;
|
||||||
} else {
|
}
|
||||||
|
|
||||||
const opened = this.openIfDraft();
|
const opened = this.openIfDraft();
|
||||||
if (opened) {
|
if (opened) {
|
||||||
return Promise.resolve();
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts.topic) {
|
if (opts.topic) {
|
||||||
return this.open(
|
return await this.open({
|
||||||
Object.assign(
|
|
||||||
{
|
|
||||||
action: Composer.REPLY,
|
action: Composer.REPLY,
|
||||||
draftKey: opts.topic.get("draft_key"),
|
draftKey: opts.topic.get("draft_key"),
|
||||||
draftSequence: opts.topic.get("draft_sequence"),
|
draftSequence: opts.topic.get("draft_sequence"),
|
||||||
topic: opts.topic,
|
topic: opts.topic,
|
||||||
},
|
...(opts.openOpts || {}),
|
||||||
opts.openOpts || {}
|
});
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts.fallbackToNewTopic) {
|
if (opts.fallbackToNewTopic) {
|
||||||
return this.open(
|
return await this.open({
|
||||||
Object.assign(
|
|
||||||
{
|
|
||||||
action: Composer.CREATE_TOPIC,
|
action: Composer.CREATE_TOPIC,
|
||||||
draftKey: Composer.NEW_TOPIC_KEY,
|
draftKey: Composer.NEW_TOPIC_KEY,
|
||||||
},
|
...(opts.openOpts || {}),
|
||||||
opts.openOpts || {}
|
});
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_focusAndInsertText(insertText) {
|
_focusAndInsertText(insertText) {
|
||||||
scheduleOnce("afterRender", () => {
|
scheduleOnce("afterRender", () => {
|
||||||
const input = document.querySelector("textarea.d-editor-input");
|
document.querySelector("textarea.d-editor-input")?.focus();
|
||||||
input && input.focus();
|
|
||||||
|
|
||||||
if (insertText) {
|
if (insertText) {
|
||||||
this.model.appendText(insertText, null, { new_line: true });
|
this.model.appendText(insertText, null, { new_line: true });
|
||||||
|
@ -480,7 +470,10 @@ export default Controller.extend({
|
||||||
|
|
||||||
@action
|
@action
|
||||||
openIfDraft(event) {
|
openIfDraft(event) {
|
||||||
if (this.get("model.viewDraft")) {
|
if (!this.get("model.viewDraft")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// when called from shortcut, ensure we don't propagate the key to
|
// when called from shortcut, ensure we don't propagate the key to
|
||||||
// the composer input title
|
// the composer input title
|
||||||
if (event) {
|
if (event) {
|
||||||
|
@ -489,14 +482,13 @@ export default Controller.extend({
|
||||||
}
|
}
|
||||||
|
|
||||||
this.set("model.composeState", Composer.OPEN);
|
this.set("model.composeState", Composer.OPEN);
|
||||||
|
|
||||||
document.documentElement.style.setProperty(
|
document.documentElement.style.setProperty(
|
||||||
"--composer-height",
|
"--composer-height",
|
||||||
this.get("model.composerHeight")
|
this.get("model.composerHeight")
|
||||||
);
|
);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -513,20 +505,11 @@ export default Controller.extend({
|
||||||
this.close();
|
this.close();
|
||||||
},
|
},
|
||||||
|
|
||||||
openComposer(options, post, topic) {
|
async openComposer(options, post, topic) {
|
||||||
this.open(options).then(() => {
|
await this.open(options);
|
||||||
let url;
|
|
||||||
if (post) {
|
|
||||||
url = post.url;
|
|
||||||
}
|
|
||||||
if (!post && topic) {
|
|
||||||
url = topic.url;
|
|
||||||
}
|
|
||||||
|
|
||||||
let topicTitle;
|
let url = post?.url || topic?.url;
|
||||||
if (topic) {
|
const topicTitle = topic?.title;
|
||||||
topicTitle = topic.title;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!url || !topicTitle) {
|
if (!url || !topicTitle) {
|
||||||
return;
|
return;
|
||||||
|
@ -539,12 +522,13 @@ export default Controller.extend({
|
||||||
});
|
});
|
||||||
|
|
||||||
const reply = this.get("model.reply");
|
const reply = this.get("model.reply");
|
||||||
if (!reply || !reply.includes(continueDiscussion)) {
|
if (reply?.includes(continueDiscussion)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.model.prependText(continueDiscussion, {
|
this.model.prependText(continueDiscussion, {
|
||||||
new_line: true,
|
new_line: true,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
cancelUpload() {
|
cancelUpload() {
|
||||||
|
@ -654,19 +638,17 @@ export default Controller.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
// Toggle the reply view
|
// Toggle the reply view
|
||||||
toggle() {
|
async toggle() {
|
||||||
this.closeAutocomplete();
|
this.closeAutocomplete();
|
||||||
|
|
||||||
const composer = this.model;
|
const composer = this.model;
|
||||||
|
|
||||||
if (isEmpty(composer?.reply) && isEmpty(composer?.title)) {
|
if (isEmpty(composer?.reply) && isEmpty(composer?.title)) {
|
||||||
this.close();
|
this.close();
|
||||||
} else {
|
} else if (composer?.viewOpenOrFullscreen) {
|
||||||
if (composer?.viewOpenOrFullscreen) {
|
|
||||||
this.shrink();
|
this.shrink();
|
||||||
} else {
|
} else {
|
||||||
this.cancelComposer();
|
await this.cancelComposer();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -678,7 +660,7 @@ export default Controller.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
// Import a quote from the post
|
// Import a quote from the post
|
||||||
importQuote(toolbarEvent) {
|
async importQuote(toolbarEvent) {
|
||||||
const postStream = this.get("topic.postStream");
|
const postStream = this.get("topic.postStream");
|
||||||
let postId = this.get("model.post.id");
|
let postId = this.get("model.post.id");
|
||||||
|
|
||||||
|
@ -702,22 +684,21 @@ export default Controller.extend({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (postId) {
|
if (!postId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.set("model.loading", true);
|
this.set("model.loading", true);
|
||||||
|
|
||||||
return this.store.find("post", postId).then((post) => {
|
const post = await this.store.find("post", postId);
|
||||||
const quote = buildQuote(post, post.raw, {
|
const quote = buildQuote(post, post.raw, { full: true });
|
||||||
full: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
toolbarEvent.addText(quote);
|
toolbarEvent.addText(quote);
|
||||||
this.set("model.loading", false);
|
this.set("model.loading", false);
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
cancel() {
|
async cancel() {
|
||||||
this.cancelComposer();
|
await this.cancelComposer();
|
||||||
},
|
},
|
||||||
|
|
||||||
save(ignore, event) {
|
save(ignore, event) {
|
||||||
|
@ -1002,6 +983,7 @@ export default Controller.extend({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.responseJson.route_to) {
|
if (result.responseJson.route_to) {
|
||||||
|
// TODO: await this:
|
||||||
this.destroyDraft();
|
this.destroyDraft();
|
||||||
if (result.responseJson.message) {
|
if (result.responseJson.message) {
|
||||||
return bootbox.alert(result.responseJson.message, () => {
|
return bootbox.alert(result.responseJson.message, () => {
|
||||||
|
@ -1072,9 +1054,7 @@ export default Controller.extend({
|
||||||
@param {Boolean} [opts.skipDraftCheck]
|
@param {Boolean} [opts.skipDraftCheck]
|
||||||
@param {Boolean} [opts.skipJumpOnSave] Option to skip navigating to the post when saved in this composer session
|
@param {Boolean} [opts.skipJumpOnSave] Option to skip navigating to the post when saved in this composer session
|
||||||
**/
|
**/
|
||||||
open(opts) {
|
open(opts = {}) {
|
||||||
opts = opts || {};
|
|
||||||
|
|
||||||
if (!opts.draftKey) {
|
if (!opts.draftKey) {
|
||||||
throw new Error("composer opened without a proper draft key");
|
throw new Error("composer opened without a proper draft key");
|
||||||
}
|
}
|
||||||
|
@ -1192,7 +1172,7 @@ export default Controller.extend({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this._setModel(composerModel, opts).then(resolve, reject);
|
return this._setModel(composerModel, opts).then(resolve, reject);
|
||||||
});
|
});
|
||||||
|
|
||||||
promise = promise.finally(() => {
|
promise = promise.finally(() => {
|
||||||
|
@ -1202,27 +1182,24 @@ export default Controller.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
// Given a potential instance and options, set the model for this composer.
|
// Given a potential instance and options, set the model for this composer.
|
||||||
_setModel(optionalComposerModel, opts) {
|
async _setModel(optionalComposerModel, opts) {
|
||||||
let promise = Promise.resolve();
|
|
||||||
|
|
||||||
this.set("linkLookup", null);
|
this.set("linkLookup", null);
|
||||||
|
|
||||||
promise = promise.then(() => {
|
let composerModel;
|
||||||
if (opts.draft) {
|
if (opts.draft) {
|
||||||
return loadDraft(this.store, opts).then((model) => {
|
composerModel = await loadDraft(this.store, opts);
|
||||||
if (!model) {
|
|
||||||
|
if (!composerModel) {
|
||||||
throw new Error("draft was not found");
|
throw new Error("draft was not found");
|
||||||
}
|
}
|
||||||
return model;
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
let model =
|
const model =
|
||||||
optionalComposerModel || this.store.createRecord("composer");
|
optionalComposerModel || this.store.createRecord("composer");
|
||||||
return model.open(opts).then(() => model);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
promise.then((composerModel) => {
|
await model.open(opts);
|
||||||
|
composerModel = model;
|
||||||
|
}
|
||||||
|
|
||||||
this.set("model", composerModel);
|
this.set("model", composerModel);
|
||||||
|
|
||||||
composerModel.setProperties({
|
composerModel.setProperties({
|
||||||
|
@ -1276,9 +1253,6 @@ export default Controller.extend({
|
||||||
"--composer-height",
|
"--composer-height",
|
||||||
defaultComposerHeight
|
defaultComposerHeight
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
|
||||||
return promise;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
viewNewReply() {
|
viewNewReply() {
|
||||||
|
@ -1287,24 +1261,24 @@ export default Controller.extend({
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
destroyDraft(draftSequence = null) {
|
async destroyDraft(draftSequence = null) {
|
||||||
const key = this.get("model.draftKey");
|
const key = this.get("model.draftKey");
|
||||||
if (key) {
|
if (!key) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (key === Composer.NEW_TOPIC_KEY) {
|
if (key === Composer.NEW_TOPIC_KEY) {
|
||||||
this.currentUser.set("has_topic_draft", false);
|
this.currentUser.set("has_topic_draft", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._saveDraftPromise) {
|
if (this._saveDraftPromise) {
|
||||||
return this._saveDraftPromise.then(() => this.destroyDraft());
|
await this._saveDraftPromise;
|
||||||
|
return await this.destroyDraft();
|
||||||
}
|
}
|
||||||
|
|
||||||
const sequence = draftSequence || this.get("model.draftSequence");
|
const sequence = draftSequence || this.get("model.draftSequence");
|
||||||
return Draft.clear(key, sequence).then(() =>
|
await Draft.clear(key, sequence);
|
||||||
this.appEvents.trigger("draft:destroyed", key)
|
this.appEvents.trigger("draft:destroyed", key);
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
confirmDraftAbandon(data) {
|
confirmDraftAbandon(data) {
|
||||||
|
@ -1319,7 +1293,11 @@ export default Controller.extend({
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_checkDraftPopup) {
|
if (!_checkDraftPopup) {
|
||||||
|
data.draft = null;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
bootbox.dialog(I18n.t("drafts.abandon.confirm"), [
|
bootbox.dialog(I18n.t("drafts.abandon.confirm"), [
|
||||||
{
|
{
|
||||||
|
@ -1339,10 +1317,6 @@ export default Controller.extend({
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
data.draft = null;
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
cancelComposer() {
|
cancelComposer() {
|
||||||
|
@ -1352,7 +1326,7 @@ export default Controller.extend({
|
||||||
cancel(this._saveDraftDebounce);
|
cancel(this._saveDraftDebounce);
|
||||||
}
|
}
|
||||||
|
|
||||||
let promise = new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (this.get("model.hasMetaData") || this.get("model.replyDirty")) {
|
if (this.get("model.hasMetaData") || this.get("model.replyDirty")) {
|
||||||
const modal = showModal("discard-draft", {
|
const modal = showModal("discard-draft", {
|
||||||
model: this.model,
|
model: this.model,
|
||||||
|
@ -1360,7 +1334,7 @@ export default Controller.extend({
|
||||||
});
|
});
|
||||||
modal.setProperties({
|
modal.setProperties({
|
||||||
onDestroyDraft: () => {
|
onDestroyDraft: () => {
|
||||||
this.destroyDraft()
|
return this.destroyDraft()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.model.clearState();
|
this.model.clearState();
|
||||||
this.close();
|
this.close();
|
||||||
|
@ -1375,7 +1349,7 @@ export default Controller.extend({
|
||||||
this.model.clearState();
|
this.model.clearState();
|
||||||
this.close();
|
this.close();
|
||||||
this.appEvents.trigger("composer:cancelled");
|
this.appEvents.trigger("composer:cancelled");
|
||||||
resolve();
|
return resolve();
|
||||||
},
|
},
|
||||||
// needed to resume saving drafts if composer stays open
|
// needed to resume saving drafts if composer stays open
|
||||||
onDismissModal: () => reject(),
|
onDismissModal: () => reject(),
|
||||||
|
@ -1392,9 +1366,7 @@ export default Controller.extend({
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
}).finally(() => {
|
||||||
|
|
||||||
return promise.finally(() => {
|
|
||||||
this.skipAutoSave = false;
|
this.skipAutoSave = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -1411,27 +1383,20 @@ export default Controller.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
_saveDraft() {
|
_saveDraft() {
|
||||||
const model = this.model;
|
if (!this.model) {
|
||||||
if (model) {
|
return;
|
||||||
if (model.draftSaving) {
|
|
||||||
// in test debounce is Ember.run, this will cause
|
|
||||||
// an infinite loop
|
|
||||||
if (!isTesting()) {
|
|
||||||
this._saveDraftDebounce = discourseDebounce(
|
|
||||||
this,
|
|
||||||
this._saveDraft,
|
|
||||||
2000
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.model.draftSaving) {
|
||||||
|
this._saveDraftDebounce = discourseDebounce(this, this._saveDraft, 2000);
|
||||||
} else {
|
} else {
|
||||||
this._saveDraftPromise = model
|
this._saveDraftPromise = this.model
|
||||||
.saveDraft(this.currentUser)
|
.saveDraft(this.currentUser)
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this._lastDraftSaved = Date.now();
|
this._lastDraftSaved = Date.now();
|
||||||
this._saveDraftPromise = null;
|
this._saveDraftPromise = null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
@observes("model.reply", "model.title")
|
@observes("model.reply", "model.title")
|
||||||
|
@ -1449,8 +1414,11 @@ export default Controller.extend({
|
||||||
if (Date.now() - this._lastDraftSaved > 15000) {
|
if (Date.now() - this._lastDraftSaved > 15000) {
|
||||||
this._saveDraft();
|
this._saveDraft();
|
||||||
} else {
|
} else {
|
||||||
let method = isTesting() ? run : discourseDebounce;
|
this._saveDraftDebounce = discourseDebounce(
|
||||||
this._saveDraftDebounce = method(this, this._saveDraft, 2000);
|
this,
|
||||||
|
this._saveDraft,
|
||||||
|
2000
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1516,7 +1484,7 @@ export default Controller.extend({
|
||||||
elem.classList.remove("fullscreen-composer");
|
elem.classList.remove("fullscreen-composer");
|
||||||
elem.classList.remove("composer-open");
|
elem.classList.remove("composer-open");
|
||||||
|
|
||||||
document.activeElement && document.activeElement.blur();
|
document.activeElement?.blur();
|
||||||
this.setProperties({ model: null, lastValidatedAt: null });
|
this.setProperties({ model: null, lastValidatedAt: null });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -3,16 +3,18 @@ import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||||
|
|
||||||
export default Controller.extend(ModalFunctionality, {
|
export default Controller.extend(ModalFunctionality, {
|
||||||
actions: {
|
actions: {
|
||||||
destroyDraft() {
|
async destroyDraft() {
|
||||||
this.onDestroyDraft();
|
await this.onDestroyDraft();
|
||||||
this.send("closeModal");
|
this.send("closeModal");
|
||||||
},
|
},
|
||||||
saveDraftAndClose() {
|
|
||||||
this.onSaveDraft();
|
async saveDraftAndClose() {
|
||||||
|
await this.onSaveDraft();
|
||||||
this.send("closeModal");
|
this.send("closeModal");
|
||||||
},
|
},
|
||||||
dismissModal() {
|
|
||||||
this.onDismissModal();
|
async dismissModal() {
|
||||||
|
await this.onDismissModal();
|
||||||
this.send("closeModal");
|
this.send("closeModal");
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue
Block a user