DEV: Move text area surround code out of d-editor (#15950)
This commit moves _getMultilineContents and _applySurround into TextareaTextManipulation, so other text area components using that mixin can benefit from them (such as the chat composer). It also creates a public function wrapper for many TextareaTextManipulation functions that should not have underscore prefixes because they are used outside the file. Will make follow-up PRs for each plugin/theme using those functions then a final follow-up core PR to fix these up.
This commit is contained in:
parent
c92e62a271
commit
6a5ef27eaa
|
@ -35,29 +35,15 @@ import { siteDir } from "discourse/lib/text-direction";
|
|||
import { translations } from "pretty-text/emoji/data";
|
||||
import { wantsNewWindow } from "discourse/lib/intercept-click";
|
||||
import { action } from "@ember/object";
|
||||
import TextareaTextManipulation from "discourse/mixins/textarea-text-manipulation";
|
||||
|
||||
// Our head can be a static string or a function that returns a string
|
||||
// based on input (like for numbered lists).
|
||||
function getHead(head, prev) {
|
||||
if (typeof head === "string") {
|
||||
return [head, head.length];
|
||||
} else {
|
||||
return getHead(head(prev));
|
||||
}
|
||||
}
|
||||
import TextareaTextManipulation, {
|
||||
getHead,
|
||||
} from "discourse/mixins/textarea-text-manipulation";
|
||||
|
||||
function getButtonLabel(labelKey, defaultLabel) {
|
||||
// use the Font Awesome icon if the label matches the default
|
||||
return I18n.t(labelKey) === defaultLabel ? null : labelKey;
|
||||
}
|
||||
|
||||
const OP = {
|
||||
NONE: 0,
|
||||
REMOVED: 1,
|
||||
ADDED: 2,
|
||||
};
|
||||
|
||||
const FOUR_SPACES_INDENT = "4-spaces-indent";
|
||||
|
||||
let _createCallbacks = [];
|
||||
|
@ -285,8 +271,8 @@ export default Component.extend(TextareaTextManipulation, {
|
|||
});
|
||||
});
|
||||
|
||||
this._itsatrap.bind("tab", () => this._indentSelection("right"));
|
||||
this._itsatrap.bind("shift+tab", () => this._indentSelection("left"));
|
||||
this._itsatrap.bind("tab", () => this.indentSelection("right"));
|
||||
this._itsatrap.bind("shift+tab", () => this.indentSelection("left"));
|
||||
|
||||
// disable clicking on links in the preview
|
||||
this.element
|
||||
|
@ -294,13 +280,13 @@ export default Component.extend(TextareaTextManipulation, {
|
|||
.addEventListener("click", this._handlePreviewLinkClick);
|
||||
|
||||
if (this.composerEvents) {
|
||||
this.appEvents.on("composer:insert-block", this, "_insertBlock");
|
||||
this.appEvents.on("composer:insert-text", this, "_insertText");
|
||||
this.appEvents.on("composer:replace-text", this, "_replaceText");
|
||||
this.appEvents.on("composer:insert-block", this, "insertBlock");
|
||||
this.appEvents.on("composer:insert-text", this, "insertText");
|
||||
this.appEvents.on("composer:replace-text", this, "replaceText");
|
||||
this.appEvents.on(
|
||||
"composer:indent-selected-text",
|
||||
this,
|
||||
"_indentSelection"
|
||||
"indentSelection"
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -338,13 +324,13 @@ export default Component.extend(TextareaTextManipulation, {
|
|||
@on("willDestroyElement")
|
||||
_shutDown() {
|
||||
if (this.composerEvents) {
|
||||
this.appEvents.off("composer:insert-block", this, "_insertBlock");
|
||||
this.appEvents.off("composer:insert-text", this, "_insertText");
|
||||
this.appEvents.off("composer:replace-text", this, "_replaceText");
|
||||
this.appEvents.off("composer:insert-block", this, "insertBlock");
|
||||
this.appEvents.off("composer:insert-text", this, "insertText");
|
||||
this.appEvents.off("composer:replace-text", this, "replaceText");
|
||||
this.appEvents.off(
|
||||
"composer:indent-selected-text",
|
||||
this,
|
||||
"_indentSelection"
|
||||
"indentSelection"
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -477,7 +463,7 @@ export default Component.extend(TextareaTextManipulation, {
|
|||
key: "#",
|
||||
afterComplete: (value) => {
|
||||
this.set("value", value);
|
||||
schedule("afterRender", this, this._focusTextArea);
|
||||
schedule("afterRender", this, this.focusTextArea);
|
||||
},
|
||||
transformComplete: (obj) => {
|
||||
return obj.text;
|
||||
|
@ -504,7 +490,7 @@ export default Component.extend(TextareaTextManipulation, {
|
|||
key: ":",
|
||||
afterComplete: (text) => {
|
||||
this.set("value", text);
|
||||
schedule("afterRender", this, this._focusTextArea);
|
||||
schedule("afterRender", this, this.focusTextArea);
|
||||
},
|
||||
|
||||
onKeyUp: (text, cp) => {
|
||||
|
@ -617,117 +603,9 @@ export default Component.extend(TextareaTextManipulation, {
|
|||
});
|
||||
},
|
||||
|
||||
// perform the same operation over many lines of text
|
||||
_getMultilineContents(lines, head, hval, hlen, tail, tlen, opts) {
|
||||
let operation = OP.NONE;
|
||||
|
||||
const applyEmptyLines = opts && opts.applyEmptyLines;
|
||||
|
||||
return lines
|
||||
.map((l) => {
|
||||
if (!applyEmptyLines && l.length === 0) {
|
||||
return l;
|
||||
}
|
||||
|
||||
if (
|
||||
operation !== OP.ADDED &&
|
||||
((l.slice(0, hlen) === hval && tlen === 0) ||
|
||||
(tail.length && l.slice(-tlen) === tail))
|
||||
) {
|
||||
operation = OP.REMOVED;
|
||||
if (tlen === 0) {
|
||||
const result = l.slice(hlen);
|
||||
[hval, hlen] = getHead(head, hval);
|
||||
return result;
|
||||
} else if (l.slice(-tlen) === tail) {
|
||||
const result = l.slice(hlen, -tlen);
|
||||
[hval, hlen] = getHead(head, hval);
|
||||
return result;
|
||||
}
|
||||
} else if (operation === OP.NONE) {
|
||||
operation = OP.ADDED;
|
||||
} else if (operation === OP.REMOVED) {
|
||||
return l;
|
||||
}
|
||||
|
||||
const result = `${hval}${l}${tail}`;
|
||||
[hval, hlen] = getHead(head, hval);
|
||||
return result;
|
||||
})
|
||||
.join("\n");
|
||||
},
|
||||
|
||||
_applySurround(sel, head, tail, exampleKey, opts) {
|
||||
const pre = sel.pre;
|
||||
const post = sel.post;
|
||||
|
||||
const tlen = tail.length;
|
||||
if (sel.start === sel.end) {
|
||||
if (tlen === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [hval, hlen] = getHead(head);
|
||||
const example = I18n.t(`composer.${exampleKey}`);
|
||||
this.set("value", `${pre}${hval}${example}${tail}${post}`);
|
||||
this._selectText(pre.length + hlen, example.length);
|
||||
} else if (opts && !opts.multiline) {
|
||||
let [hval, hlen] = getHead(head);
|
||||
|
||||
if (opts.useBlockMode && sel.value.split("\n").length > 1) {
|
||||
hval += "\n";
|
||||
hlen += 1;
|
||||
tail = `\n${tail}`;
|
||||
}
|
||||
|
||||
if (pre.slice(-hlen) === hval && post.slice(0, tail.length) === tail) {
|
||||
this.set(
|
||||
"value",
|
||||
`${pre.slice(0, -hlen)}${sel.value}${post.slice(tail.length)}`
|
||||
);
|
||||
this._selectText(sel.start - hlen, sel.value.length);
|
||||
} else {
|
||||
this.set("value", `${pre}${hval}${sel.value}${tail}${post}`);
|
||||
this._selectText(sel.start + hlen, sel.value.length);
|
||||
}
|
||||
} else {
|
||||
const lines = sel.value.split("\n");
|
||||
|
||||
let [hval, hlen] = getHead(head);
|
||||
if (
|
||||
lines.length === 1 &&
|
||||
pre.slice(-tlen) === tail &&
|
||||
post.slice(0, hlen) === hval
|
||||
) {
|
||||
this.set(
|
||||
"value",
|
||||
`${pre.slice(0, -hlen)}${sel.value}${post.slice(tlen)}`
|
||||
);
|
||||
this._selectText(sel.start - hlen, sel.value.length);
|
||||
} else {
|
||||
const contents = this._getMultilineContents(
|
||||
lines,
|
||||
head,
|
||||
hval,
|
||||
hlen,
|
||||
tail,
|
||||
tlen,
|
||||
opts
|
||||
);
|
||||
|
||||
this.set("value", `${pre}${contents}${post}`);
|
||||
if (lines.length === 1 && tlen > 0) {
|
||||
this._selectText(sel.start + hlen, sel.value.length);
|
||||
} else {
|
||||
this._selectText(sel.start, contents.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_applyList(sel, head, exampleKey, opts) {
|
||||
if (sel.value.indexOf("\n") !== -1) {
|
||||
this._applySurround(sel, head, "", exampleKey, opts);
|
||||
this.applySurround(sel, head, "", exampleKey, opts);
|
||||
} else {
|
||||
const [hval, hlen] = getHead(head);
|
||||
if (sel.start === sel.end) {
|
||||
|
@ -745,7 +623,7 @@ export default Component.extend(TextareaTextManipulation, {
|
|||
const post = trimmedPost.length ? `\n\n${trimmedPost}` : trimmedPost;
|
||||
|
||||
this.set("value", `${preLines}${number}${post}`);
|
||||
this._selectText(preLines.length, number.length);
|
||||
this.selectText(preLines.length, number.length);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -811,16 +689,16 @@ export default Component.extend(TextareaTextManipulation, {
|
|||
return;
|
||||
}
|
||||
|
||||
const selected = this._getSelected(button.trimLeading);
|
||||
const selected = this.getSelected(button.trimLeading);
|
||||
const toolbarEvent = {
|
||||
selected,
|
||||
selectText: (from, length) =>
|
||||
this._selectText(from, length, { scroll: false }),
|
||||
this.selectText(from, length, { scroll: false }),
|
||||
applySurround: (head, tail, exampleKey, opts) =>
|
||||
this._applySurround(selected, head, tail, exampleKey, opts),
|
||||
this.applySurround(selected, head, tail, exampleKey, opts),
|
||||
applyList: (head, exampleKey, opts) =>
|
||||
this._applyList(selected, head, exampleKey, opts),
|
||||
addText: (text) => this._addText(selected, text),
|
||||
addText: (text) => this.addText(selected, text),
|
||||
getText: () => this.value,
|
||||
toggleDirection: () => this._toggleDirection(),
|
||||
};
|
||||
|
@ -855,7 +733,7 @@ export default Component.extend(TextareaTextManipulation, {
|
|||
return;
|
||||
}
|
||||
|
||||
const sel = this._getSelected("", { lineVal: true });
|
||||
const sel = this.getSelected("", { lineVal: true });
|
||||
const selValue = sel.value;
|
||||
const hasNewLine = selValue.indexOf("\n") !== -1;
|
||||
const isBlankLine = sel.lineVal.trim().length === 0;
|
||||
|
@ -867,25 +745,20 @@ export default Component.extend(TextareaTextManipulation, {
|
|||
if (isFourSpacesIndent) {
|
||||
const example = I18n.t(`composer.code_text`);
|
||||
this.set("value", `${sel.pre} ${example}${sel.post}`);
|
||||
return this._selectText(sel.pre.length + 4, example.length);
|
||||
return this.selectText(sel.pre.length + 4, example.length);
|
||||
} else {
|
||||
return this._applySurround(
|
||||
sel,
|
||||
"```\n",
|
||||
"\n```",
|
||||
"paste_code_text"
|
||||
);
|
||||
return this.applySurround(sel, "```\n", "\n```", "paste_code_text");
|
||||
}
|
||||
} else {
|
||||
return this._applySurround(sel, "`", "`", "code_title");
|
||||
return this.applySurround(sel, "`", "`", "code_title");
|
||||
}
|
||||
} else {
|
||||
if (isFourSpacesIndent) {
|
||||
return this._applySurround(sel, " ", "", "code_text");
|
||||
return this.applySurround(sel, " ", "", "code_text");
|
||||
} else {
|
||||
const preNewline = sel.pre[-1] !== "\n" && sel.pre !== "" ? "\n" : "";
|
||||
const postNewline = sel.post[0] !== "\n" ? "\n" : "";
|
||||
return this._addText(
|
||||
return this.addText(
|
||||
sel,
|
||||
`${preNewline}\`\`\`\n${sel.value}\n\`\`\`${postNewline}`
|
||||
);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { bind } from "discourse-common/utils/decorators";
|
||||
import I18n from "I18n";
|
||||
import Mixin from "@ember/object/mixin";
|
||||
import { generateLinkifyFunction } from "discourse/lib/text";
|
||||
import toMarkdown from "discourse/lib/to-markdown";
|
||||
|
@ -14,6 +15,22 @@ import { next, schedule } from "@ember/runloop";
|
|||
const INDENT_DIRECTION_LEFT = "left";
|
||||
const INDENT_DIRECTION_RIGHT = "right";
|
||||
|
||||
const OP = {
|
||||
NONE: 0,
|
||||
REMOVED: 1,
|
||||
ADDED: 2,
|
||||
};
|
||||
|
||||
// Our head can be a static string or a function that returns a string
|
||||
// based on input (like for numbered lists).
|
||||
export function getHead(head, prev) {
|
||||
if (typeof head === "string") {
|
||||
return [head, head.length];
|
||||
} else {
|
||||
return getHead(head(prev));
|
||||
}
|
||||
}
|
||||
|
||||
export default Mixin.create({
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
|
@ -24,6 +41,13 @@ export default Mixin.create({
|
|||
},
|
||||
|
||||
// ensures textarea scroll position is correct
|
||||
//
|
||||
// TODO (martin) clean up this indirection, functions used outside this
|
||||
// file should not be prefixed with lowercase
|
||||
focusTextArea() {
|
||||
this._focusTextArea();
|
||||
},
|
||||
|
||||
_focusTextArea() {
|
||||
if (!this.element || this.isDestroying || this.isDestroyed) {
|
||||
return;
|
||||
|
@ -37,12 +61,30 @@ export default Mixin.create({
|
|||
this._textarea.focus();
|
||||
},
|
||||
|
||||
// TODO (martin) clean up this indirection, functions used outside this
|
||||
// file should not be prefixed with lowercase
|
||||
insertBlock(text) {
|
||||
this._insertBlock(text);
|
||||
},
|
||||
|
||||
_insertBlock(text) {
|
||||
this._addBlock(this._getSelected(), text);
|
||||
this._addBlock(this.getSelected(), text);
|
||||
},
|
||||
|
||||
// TODO (martin) clean up this indirection, functions used outside this
|
||||
// file should not be prefixed with lowercase
|
||||
insertText(text, options) {
|
||||
this._insertText(text, options);
|
||||
},
|
||||
|
||||
_insertText(text, options) {
|
||||
this._addText(this._getSelected(), text, options);
|
||||
this._addText(this.getSelected(), text, options);
|
||||
},
|
||||
|
||||
// TODO (martin) clean up this indirection, functions used outside this
|
||||
// file should not be prefixed with lowercase
|
||||
getSelected(trimLeading, opts) {
|
||||
return this._getSelected(trimLeading, opts);
|
||||
},
|
||||
|
||||
_getSelected(trimLeading, opts) {
|
||||
|
@ -80,6 +122,12 @@ export default Mixin.create({
|
|||
}
|
||||
},
|
||||
|
||||
// TODO (martin) clean up this indirection, functions used outside this
|
||||
// file should not be prefixed with lowercase
|
||||
selectText(from, length, opts = { scroll: true }) {
|
||||
this._selectText(from, length, opts);
|
||||
},
|
||||
|
||||
_selectText(from, length, opts = { scroll: true }) {
|
||||
next(() => {
|
||||
if (!this.element) {
|
||||
|
@ -99,6 +147,12 @@ export default Mixin.create({
|
|||
});
|
||||
},
|
||||
|
||||
// TODO (martin) clean up this indirection, functions used outside this
|
||||
// file should not be prefixed with lowercase
|
||||
replaceText(oldVal, newVal, opts = {}) {
|
||||
this._replaceText(oldVal, newVal, opts);
|
||||
},
|
||||
|
||||
_replaceText(oldVal, newVal, opts = {}) {
|
||||
const val = this.value;
|
||||
const needleStart = val.indexOf(oldVal);
|
||||
|
@ -135,13 +189,127 @@ export default Mixin.create({
|
|||
!opts.skipNewSelection
|
||||
) {
|
||||
// Restore cursor.
|
||||
this._selectText(
|
||||
this.selectText(
|
||||
newSelection.start,
|
||||
newSelection.end - newSelection.start
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
// TODO (martin) clean up this indirection, functions used outside this
|
||||
// file should not be prefixed with lowercase
|
||||
applySurround(sel, head, tail, exampleKey, opts) {
|
||||
this._applySurround(sel, head, tail, exampleKey, opts);
|
||||
},
|
||||
|
||||
_applySurround(sel, head, tail, exampleKey, opts) {
|
||||
const pre = sel.pre;
|
||||
const post = sel.post;
|
||||
|
||||
const tlen = tail.length;
|
||||
if (sel.start === sel.end) {
|
||||
if (tlen === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [hval, hlen] = getHead(head);
|
||||
const example = I18n.t(`composer.${exampleKey}`);
|
||||
this.set("value", `${pre}${hval}${example}${tail}${post}`);
|
||||
this.selectText(pre.length + hlen, example.length);
|
||||
} else if (opts && !opts.multiline) {
|
||||
let [hval, hlen] = getHead(head);
|
||||
|
||||
if (opts.useBlockMode && sel.value.split("\n").length > 1) {
|
||||
hval += "\n";
|
||||
hlen += 1;
|
||||
tail = `\n${tail}`;
|
||||
}
|
||||
|
||||
if (pre.slice(-hlen) === hval && post.slice(0, tail.length) === tail) {
|
||||
this.set(
|
||||
"value",
|
||||
`${pre.slice(0, -hlen)}${sel.value}${post.slice(tail.length)}`
|
||||
);
|
||||
this.selectText(sel.start - hlen, sel.value.length);
|
||||
} else {
|
||||
this.set("value", `${pre}${hval}${sel.value}${tail}${post}`);
|
||||
this.selectText(sel.start + hlen, sel.value.length);
|
||||
}
|
||||
} else {
|
||||
const lines = sel.value.split("\n");
|
||||
|
||||
let [hval, hlen] = getHead(head);
|
||||
if (
|
||||
lines.length === 1 &&
|
||||
pre.slice(-tlen) === tail &&
|
||||
post.slice(0, hlen) === hval
|
||||
) {
|
||||
this.set(
|
||||
"value",
|
||||
`${pre.slice(0, -hlen)}${sel.value}${post.slice(tlen)}`
|
||||
);
|
||||
this.selectText(sel.start - hlen, sel.value.length);
|
||||
} else {
|
||||
const contents = this._getMultilineContents(
|
||||
lines,
|
||||
head,
|
||||
hval,
|
||||
hlen,
|
||||
tail,
|
||||
tlen,
|
||||
opts
|
||||
);
|
||||
|
||||
this.set("value", `${pre}${contents}${post}`);
|
||||
if (lines.length === 1 && tlen > 0) {
|
||||
this.selectText(sel.start + hlen, sel.value.length);
|
||||
} else {
|
||||
this.selectText(sel.start, contents.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// perform the same operation over many lines of text
|
||||
_getMultilineContents(lines, head, hval, hlen, tail, tlen, opts) {
|
||||
let operation = OP.NONE;
|
||||
|
||||
const applyEmptyLines = opts && opts.applyEmptyLines;
|
||||
|
||||
return lines
|
||||
.map((l) => {
|
||||
if (!applyEmptyLines && l.length === 0) {
|
||||
return l;
|
||||
}
|
||||
|
||||
if (
|
||||
operation !== OP.ADDED &&
|
||||
((l.slice(0, hlen) === hval && tlen === 0) ||
|
||||
(tail.length && l.slice(-tlen) === tail))
|
||||
) {
|
||||
operation = OP.REMOVED;
|
||||
if (tlen === 0) {
|
||||
const result = l.slice(hlen);
|
||||
[hval, hlen] = getHead(head, hval);
|
||||
return result;
|
||||
} else if (l.slice(-tlen) === tail) {
|
||||
const result = l.slice(hlen, -tlen);
|
||||
[hval, hlen] = getHead(head, hval);
|
||||
return result;
|
||||
}
|
||||
} else if (operation === OP.NONE) {
|
||||
operation = OP.ADDED;
|
||||
} else if (operation === OP.REMOVED) {
|
||||
return l;
|
||||
}
|
||||
|
||||
const result = `${hval}${l}${tail}`;
|
||||
[hval, hlen] = getHead(head, hval);
|
||||
return result;
|
||||
})
|
||||
.join("\n");
|
||||
},
|
||||
|
||||
_addBlock(sel, text) {
|
||||
text = (text || "").trim();
|
||||
if (text.length === 0) {
|
||||
|
@ -172,6 +340,12 @@ export default Mixin.create({
|
|||
schedule("afterRender", this, this._focusTextArea);
|
||||
},
|
||||
|
||||
// TODO (martin) clean up this indirection, functions used outside this
|
||||
// file should not be prefixed with lowercase
|
||||
addText(sel, text, options) {
|
||||
this._addText(sel, text, options);
|
||||
},
|
||||
|
||||
_addText(sel, text, options) {
|
||||
if (options && options.ensureSpace) {
|
||||
if ((sel.pre + "").length > 0) {
|
||||
|
@ -196,6 +370,12 @@ export default Mixin.create({
|
|||
this._focusTextArea();
|
||||
},
|
||||
|
||||
// TODO (martin) clean up this indirection, functions used outside this
|
||||
// file should not be prefixed with lowercase
|
||||
extractTable(text) {
|
||||
return this._extractTable(text);
|
||||
},
|
||||
|
||||
_extractTable(text) {
|
||||
if (text.endsWith("\n")) {
|
||||
text = text.substring(0, text.length - 1);
|
||||
|
@ -233,6 +413,12 @@ export default Mixin.create({
|
|||
return null;
|
||||
},
|
||||
|
||||
// TODO (martin) clean up this indirection, functions used outside this
|
||||
// file should not be prefixed with lowercase
|
||||
isInside(text, regex) {
|
||||
return this._isInside(text, regex);
|
||||
},
|
||||
|
||||
_isInside(text, regex) {
|
||||
const matches = text.match(regex);
|
||||
return matches && matches.length % 2;
|
||||
|
@ -254,7 +440,7 @@ export default Mixin.create({
|
|||
let html = clipboard.getData("text/html");
|
||||
let handled = false;
|
||||
|
||||
const selected = this._getSelected(null, { lineVal: true });
|
||||
const selected = this.getSelected(null, { lineVal: true });
|
||||
const { pre, value: selectedValue, lineVal } = selected;
|
||||
const isInlinePasting = pre.match(/[^\n]$/);
|
||||
const isCodeBlock = this._isInside(pre, /(^|\n)```/g);
|
||||
|
@ -349,12 +535,12 @@ export default Mixin.create({
|
|||
},
|
||||
|
||||
@bind
|
||||
_indentSelection(direction) {
|
||||
indentSelection(direction) {
|
||||
if (![INDENT_DIRECTION_LEFT, INDENT_DIRECTION_RIGHT].includes(direction)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selected = this._getSelected(null, { lineVal: true });
|
||||
const selected = this.getSelected(null, { lineVal: true });
|
||||
const { lineVal } = selected;
|
||||
let value = selected.value;
|
||||
|
||||
|
@ -414,14 +600,14 @@ export default Mixin.create({
|
|||
.join("\n");
|
||||
|
||||
if (newValue.trim() !== "") {
|
||||
this._replaceText(value, newValue, { skipNewSelection: true });
|
||||
this._selectText(this.value.indexOf(newValue), newValue.length);
|
||||
this.replaceText(value, newValue, { skipNewSelection: true });
|
||||
this.selectText(this.value.indexOf(newValue), newValue.length);
|
||||
}
|
||||
},
|
||||
|
||||
@action
|
||||
emojiSelected(code) {
|
||||
let selected = this._getSelected();
|
||||
let selected = this.getSelected();
|
||||
const captures = selected.pre.match(/\B:(\w*)$/);
|
||||
|
||||
if (isEmpty(captures)) {
|
||||
|
|
Loading…
Reference in New Issue