Files
Jarek RadoszandGitHub f1e97e776b DEV: Don't nest deferring calls (#30449)
Each case simplified:

`next(() => later(() => ...))` -> "wait 0 ms then wait X ms"
`next(() => debounce(() => ...))` -> "wait 0 ms then wait X ms
(debounced)"
`next(() => scheduleAfter("render", ...))` -> "in the next (empty) run
loop, do the thing (after a no-op render step)"
2025-01-21 11:24:33 +01:00

129 lines
3.1 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { registerDestructor } from "@ember/destroyable";
import EmberObject from "@ember/object";
import { setOwner } from "@ember/owner";
import { next, schedule } from "@ember/runloop";
import { service } from "@ember/service";
import { bind } from "discourse/lib/decorators";
import TextareaTextManipulation from "discourse/lib/textarea-text-manipulation";
// This class sole purpose is to provide a way to interact with the textarea
// using the existing TextareaTextManipulation mixin without using it directly
// in the composer component. It will make future migration easier.
export default class TextareaInteractor extends EmberObject {
@service capabilities;
@service site;
@service siteSettings;
textManipulation;
constructor(owner, textarea) {
super(...arguments);
setOwner(this, owner);
this.textarea = textarea;
this.element = textarea;
this.textManipulation = new TextareaTextManipulation(owner, {
textarea,
// we don't need app events
eventPrefix: null,
});
// paste is using old native ember events defined on composer
this.textarea.addEventListener("paste", this.textManipulation.paste);
registerDestructor(this, (instance) => instance.teardown());
}
teardown() {
this.textarea.removeEventListener("paste", this.textManipulation.paste);
}
set value(value) {
this.textarea.value = value;
const event = new Event("input", {
bubbles: true,
cancelable: true,
});
this.textarea.dispatchEvent(event);
}
@bind
blur() {
next(() => {
this.textarea.blur();
});
}
@bind
focus(opts = { ensureAtEnd: false, refreshHeight: true, addText: null }) {
next(() => {
if (opts.refreshHeight) {
this.refreshHeight();
}
if (opts.ensureAtEnd) {
this.ensureCaretAtEnd();
}
if (this.capabilities.isIpadOS || this.site.mobileView) {
return;
}
if (opts.addText) {
this.textManipulation.addText(
this.textManipulation.getSelected(),
opts.addText
);
}
this.textManipulation.blurAndFocus();
});
}
@bind
ensureCaretAtEnd() {
schedule("afterRender", () => {
this.textarea.setSelectionRange(
this.textarea.value.length,
this.textarea.value.length
);
});
}
@bind
refreshHeight() {
schedule("afterRender", () => {
// this is a quirk which forces us to `auto` first or textarea
// won't resize
this.textarea.style.height = "auto";
// +1 is to workaround a rounding error visible on electron
// causing scrollbars to show when they shouldn’t
this.textarea.style.height = this.textarea.scrollHeight + 1 + "px";
});
}
@bind
getSelected() {
return this.textManipulation.getSelected(...arguments);
}
@bind
applySurround() {
return this.textManipulation.applySurround(...arguments);
}
@bind
addText() {
return this.textManipulation.addText(...arguments);
}
@bind
isInside() {
return this.textManipulation.isInside(...arguments);
}
emojiSelected(code) {
this.textManipulation.emojiSelected(code);
}
}