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