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:
Jarek Radosz 2022-08-18 13:58:08 +02:00 committed by GitHub
parent a252bbf3e8
commit 7b51ac418b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 211 additions and 241 deletions

View File

@ -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 });
}, },

View File

@ -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");
}, },
}, },