mirror of
https://github.com/discourse/discourse.git
synced 2024-11-27 06:03:38 +08:00
DEV: refactor (composer|d)-editor.js
This started as a way to prevent "previewUpdated" from doing the same work twice when morphing. Ended up refactoring "previewUpdated" and extracted into 5 distinct methods for clearer understanding and more consistent debouncing (using the "@debounce" decorator instead of the "discourseDebounce" method). No "feature" was changed, other than not doing the "decorateCookedElement" when morphing is enabled, since we already did it _before_ morphing.
This commit is contained in:
parent
4b043a2a82
commit
8509fc2ebc
|
@ -33,7 +33,6 @@ import ComposerUploadUppy from "discourse/mixins/composer-upload-uppy";
|
|||
import Composer from "discourse/models/composer";
|
||||
import { isTesting } from "discourse-common/config/environment";
|
||||
import { tinyAvatar } from "discourse-common/lib/avatar-utils";
|
||||
import discourseDebounce from "discourse-common/lib/debounce";
|
||||
import { iconHTML } from "discourse-common/lib/icon-library";
|
||||
import discourseLater from "discourse-common/lib/later";
|
||||
import { findRawTemplate } from "discourse-common/lib/raw-templates";
|
||||
|
@ -107,6 +106,9 @@ export function addApiImageWrapperButtonClickEvent(fn) {
|
|||
apiImageWrapperBtnEvents.push(fn);
|
||||
}
|
||||
|
||||
const DEBOUNCE_FETCH_MS = 450;
|
||||
const DEBOUNCE_JIT_MS = 2000;
|
||||
|
||||
export default Component.extend(ComposerUploadUppy, {
|
||||
classNameBindings: ["showToolbar:toolbar-visible", ":wmd-controls"],
|
||||
|
||||
|
@ -218,10 +220,11 @@ export default Component.extend(ComposerUploadUppy, {
|
|||
|
||||
@on("didInsertElement")
|
||||
_composerEditorInit() {
|
||||
const $input = $(this.element.querySelector(".d-editor-input"));
|
||||
const input = this.element.querySelector(".d-editor-input");
|
||||
const preview = this.element.querySelector(".d-editor-preview-wrapper");
|
||||
|
||||
if (this.siteSettings.enable_mentions) {
|
||||
$input.autocomplete({
|
||||
$(input).autocomplete({
|
||||
template: findRawTemplate("user-selector-autocomplete"),
|
||||
dataSource: (term) => {
|
||||
destroyUserStatuses();
|
||||
|
@ -235,9 +238,7 @@ export default Component.extend(ComposerUploadUppy, {
|
|||
return result;
|
||||
});
|
||||
},
|
||||
onRender: (options) => {
|
||||
renderUserStatusHtml(options);
|
||||
},
|
||||
onRender: (options) => renderUserStatusHtml(options),
|
||||
key: "@",
|
||||
transformComplete: (v) => v.username || v.name,
|
||||
afterComplete: this._afterMentionComplete,
|
||||
|
@ -247,13 +248,16 @@ export default Component.extend(ComposerUploadUppy, {
|
|||
});
|
||||
}
|
||||
|
||||
this.element
|
||||
.querySelector(".d-editor-input")
|
||||
?.addEventListener("scroll", this._throttledSyncEditorAndPreviewScroll);
|
||||
input?.addEventListener(
|
||||
"scroll",
|
||||
this._throttledSyncEditorAndPreviewScroll
|
||||
);
|
||||
|
||||
this._registerImageAltTextButtonClick(preview);
|
||||
|
||||
// Focus on the body unless we have a title
|
||||
if (!this.get("composer.canEditTitle")) {
|
||||
putCursorAtEnd(this.element.querySelector(".d-editor-input"));
|
||||
putCursorAtEnd(input);
|
||||
}
|
||||
|
||||
if (this.allowUpload) {
|
||||
|
@ -488,6 +492,17 @@ export default Component.extend(ComposerUploadUppy, {
|
|||
$preview.scrollTop(desired + 50);
|
||||
},
|
||||
|
||||
_renderMentions(preview, unseen) {
|
||||
unseen ||= linkSeenMentions(preview, this.siteSettings);
|
||||
if (unseen.length > 0) {
|
||||
this._renderUnseenMentions(preview, unseen);
|
||||
} else {
|
||||
this._warnMentionedGroups(preview);
|
||||
this._warnCannotSeeMention(preview);
|
||||
}
|
||||
},
|
||||
|
||||
@debounce(DEBOUNCE_FETCH_MS)
|
||||
_renderUnseenMentions(preview, unseen) {
|
||||
fetchUnseenMentions({
|
||||
names: unseen,
|
||||
|
@ -501,17 +516,50 @@ export default Component.extend(ComposerUploadUppy, {
|
|||
});
|
||||
},
|
||||
|
||||
_renderUnseenHashtags(preview) {
|
||||
const hashtagContext = this.site.hashtag_configurations["topic-composer"];
|
||||
const unseen = linkSeenHashtagsInContext(hashtagContext, preview);
|
||||
_renderHashtags(preview, unseen) {
|
||||
const context = this.site.hashtag_configurations["topic-composer"];
|
||||
unseen ||= linkSeenHashtagsInContext(context, preview);
|
||||
if (unseen.length > 0) {
|
||||
fetchUnseenHashtagsInContext(hashtagContext, unseen).then(() => {
|
||||
linkSeenHashtagsInContext(hashtagContext, preview);
|
||||
});
|
||||
this._renderUnseenHashtags(preview, unseen, context);
|
||||
}
|
||||
},
|
||||
|
||||
@debounce(2000)
|
||||
@debounce(DEBOUNCE_FETCH_MS)
|
||||
_renderUnseenHashtags(preview, unseen, context) {
|
||||
fetchUnseenHashtagsInContext(context, unseen).then(() =>
|
||||
linkSeenHashtagsInContext(context, preview)
|
||||
);
|
||||
},
|
||||
|
||||
@debounce(DEBOUNCE_FETCH_MS)
|
||||
_refreshOneboxes(preview) {
|
||||
const post = this.get("composer.post");
|
||||
// If we are editing a post, we'll refresh its contents once.
|
||||
const refresh = post && !post.get("refreshedPost");
|
||||
|
||||
const loaded = loadOneboxes(
|
||||
preview,
|
||||
ajax,
|
||||
this.get("composer.topic.id"),
|
||||
this.get("composer.category.id"),
|
||||
this.siteSettings.max_oneboxes_per_post,
|
||||
refresh
|
||||
);
|
||||
|
||||
if (refresh && loaded > 0) {
|
||||
post.set("refreshedPost", true);
|
||||
}
|
||||
},
|
||||
|
||||
_expandShortUrls(preview) {
|
||||
resolveAllShortUrls(ajax, this.siteSettings, preview);
|
||||
},
|
||||
|
||||
_decorateCookedElement(preview) {
|
||||
this.appEvents.trigger("decorate-non-stream-cooked-element", preview);
|
||||
},
|
||||
|
||||
@debounce(DEBOUNCE_JIT_MS)
|
||||
_warnMentionedGroups(preview) {
|
||||
schedule("afterRender", () => {
|
||||
preview
|
||||
|
@ -537,7 +585,7 @@ export default Component.extend(ComposerUploadUppy, {
|
|||
|
||||
// add a delay to allow for typing, so you don't open the warning right away
|
||||
// previously we would warn after @bob even if you were about to mention @bob2
|
||||
@debounce(2000)
|
||||
@debounce(DEBOUNCE_JIT_MS)
|
||||
_warnCannotSeeMention(preview) {
|
||||
if (this.composer.draftKey === Composer.NEW_PRIVATE_MESSAGE_KEY) {
|
||||
return;
|
||||
|
@ -773,24 +821,31 @@ export default Component.extend(ComposerUploadUppy, {
|
|||
},
|
||||
|
||||
_registerImageAltTextButtonClick(preview) {
|
||||
preview.addEventListener("click", this._handleAltTextCancelButtonClick);
|
||||
preview.addEventListener("click", this._handleAltTextEditButtonClick);
|
||||
preview.addEventListener("click", this._handleAltTextOkButtonClick);
|
||||
preview.addEventListener("click", this._handleAltTextCancelButtonClick);
|
||||
preview.addEventListener("click", this._handleImageDeleteButtonClick);
|
||||
preview.addEventListener("keypress", this._handleAltTextInputKeypress);
|
||||
preview.addEventListener("click", this._handleImageGridButtonClick);
|
||||
preview.addEventListener("click", this._handleImageScaleButtonClick);
|
||||
preview.addEventListener("keypress", this._handleAltTextInputKeypress);
|
||||
|
||||
if (apiImageWrapperBtnEvents.length > 0) {
|
||||
apiImageWrapperBtnEvents.forEach((fn) => {
|
||||
preview.addEventListener("click", fn);
|
||||
});
|
||||
}
|
||||
apiImageWrapperBtnEvents.forEach((fn) =>
|
||||
preview.addEventListener("click", fn)
|
||||
);
|
||||
},
|
||||
|
||||
@on("willDestroyElement")
|
||||
_composerClosed() {
|
||||
this._unbindMobileUploadButton();
|
||||
const input = this.element.querySelector(".d-editor-input");
|
||||
const preview = this.element.querySelector(".d-editor-preview-wrapper");
|
||||
|
||||
if (this.allowUpload) {
|
||||
this._unbindUploadTarget();
|
||||
this._unbindMobileUploadButton();
|
||||
}
|
||||
|
||||
this.appEvents.trigger(`${this.composerEventPrefix}:will-close`);
|
||||
|
||||
next(() => {
|
||||
// need to wait a bit for the "slide down" transition of the composer
|
||||
discourseLater(
|
||||
|
@ -799,27 +854,22 @@ export default Component.extend(ComposerUploadUppy, {
|
|||
);
|
||||
});
|
||||
|
||||
this.element
|
||||
.querySelector(".d-editor-input")
|
||||
?.removeEventListener(
|
||||
"scroll",
|
||||
this._throttledSyncEditorAndPreviewScroll
|
||||
);
|
||||
input?.removeEventListener(
|
||||
"scroll",
|
||||
this._throttledSyncEditorAndPreviewScroll
|
||||
);
|
||||
|
||||
const preview = this.element.querySelector(".d-editor-preview-wrapper");
|
||||
preview?.removeEventListener("click", this._handleImageScaleButtonClick);
|
||||
preview?.removeEventListener("click", this._handleAltTextCancelButtonClick);
|
||||
preview?.removeEventListener("click", this._handleAltTextEditButtonClick);
|
||||
preview?.removeEventListener("click", this._handleAltTextOkButtonClick);
|
||||
preview?.removeEventListener("click", this._handleImageDeleteButtonClick);
|
||||
preview?.removeEventListener("click", this._handleImageGridButtonClick);
|
||||
preview?.removeEventListener("click", this._handleAltTextCancelButtonClick);
|
||||
preview?.removeEventListener("click", this._handleImageScaleButtonClick);
|
||||
preview?.removeEventListener("keypress", this._handleAltTextInputKeypress);
|
||||
|
||||
if (apiImageWrapperBtnEvents.length > 0) {
|
||||
apiImageWrapperBtnEvents.forEach((fn) => {
|
||||
preview?.removeEventListener("click", fn);
|
||||
});
|
||||
}
|
||||
apiImageWrapperBtnEvents.forEach((fn) =>
|
||||
preview?.removeEventListener("click", fn)
|
||||
);
|
||||
},
|
||||
|
||||
onExpandPopupMenuOptions(toolbarEvent) {
|
||||
|
@ -919,65 +969,17 @@ export default Component.extend(ComposerUploadUppy, {
|
|||
});
|
||||
},
|
||||
|
||||
previewUpdated(preview) {
|
||||
// cache jquery objects for functions still using jquery
|
||||
const $preview = $(preview);
|
||||
previewUpdated(preview, unseenMentions, unseenHashtags) {
|
||||
this._renderMentions(preview, unseenMentions);
|
||||
this._renderHashtags(preview, unseenHashtags);
|
||||
this._refreshOneboxes(preview);
|
||||
this._expandShortUrls(preview);
|
||||
|
||||
// Paint mentions
|
||||
const unseenMentions = linkSeenMentions(preview, this.siteSettings);
|
||||
if (unseenMentions.length) {
|
||||
discourseDebounce(
|
||||
this,
|
||||
this._renderUnseenMentions,
|
||||
preview,
|
||||
unseenMentions,
|
||||
450
|
||||
);
|
||||
if (!this.siteSettings.enable_diffhtml_preview) {
|
||||
this._decorateCookedElement(preview);
|
||||
}
|
||||
|
||||
this._warnMentionedGroups(preview);
|
||||
this._warnCannotSeeMention(preview);
|
||||
|
||||
// Paint category, tag, and other data source hashtags
|
||||
const hashtagContext = this.site.hashtag_configurations["topic-composer"];
|
||||
if (linkSeenHashtagsInContext(hashtagContext, preview).length > 0) {
|
||||
discourseDebounce(this, this._renderUnseenHashtags, preview, 450);
|
||||
}
|
||||
|
||||
// Paint oneboxes
|
||||
const paintFunc = () => {
|
||||
const post = this.get("composer.post");
|
||||
let refresh = false;
|
||||
|
||||
//If we are editing a post, we'll refresh its contents once.
|
||||
if (post && !post.get("refreshedPost")) {
|
||||
refresh = true;
|
||||
}
|
||||
|
||||
const paintedCount = loadOneboxes(
|
||||
preview,
|
||||
ajax,
|
||||
this.get("composer.topic.id"),
|
||||
this.get("composer.category.id"),
|
||||
this.siteSettings.max_oneboxes_per_post,
|
||||
refresh
|
||||
);
|
||||
|
||||
if (refresh && paintedCount > 0) {
|
||||
post.set("refreshedPost", true);
|
||||
}
|
||||
};
|
||||
|
||||
discourseDebounce(this, paintFunc, 450);
|
||||
|
||||
// Short upload urls need resolution
|
||||
resolveAllShortUrls(ajax, this.siteSettings, preview);
|
||||
|
||||
preview.addEventListener("click", this._handleImageScaleButtonClick);
|
||||
this._registerImageAltTextButtonClick(preview);
|
||||
|
||||
this.appEvents.trigger("decorate-non-stream-cooked-element", preview);
|
||||
this.afterRefresh($preview);
|
||||
this.afterRefresh(preview);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -6,7 +6,7 @@ import ItsATrap from "@discourse/itsatrap";
|
|||
import $ from "jquery";
|
||||
import { emojiSearch, isSkinTonableEmoji } from "pretty-text/emoji";
|
||||
import { translations } from "pretty-text/emoji/data";
|
||||
import { resolveAllShortUrls } from "pretty-text/upload-short-url";
|
||||
import { resolveCachedShortUrls } from "pretty-text/upload-short-url";
|
||||
import { Promise } from "rsvp";
|
||||
import InsertHyperlink from "discourse/components/modal/insert-hyperlink";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
|
@ -445,14 +445,16 @@ export default Component.extend(TextareaTextManipulation, {
|
|||
|
||||
this.set("preview", cooked);
|
||||
|
||||
let unseenMentions, unseenHashtags;
|
||||
|
||||
if (this.siteSettings.enable_diffhtml_preview) {
|
||||
const previewElement = this.element.querySelector(".d-editor-preview");
|
||||
const cookedElement = previewElement.cloneNode(false);
|
||||
cookedElement.innerHTML = cooked;
|
||||
|
||||
linkSeenMentions(cookedElement, this.siteSettings);
|
||||
unseenMentions = linkSeenMentions(cookedElement, this.siteSettings);
|
||||
|
||||
linkSeenHashtagsInContext(
|
||||
unseenHashtags = linkSeenHashtagsInContext(
|
||||
this.site.hashtag_configurations["topic-composer"],
|
||||
cookedElement
|
||||
);
|
||||
|
@ -467,7 +469,7 @@ export default Component.extend(TextareaTextManipulation, {
|
|||
/* offline */ true
|
||||
);
|
||||
|
||||
resolveAllShortUrls(ajax, this.siteSettings, cookedElement);
|
||||
resolveCachedShortUrls(this.siteSettings, cookedElement);
|
||||
|
||||
// trigger all the "api.decorateCookedElement"
|
||||
this.appEvents.trigger(
|
||||
|
@ -495,7 +497,7 @@ export default Component.extend(TextareaTextManipulation, {
|
|||
const previewElement = this.element.querySelector(".d-editor-preview");
|
||||
|
||||
if (previewElement && this.previewUpdated) {
|
||||
this.previewUpdated(previewElement);
|
||||
this.previewUpdated(previewElement, unseenMentions, unseenHashtags);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
|
|
@ -698,7 +698,7 @@ export default class ComposerService extends Service {
|
|||
}
|
||||
|
||||
@action
|
||||
afterRefresh($preview) {
|
||||
afterRefresh(preview) {
|
||||
const topic = this.get("model.topic");
|
||||
const linkLookup = this.linkLookup;
|
||||
|
||||
|
@ -712,13 +712,12 @@ export default class ComposerService extends Service {
|
|||
}
|
||||
|
||||
const post = this.get("model.post");
|
||||
const $links = $("a[href]", $preview);
|
||||
$links.each((idx, l) => {
|
||||
preview.querySelectorAll("a[href]").forEach((l) => {
|
||||
const href = l.href;
|
||||
if (href && href.length) {
|
||||
// skip links added by watched words
|
||||
if (l.dataset.word !== undefined) {
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
// skip links in quotes and oneboxes
|
||||
|
@ -734,7 +733,7 @@ export default class ComposerService extends Service {
|
|||
element.tagName === "ASIDE" &&
|
||||
element.classList.contains("quote")
|
||||
) {
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
|
@ -742,7 +741,7 @@ export default class ComposerService extends Service {
|
|||
element.classList.contains("onebox") &&
|
||||
href !== element.dataset["onebox-src"]
|
||||
) {
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -771,11 +770,8 @@ export default class ComposerService extends Service {
|
|||
}),
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user