DEV: reduces jquery usage and memory leaks in composer (#14924)
Removes more than 60 jquery function leaks in one `Acceptance: Composer` run.
This commit is contained in:
parent
ef881fdedc
commit
ae16b0a9d4
|
@ -11,9 +11,9 @@ import discourseDebounce from "discourse-common/lib/debounce";
|
|||
import { headerHeight } from "discourse/components/site-header";
|
||||
import positioningWorkaround from "discourse/lib/safari-hacks";
|
||||
|
||||
const START_EVENTS = "touchstart mousedown";
|
||||
const DRAG_EVENTS = "touchmove mousemove";
|
||||
const END_EVENTS = "touchend mouseup";
|
||||
const START_DRAG_EVENTS = ["touchstart", "mousedown"];
|
||||
const DRAG_EVENTS = ["touchmove", "mousemove"];
|
||||
const END_DRAG_EVENTS = ["touchend", "mouseup"];
|
||||
|
||||
const THROTTLE_RATE = 20;
|
||||
|
||||
|
@ -54,17 +54,15 @@ export default Component.extend(KeyEnterEscape, {
|
|||
},
|
||||
|
||||
movePanels(size) {
|
||||
$("#main-outlet").css("padding-bottom", size ? size : "");
|
||||
document.querySelector("#main-outlet").style.paddingBottom = size
|
||||
? `${size}px`
|
||||
: "";
|
||||
|
||||
// signal the progress bar it should move!
|
||||
this.appEvents.trigger("composer:resized");
|
||||
},
|
||||
|
||||
@observes(
|
||||
"composeState",
|
||||
"composer.action",
|
||||
"composer.canEditTopicFeaturedLink"
|
||||
)
|
||||
@observes("composeState", "composer.{action,canEditTopicFeaturedLink}")
|
||||
resize() {
|
||||
schedule("afterRender", () => {
|
||||
if (!this.element || this.isDestroying || this.isDestroyed) {
|
||||
|
@ -76,8 +74,11 @@ export default Component.extend(KeyEnterEscape, {
|
|||
},
|
||||
|
||||
debounceMove() {
|
||||
const h = $("#reply-control:not(.saving)").height() || 0;
|
||||
this.movePanels(h);
|
||||
let height = 0;
|
||||
if (!this.element.classList.contains("saving")) {
|
||||
height = this.element.offsetHeight;
|
||||
}
|
||||
this.movePanels(height);
|
||||
},
|
||||
|
||||
keyUp() {
|
||||
|
@ -105,45 +106,13 @@ export default Component.extend(KeyEnterEscape, {
|
|||
},
|
||||
|
||||
setupComposerResizeEvents() {
|
||||
const $composer = $(this.element);
|
||||
const $grippie = $(this.element.querySelector(".grippie"));
|
||||
const $document = $(document);
|
||||
let origComposerSize = 0;
|
||||
let lastMousePos = 0;
|
||||
this.origComposerSize = 0;
|
||||
this.lastMousePos = 0;
|
||||
|
||||
const performDrag = (event) => {
|
||||
$composer.trigger("div-resizing");
|
||||
this.appEvents.trigger("composer:div-resizing");
|
||||
$composer.addClass("clear-transitions");
|
||||
const currentMousePos = mouseYPos(event);
|
||||
let size = origComposerSize + (lastMousePos - currentMousePos);
|
||||
|
||||
const winHeight = $(window).height();
|
||||
size = Math.min(size, winHeight - headerHeight());
|
||||
this.movePanels(size);
|
||||
$composer.height(size);
|
||||
};
|
||||
|
||||
const throttledPerformDrag = ((event) => {
|
||||
event.preventDefault();
|
||||
throttle(this, performDrag, event, THROTTLE_RATE);
|
||||
}).bind(this);
|
||||
|
||||
const endDrag = (() => {
|
||||
this.appEvents.trigger("composer:resize-ended");
|
||||
$document.off(DRAG_EVENTS, throttledPerformDrag);
|
||||
$document.off(END_EVENTS, endDrag);
|
||||
$composer.removeClass("clear-transitions");
|
||||
$composer.focus();
|
||||
}).bind(this);
|
||||
|
||||
$grippie.on(START_EVENTS, (event) => {
|
||||
event.preventDefault();
|
||||
origComposerSize = $composer.height();
|
||||
lastMousePos = mouseYPos(event);
|
||||
$document.on(DRAG_EVENTS, throttledPerformDrag);
|
||||
$document.on(END_EVENTS, endDrag);
|
||||
this.appEvents.trigger("composer:resize-started");
|
||||
START_DRAG_EVENTS.forEach((startDragEvent) => {
|
||||
this.element
|
||||
.querySelector(".grippie")
|
||||
?.addEventListener(startDragEvent, this.startDragHandler);
|
||||
});
|
||||
|
||||
if (this._visualViewportResizing()) {
|
||||
|
@ -152,6 +121,58 @@ export default Component.extend(KeyEnterEscape, {
|
|||
}
|
||||
},
|
||||
|
||||
@bind
|
||||
performDragHandler() {
|
||||
this.appEvents.trigger("composer:div-resizing");
|
||||
this.element.classList.add("clear-transitions");
|
||||
const currentMousePos = mouseYPos(event);
|
||||
let size = this.origComposerSize + (this.lastMousePos - currentMousePos);
|
||||
|
||||
size = Math.min(size, window.innerHeight - headerHeight());
|
||||
this.movePanels(size);
|
||||
this.element.style.height = size ? `${size}px` : "";
|
||||
},
|
||||
|
||||
@bind
|
||||
startDragHandler(event) {
|
||||
event.preventDefault();
|
||||
|
||||
this.origComposerSize = this.element.offsetHeight;
|
||||
this.lastMousePos = mouseYPos(event);
|
||||
|
||||
DRAG_EVENTS.forEach((dragEvent) => {
|
||||
document.addEventListener(dragEvent, this.throttledPerformDrag);
|
||||
});
|
||||
|
||||
END_DRAG_EVENTS.forEach((endDragEvent) => {
|
||||
document.addEventListener(endDragEvent, this.endDragHandler);
|
||||
});
|
||||
|
||||
this.appEvents.trigger("composer:resize-started");
|
||||
},
|
||||
|
||||
@bind
|
||||
endDragHandler() {
|
||||
this.appEvents.trigger("composer:resize-ended");
|
||||
|
||||
DRAG_EVENTS.forEach((dragEvent) => {
|
||||
document.removeEventListener(dragEvent, this.throttledPerformDrag);
|
||||
});
|
||||
|
||||
END_DRAG_EVENTS.forEach((endDragEvent) => {
|
||||
document.removeEventListener(endDragEvent, this.endDragHandler);
|
||||
});
|
||||
|
||||
this.element.classList.remove("clear-transitions");
|
||||
this.element.focus();
|
||||
},
|
||||
|
||||
@bind
|
||||
throttledPerformDrag(event) {
|
||||
event.preventDefault();
|
||||
throttle(this, this.performDragHandler, event, THROTTLE_RATE);
|
||||
},
|
||||
|
||||
@bind
|
||||
viewportResize() {
|
||||
const composerVH = window.visualViewport.height * 0.01,
|
||||
|
@ -207,10 +228,17 @@ export default Component.extend(KeyEnterEscape, {
|
|||
|
||||
willDestroyElement() {
|
||||
this._super(...arguments);
|
||||
|
||||
if (this._visualViewportResizing()) {
|
||||
window.visualViewport.removeEventListener("resize", this.viewportResize);
|
||||
}
|
||||
|
||||
START_DRAG_EVENTS.forEach((startDragEvent) => {
|
||||
this.element
|
||||
.querySelector(".grippie")
|
||||
?.removeEventListener(startDragEvent, this.startDragHandler);
|
||||
});
|
||||
|
||||
cancel(this._lastKeyTimeout);
|
||||
},
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
tinyAvatar,
|
||||
} from "discourse/lib/utilities";
|
||||
import discourseComputed, {
|
||||
bind,
|
||||
observes,
|
||||
on,
|
||||
} from "discourse-common/utils/decorators";
|
||||
|
@ -138,9 +139,7 @@ export default Component.extend(ComposerUpload, {
|
|||
|
||||
@discourseComputed
|
||||
showLink() {
|
||||
return (
|
||||
this.currentUser && this.currentUser.get("link_posting_access") !== "none"
|
||||
);
|
||||
return this.currentUser && this.currentUser.link_posting_access !== "none";
|
||||
},
|
||||
|
||||
@observes("focusTarget")
|
||||
|
@ -189,7 +188,8 @@ export default Component.extend(ComposerUpload, {
|
|||
};
|
||||
},
|
||||
|
||||
userSearchTerm(term) {
|
||||
@bind
|
||||
_userSearchTerm(term) {
|
||||
const topicId = this.get("topic.id");
|
||||
// maybe this is a brand new topic, so grab category from composer
|
||||
const categoryId =
|
||||
|
@ -218,34 +218,42 @@ export default Component.extend(ComposerUpload, {
|
|||
return extensions.map((ext) => `.${ext}`).join();
|
||||
},
|
||||
|
||||
@bind
|
||||
_afterMentionComplete(value) {
|
||||
this.composer.set("reply", value);
|
||||
|
||||
// ensures textarea scroll position is correct
|
||||
schedule("afterRender", () => {
|
||||
const input = this.element.querySelector(".d-editor-input");
|
||||
input?.blur();
|
||||
input?.focus();
|
||||
});
|
||||
},
|
||||
|
||||
@on("didInsertElement")
|
||||
_composerEditorInit() {
|
||||
const $input = $(this.element.querySelector(".d-editor-input"));
|
||||
const $preview = $(this.element.querySelector(".d-editor-preview-wrapper"));
|
||||
|
||||
if (this.siteSettings.enable_mentions) {
|
||||
$input.autocomplete({
|
||||
template: findRawTemplate("user-selector-autocomplete"),
|
||||
dataSource: (term) => this.userSearchTerm.call(this, term),
|
||||
dataSource: this._userSearchTerm,
|
||||
key: "@",
|
||||
transformComplete: (v) => v.username || v.name,
|
||||
afterComplete: (value) => {
|
||||
this.composer.set("reply", value);
|
||||
|
||||
// ensures textarea scroll position is correct
|
||||
schedule("afterRender", () => $input.blur().focus());
|
||||
},
|
||||
afterComplete: this._afterMentionComplete,
|
||||
triggerRule: (textarea) =>
|
||||
!inCodeBlock(textarea.value, caretPosition(textarea)),
|
||||
});
|
||||
}
|
||||
|
||||
if (this._enableAdvancedEditorPreviewSync()) {
|
||||
this._initInputPreviewSync($input, $preview);
|
||||
const input = this.element.querySelector(".d-editor-input");
|
||||
const preview = this.element.querySelector(".d-editor-preview-wrapper");
|
||||
this._initInputPreviewSync(input, preview);
|
||||
} else {
|
||||
$input.on("scroll", () =>
|
||||
throttle(this, this._syncEditorAndPreviewScroll, $input, $preview, 20)
|
||||
);
|
||||
this.element
|
||||
.querySelector(".d-editor-input")
|
||||
?.addEventListener("scroll", this._throttledSyncEditorAndPreviewScroll);
|
||||
}
|
||||
|
||||
// Focus on the body unless we have a title
|
||||
|
@ -316,30 +324,47 @@ export default Component.extend(ComposerUpload, {
|
|||
this.set("shouldBuildScrollMap", true);
|
||||
},
|
||||
|
||||
_initInputPreviewSync($input, $preview) {
|
||||
@bind
|
||||
_handleInputInteraction(event) {
|
||||
const preview = this.element.querySelector(".d-editor-preview-wrapper");
|
||||
|
||||
if (!$(preview).is(":visible")) {
|
||||
return;
|
||||
}
|
||||
|
||||
preview.removeEventListener("scroll", this._handleInputOrPreviewScroll);
|
||||
event.target.addEventListener("scroll", this._handleInputOrPreviewScroll);
|
||||
},
|
||||
|
||||
@bind
|
||||
_handleInputOrPreviewScroll(event) {
|
||||
this._syncScroll(
|
||||
this._syncEditorAndPreviewScroll,
|
||||
$(event.target),
|
||||
$(this.element.querySelector(".d-editor-preview-wrapper"))
|
||||
);
|
||||
},
|
||||
|
||||
@bind
|
||||
_handlePreviewInteraction(event) {
|
||||
this.element
|
||||
.querySelector(".d-editor-input")
|
||||
?.removeEventListener("scroll", this._handleInputOrPreviewScroll);
|
||||
|
||||
event.target?.addEventListener("scroll", this._handleInputOrPreviewScroll);
|
||||
},
|
||||
|
||||
_initInputPreviewSync(input, preview) {
|
||||
REBUILD_SCROLL_MAP_EVENTS.forEach((event) => {
|
||||
this.appEvents.on(event, this, this._resetShouldBuildScrollMap);
|
||||
});
|
||||
|
||||
schedule("afterRender", () => {
|
||||
$input.on("touchstart mouseenter", () => {
|
||||
if (!$preview.is(":visible")) {
|
||||
return;
|
||||
}
|
||||
$preview.off("scroll");
|
||||
input?.addEventListener("touchstart", this._handleInputInteraction);
|
||||
input?.addEventListener("mouseenter", this._handleInputInteraction);
|
||||
|
||||
$input.on("scroll", () => {
|
||||
this._syncScroll(this._syncEditorAndPreviewScroll, $input, $preview);
|
||||
});
|
||||
});
|
||||
|
||||
$preview.on("touchstart mouseenter", () => {
|
||||
$input.off("scroll");
|
||||
|
||||
$preview.on("scroll", () => {
|
||||
this._syncScroll(this._syncPreviewAndEditorScroll, $input, $preview);
|
||||
});
|
||||
});
|
||||
preview?.addEventListener("touchstart", this._handlePreviewInteraction);
|
||||
preview?.addEventListener("mouseenter", this._handlePreviewInteraction);
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -353,13 +378,15 @@ export default Component.extend(ComposerUpload, {
|
|||
},
|
||||
|
||||
_teardownInputPreviewSync() {
|
||||
[
|
||||
$(this.element.querySelector(".d-editor-input")),
|
||||
$(this.element.querySelector(".d-editor-preview-wrapper")),
|
||||
].forEach(($element) => {
|
||||
$element.off("mouseenter touchstart");
|
||||
$element.off("scroll");
|
||||
});
|
||||
const input = this.element.querySelector(".d-editor-input");
|
||||
input?.removeEventListener("mouseEnter", this._handleInputInteraction);
|
||||
input?.removeEventListener("touchstart", this._handleInputInteraction);
|
||||
input?.removeEventListener("scroll", this._handleInputOrPreviewScroll);
|
||||
|
||||
const preview = this.element.querySelector(".d-editor-preview-wrapper");
|
||||
preview?.removeEventListener("mouseEnter", this._handlePreviewInteraction);
|
||||
preview?.removeEventListener("touchstart", this._handlePreviewInteraction);
|
||||
preview?.removeEventListener("scroll", this._handleInputOrPreviewScroll);
|
||||
|
||||
REBUILD_SCROLL_MAP_EVENTS.forEach((event) => {
|
||||
this.appEvents.off(event, this, this._resetShouldBuildScrollMap);
|
||||
|
@ -453,6 +480,19 @@ export default Component.extend(ComposerUpload, {
|
|||
return scrollMap;
|
||||
},
|
||||
|
||||
@bind
|
||||
_throttledSyncEditorAndPreviewScroll(event) {
|
||||
const $preview = $(this.element.querySelector(".d-editor-preview-wrapper"));
|
||||
|
||||
throttle(
|
||||
this,
|
||||
this._syncEditorAndPreviewScroll,
|
||||
$(event.target),
|
||||
$preview,
|
||||
20
|
||||
);
|
||||
},
|
||||
|
||||
_syncEditorAndPreviewScroll($input, $preview, scrollMap) {
|
||||
if (this._enableAdvancedEditorPreviewSync()) {
|
||||
let scrollTop;
|
||||
|
@ -599,91 +639,103 @@ export default Component.extend(ComposerUpload, {
|
|||
});
|
||||
},
|
||||
|
||||
_registerImageScaleButtonClick($preview) {
|
||||
$preview.off("click", ".scale-btn").on("click", ".scale-btn", (e) => {
|
||||
@bind
|
||||
_handleImageScaleButtonClick(event) {
|
||||
if (!event.target.classList.contains("scale-btn")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const index = parseInt(
|
||||
event.target.closest(".button-wrapper").dataset.imageIndex,
|
||||
10
|
||||
);
|
||||
|
||||
const scale = event.target.dataset.scale;
|
||||
const matchingPlaceholder = this.get("composer.reply").match(
|
||||
IMAGE_MARKDOWN_REGEX
|
||||
);
|
||||
|
||||
if (matchingPlaceholder) {
|
||||
const match = matchingPlaceholder[index];
|
||||
|
||||
if (match) {
|
||||
const replacement = match.replace(
|
||||
IMAGE_MARKDOWN_REGEX,
|
||||
`![$1|$2, ${scale}%$4]($5)`
|
||||
);
|
||||
|
||||
this.appEvents.trigger(
|
||||
"composer:replace-text",
|
||||
matchingPlaceholder[index],
|
||||
replacement,
|
||||
{ regex: IMAGE_MARKDOWN_REGEX, index }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
return;
|
||||
},
|
||||
|
||||
@bind
|
||||
_handleAltTextInputKeypress(event) {
|
||||
if (!event.target.classList.contains("alt-text-input")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === "[" || event.key === "]") {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
if (event.key === "Enter") {
|
||||
const index = parseInt(
|
||||
$(e.target).closest(".button-wrapper").attr("data-image-index"),
|
||||
$(event.target).closest(".button-wrapper").attr("data-image-index"),
|
||||
10
|
||||
);
|
||||
|
||||
const scale = e.target.attributes["data-scale"].value;
|
||||
const matchingPlaceholder = this.get("composer.reply").match(
|
||||
IMAGE_MARKDOWN_REGEX
|
||||
);
|
||||
const match = matchingPlaceholder[index];
|
||||
const replacement = match.replace(
|
||||
IMAGE_MARKDOWN_REGEX,
|
||||
`![${$(event.target).val()}|$2$3$4]($5)`
|
||||
);
|
||||
|
||||
if (matchingPlaceholder) {
|
||||
const match = matchingPlaceholder[index];
|
||||
this.appEvents.trigger("composer:replace-text", match, replacement);
|
||||
|
||||
if (match) {
|
||||
const replacement = match.replace(
|
||||
IMAGE_MARKDOWN_REGEX,
|
||||
`![$1|$2, ${scale}%$4]($5)`
|
||||
);
|
||||
|
||||
this.appEvents.trigger(
|
||||
"composer:replace-text",
|
||||
matchingPlaceholder[index],
|
||||
replacement,
|
||||
{ regex: IMAGE_MARKDOWN_REGEX, index }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
return;
|
||||
});
|
||||
const parentContainer = $(event.target).closest(
|
||||
".alt-text-readonly-container"
|
||||
);
|
||||
const altText = parentContainer.find(".alt-text");
|
||||
const altTextButton = parentContainer.find(".alt-text-edit-btn");
|
||||
altText.show();
|
||||
altTextButton.show();
|
||||
$(event.target).hide();
|
||||
}
|
||||
},
|
||||
|
||||
_registerImageAltTextButtonClick($preview) {
|
||||
$preview
|
||||
.off("click", ".alt-text-edit-btn")
|
||||
.on("click", ".alt-text-edit-btn", (e) => {
|
||||
const parentContainer = $(e.target).closest(
|
||||
".alt-text-readonly-container"
|
||||
);
|
||||
const altText = parentContainer.find(".alt-text");
|
||||
const correspondingInput = parentContainer.find(".alt-text-input");
|
||||
@bind
|
||||
_handleAltTextEditButtonClick(event) {
|
||||
if (!event.target.classList.contains("alt-text-edit-btn")) {
|
||||
return;
|
||||
}
|
||||
|
||||
$(e.target).hide();
|
||||
altText.hide();
|
||||
correspondingInput.val(altText.text());
|
||||
correspondingInput.show();
|
||||
e.preventDefault();
|
||||
});
|
||||
const parentContainer = $(event.target).closest(
|
||||
".alt-text-readonly-container"
|
||||
);
|
||||
const altText = parentContainer.find(".alt-text");
|
||||
const correspondingInput = parentContainer.find(".alt-text-input");
|
||||
|
||||
$preview
|
||||
.off("keypress", ".alt-text-input")
|
||||
.on("keypress", ".alt-text-input", (e) => {
|
||||
if (e.key === "[" || e.key === "]") {
|
||||
e.preventDefault();
|
||||
}
|
||||
$(event.target).hide();
|
||||
altText.hide();
|
||||
correspondingInput.val(altText.text());
|
||||
correspondingInput.show();
|
||||
event.preventDefault();
|
||||
},
|
||||
|
||||
if (e.key === "Enter") {
|
||||
const index = parseInt(
|
||||
$(e.target).closest(".button-wrapper").attr("data-image-index"),
|
||||
10
|
||||
);
|
||||
const matchingPlaceholder = this.get("composer.reply").match(
|
||||
IMAGE_MARKDOWN_REGEX
|
||||
);
|
||||
const match = matchingPlaceholder[index];
|
||||
const replacement = match.replace(
|
||||
IMAGE_MARKDOWN_REGEX,
|
||||
`![${$(e.target).val()}|$2$3$4]($5)`
|
||||
);
|
||||
|
||||
this.appEvents.trigger("composer:replace-text", match, replacement);
|
||||
|
||||
const parentContainer = $(e.target).closest(
|
||||
".alt-text-readonly-container"
|
||||
);
|
||||
const altText = parentContainer.find(".alt-text");
|
||||
const altTextButton = parentContainer.find(".alt-text-edit-btn");
|
||||
altText.show();
|
||||
altTextButton.show();
|
||||
$(e.target).hide();
|
||||
}
|
||||
});
|
||||
_registerImageAltTextButtonClick(preview) {
|
||||
preview.addEventListener("click", this._handleAltTextEditButtonClick);
|
||||
preview.addEventListener("keypress", this._handleAltTextInputKeypress);
|
||||
},
|
||||
|
||||
@on("willDestroyElement")
|
||||
|
@ -701,6 +753,20 @@ export default Component.extend(ComposerUpload, {
|
|||
if (this._enableAdvancedEditorPreviewSync()) {
|
||||
this._teardownInputPreviewSync();
|
||||
}
|
||||
|
||||
if (!this._enableAdvancedEditorPreviewSync()) {
|
||||
this.element
|
||||
.querySelector(".d-editor-input")
|
||||
?.removeEventListener(
|
||||
"scroll",
|
||||
this._throttledSyncEditorAndPreviewScroll
|
||||
);
|
||||
}
|
||||
|
||||
const preview = this.element.querySelector(".d-editor-preview-wrapper");
|
||||
preview?.removeEventListener("click", this._handleImageScaleButtonClick);
|
||||
preview?.removeEventListener("click", this._handleAltTextEditButtonClick);
|
||||
preview?.removeEventListener("keypress", this._handleAltTextInputKeypress);
|
||||
},
|
||||
|
||||
onExpandPopupMenuOptions(toolbarEvent) {
|
||||
|
@ -863,8 +929,8 @@ export default Component.extend(ComposerUpload, {
|
|||
);
|
||||
}
|
||||
|
||||
this._registerImageScaleButtonClick($preview);
|
||||
this._registerImageAltTextButtonClick($preview);
|
||||
preview.addEventListener("click", this._handleImageScaleButtonClick);
|
||||
this._registerImageAltTextButtonClick(preview);
|
||||
|
||||
this.trigger("previewRefreshed", preview);
|
||||
this.afterRefresh($preview);
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
translateModKey,
|
||||
} from "discourse/lib/utilities";
|
||||
import discourseComputed, {
|
||||
bind,
|
||||
observes,
|
||||
on,
|
||||
} from "discourse-common/utils/decorators";
|
||||
|
@ -286,31 +287,9 @@ export default Component.extend(TextareaTextManipulation, {
|
|||
});
|
||||
|
||||
// disable clicking on links in the preview
|
||||
$(this.element.querySelector(".d-editor-preview")).on(
|
||||
"click.preview",
|
||||
(e) => {
|
||||
if (wantsNewWindow(e)) {
|
||||
return;
|
||||
}
|
||||
const $target = $(e.target);
|
||||
if ($target.is("a.mention")) {
|
||||
this.appEvents.trigger(
|
||||
"click.discourse-preview-user-card-mention",
|
||||
$target
|
||||
);
|
||||
}
|
||||
if ($target.is("a.mention-group")) {
|
||||
this.appEvents.trigger(
|
||||
"click.discourse-preview-group-card-mention-group",
|
||||
$target
|
||||
);
|
||||
}
|
||||
if ($target.is("a")) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
);
|
||||
this.element
|
||||
.querySelector(".d-editor-preview")
|
||||
.addEventListener("click", this._handlePreviewLinkClick);
|
||||
|
||||
if (this.composerEvents) {
|
||||
this.appEvents.on("composer:insert-block", this, "_insertBlock");
|
||||
|
@ -323,6 +302,32 @@ export default Component.extend(TextareaTextManipulation, {
|
|||
}
|
||||
},
|
||||
|
||||
@bind
|
||||
_handlePreviewLinkClick(event) {
|
||||
if (wantsNewWindow(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.target.tagName === "A") {
|
||||
if (event.target.classList.contains("mention")) {
|
||||
this.appEvents.trigger(
|
||||
"click.discourse-preview-user-card-mention",
|
||||
$(event.target)
|
||||
);
|
||||
}
|
||||
|
||||
if (event.target.classList.contains("mention-group")) {
|
||||
this.appEvents.trigger(
|
||||
"click.discourse-preview-group-card-mention-group",
|
||||
$(event.target)
|
||||
);
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
@on("willDestroyElement")
|
||||
_shutDown() {
|
||||
if (this.composerEvents) {
|
||||
|
@ -334,7 +339,9 @@ export default Component.extend(TextareaTextManipulation, {
|
|||
this._itsatrap?.destroy();
|
||||
this._itsatrap = null;
|
||||
|
||||
$(this.element.querySelector(".d-editor-preview")).off("click.preview");
|
||||
this.element
|
||||
.querySelector(".d-editor-preview")
|
||||
?.removeEventListener("click", this._handlePreviewLinkClick);
|
||||
|
||||
this._previewMutationObserver?.disconnect();
|
||||
|
||||
|
|
|
@ -46,8 +46,6 @@ const keys = {
|
|||
let inputTimeout;
|
||||
|
||||
export default function (options) {
|
||||
const autocompletePlugin = this;
|
||||
|
||||
if (this.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
@ -55,13 +53,11 @@ export default function (options) {
|
|||
if (options === "destroy" || options.updateData) {
|
||||
cancel(inputTimeout);
|
||||
|
||||
$(this)
|
||||
.off("keyup.autocomplete")
|
||||
.off("keydown.autocomplete")
|
||||
.off("paste.autocomplete")
|
||||
.off("click.autocomplete");
|
||||
|
||||
$(window).off("click.autocomplete");
|
||||
this[0].removeEventListener("keydown", handleKeyDown);
|
||||
this[0].removeEventListener("keyup", handleKeyUp);
|
||||
this[0].removeEventListener("paste", handlePaste);
|
||||
this[0].removeEventListener("click", closeAutocomplete);
|
||||
window.removeEventListener("click", closeAutocomplete);
|
||||
|
||||
if (options === "destroy") {
|
||||
return;
|
||||
|
@ -116,8 +112,12 @@ export default function (options) {
|
|||
const isInput = me[0].tagName === "INPUT" && !options.treatAsTextarea;
|
||||
let inputSelectedItems = [];
|
||||
|
||||
function handlePaste() {
|
||||
later(() => me.trigger("keydown"), 50);
|
||||
}
|
||||
|
||||
function closeAutocomplete() {
|
||||
_autoCompletePopper && _autoCompletePopper.destroy();
|
||||
_autoCompletePopper?.destroy();
|
||||
|
||||
if (div) {
|
||||
div.hide().remove();
|
||||
|
@ -276,7 +276,7 @@ export default function (options) {
|
|||
this.val("");
|
||||
completeStart = 0;
|
||||
wrap.click(function () {
|
||||
autocompletePlugin.focus();
|
||||
this.focus();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
@ -447,24 +447,17 @@ export default function (options) {
|
|||
closeAutocomplete();
|
||||
});
|
||||
|
||||
$(window).on("click.autocomplete", () => closeAutocomplete());
|
||||
$(this).on("click.autocomplete", () => closeAutocomplete());
|
||||
|
||||
$(this).on("paste.autocomplete", () => {
|
||||
later(() => me.trigger("keydown"), 50);
|
||||
});
|
||||
|
||||
function checkTriggerRule(opts) {
|
||||
return options.triggerRule ? options.triggerRule(me[0], opts) : true;
|
||||
}
|
||||
|
||||
$(this).on("keyup.autocomplete", function (e) {
|
||||
function handleKeyUp(e) {
|
||||
if (options.debounced) {
|
||||
discourseDebounce(this, performAutocomplete, e, INPUT_DELAY);
|
||||
} else {
|
||||
performAutocomplete(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function performAutocomplete(e) {
|
||||
if ([keys.esc, keys.enter].indexOf(e.which) !== -1) {
|
||||
|
@ -503,7 +496,7 @@ export default function (options) {
|
|||
}
|
||||
}
|
||||
|
||||
$(this).on("keydown.autocomplete", function (e) {
|
||||
function handleKeyDown(e) {
|
||||
let c, i, initial, prev, prevIsGood, stopFound, term, total, userToComplete;
|
||||
let cp;
|
||||
|
||||
|
@ -602,7 +595,9 @@ export default function (options) {
|
|||
// We're cancelling it, really.
|
||||
return true;
|
||||
}
|
||||
|
||||
e.stopImmediatePropagation();
|
||||
e.preventDefault();
|
||||
return false;
|
||||
case keys.upArrow:
|
||||
selectedOption = selectedOption - 1;
|
||||
|
@ -652,7 +647,13 @@ export default function (options) {
|
|||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener("click", closeAutocomplete);
|
||||
this[0].addEventListener("click", closeAutocomplete);
|
||||
this[0].addEventListener("paste", handlePaste);
|
||||
this[0].addEventListener("keyup", handleKeyUp);
|
||||
this[0].addEventListener("keydown", handleKeyDown);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { bind } from "discourse-common/utils/decorators";
|
||||
import Mixin from "@ember/object/mixin";
|
||||
import toMarkdown from "discourse/lib/to-markdown";
|
||||
import { isTesting } from "discourse-common/config/environment";
|
||||
|
@ -6,7 +7,6 @@ import {
|
|||
determinePostReplaceSelection,
|
||||
safariHacksDisabled,
|
||||
} from "discourse/lib/utilities";
|
||||
import { bind } from "discourse-common/utils/decorators";
|
||||
import { next, schedule } from "@ember/runloop";
|
||||
|
||||
const isInside = (text, regex) => {
|
||||
|
|
Loading…
Reference in New Issue