mirror of
https://github.com/discourse/discourse.git
synced 2024-12-30 15:23:48 +08:00
DEV: refactor textarea from autocomplete (#29988)
Extracts the dependency we had on specifics of a textarea in our Autocomplete, this approach uses a TextareaTextManipulation, particularly the value getter, getCaretPosition, getCaretCoords, replaceText, and inCodeBlock.
This commit is contained in:
parent
e37952c9db
commit
4a5a499d94
|
@ -306,8 +306,7 @@ export default class DEditor extends Component {
|
||||||
this.site.hashtag_configurations["topic-composer"],
|
this.site.hashtag_configurations["topic-composer"],
|
||||||
this.siteSettings,
|
this.siteSettings,
|
||||||
{
|
{
|
||||||
afterComplete: (value) => {
|
afterComplete: () => {
|
||||||
this.set("value", value);
|
|
||||||
schedule(
|
schedule(
|
||||||
"afterRender",
|
"afterRender",
|
||||||
this.textManipulation,
|
this.textManipulation,
|
||||||
|
@ -327,8 +326,7 @@ export default class DEditor extends Component {
|
||||||
this.textManipulation.autocomplete({
|
this.textManipulation.autocomplete({
|
||||||
template: findRawTemplate("emoji-selector-autocomplete"),
|
template: findRawTemplate("emoji-selector-autocomplete"),
|
||||||
key: ":",
|
key: ":",
|
||||||
afterComplete: (text) => {
|
afterComplete: () => {
|
||||||
this.set("value", text);
|
|
||||||
schedule(
|
schedule(
|
||||||
"afterRender",
|
"afterRender",
|
||||||
this.textManipulation,
|
this.textManipulation,
|
||||||
|
@ -466,9 +464,7 @@ export default class DEditor extends Component {
|
||||||
onRender: (options) => renderUserStatusHtml(options),
|
onRender: (options) => renderUserStatusHtml(options),
|
||||||
key: "@",
|
key: "@",
|
||||||
transformComplete: (v) => v.username || v.name,
|
transformComplete: (v) => v.username || v.name,
|
||||||
afterComplete: (value) => {
|
afterComplete: () => {
|
||||||
this.set("value", value);
|
|
||||||
|
|
||||||
schedule(
|
schedule(
|
||||||
"afterRender",
|
"afterRender",
|
||||||
this.textManipulation,
|
this.textManipulation,
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { cancel } from "@ember/runloop";
|
||||||
import { createPopper } from "@popperjs/core";
|
import { createPopper } from "@popperjs/core";
|
||||||
import $ from "jquery";
|
import $ from "jquery";
|
||||||
import { isDocumentRTL } from "discourse/lib/text-direction";
|
import { isDocumentRTL } from "discourse/lib/text-direction";
|
||||||
import { caretPosition, setCaretPosition } from "discourse/lib/utilities";
|
import { TextareaAutocompleteHandler } from "discourse/lib/textarea-text-manipulation";
|
||||||
import Site from "discourse/models/site";
|
import Site from "discourse/models/site";
|
||||||
import { INPUT_DELAY } from "discourse-common/config/environment";
|
import { INPUT_DELAY } from "discourse-common/config/environment";
|
||||||
import discourseDebounce from "discourse-common/lib/debounce";
|
import discourseDebounce from "discourse-common/lib/debounce";
|
||||||
|
@ -114,6 +114,8 @@ export default function (options) {
|
||||||
const isInput = me[0].tagName === "INPUT" && !options.treatAsTextarea;
|
const isInput = me[0].tagName === "INPUT" && !options.treatAsTextarea;
|
||||||
let inputSelectedItems = [];
|
let inputSelectedItems = [];
|
||||||
|
|
||||||
|
options.textHandler ??= new TextareaAutocompleteHandler(me[0]);
|
||||||
|
|
||||||
function handlePaste() {
|
function handlePaste() {
|
||||||
discourseLater(() => me.trigger("keydown"), 50);
|
discourseLater(() => me.trigger("keydown"), 50);
|
||||||
}
|
}
|
||||||
|
@ -216,8 +218,6 @@ export default function (options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
let completeTerm = async function (term, event) {
|
let completeTerm = async function (term, event) {
|
||||||
let completeEnd = null;
|
|
||||||
|
|
||||||
if (term) {
|
if (term) {
|
||||||
if (isInput) {
|
if (isInput) {
|
||||||
me.val("");
|
me.val("");
|
||||||
|
@ -231,15 +231,13 @@ export default function (options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (term) {
|
if (term) {
|
||||||
let text = me.val();
|
|
||||||
|
|
||||||
// After completion is done our position for completeStart may have
|
// After completion is done our position for completeStart may have
|
||||||
// drifted. This can happen if the TEXTAREA changed out-of-band between
|
// drifted. This can happen if the TEXTAREA changed out-of-band between
|
||||||
// the time autocomplete was first displayed and the time of completion
|
// the time autocomplete was first displayed and the time of completion
|
||||||
// Specifically this may happen due to uploads which inject a placeholder
|
// Specifically this may happen due to uploads which inject a placeholder
|
||||||
// which is later replaced with a different length string.
|
// which is later replaced with a different length string.
|
||||||
let pos = await guessCompletePosition({ completeTerm: true });
|
let pos = await guessCompletePosition({ completeTerm: true });
|
||||||
|
let completeEnd = null;
|
||||||
if (
|
if (
|
||||||
pos.completeStart !== undefined &&
|
pos.completeStart !== undefined &&
|
||||||
pos.completeEnd !== undefined
|
pos.completeEnd !== undefined
|
||||||
|
@ -247,31 +245,18 @@ export default function (options) {
|
||||||
completeStart = pos.completeStart;
|
completeStart = pos.completeStart;
|
||||||
completeEnd = pos.completeEnd;
|
completeEnd = pos.completeEnd;
|
||||||
} else {
|
} else {
|
||||||
completeStart = completeEnd = caretPosition(me[0]);
|
completeStart = completeEnd =
|
||||||
|
options.textHandler.getCaretPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
let space =
|
options.textHandler.replaceTerm({
|
||||||
text.substring(completeEnd + 1, completeEnd + 2) === " " ? "" : " ";
|
start: completeStart,
|
||||||
|
end: completeEnd,
|
||||||
text =
|
term: (options.preserveKey ? options.key || "" : "") + term,
|
||||||
text.substring(0, completeStart) +
|
});
|
||||||
(options.preserveKey ? options.key || "" : "") +
|
|
||||||
term +
|
|
||||||
space +
|
|
||||||
text.substring(completeEnd + 1, text.length);
|
|
||||||
|
|
||||||
me.val(text);
|
|
||||||
|
|
||||||
let newCaretPos = completeStart + 1 + term.length;
|
|
||||||
|
|
||||||
if (options.key) {
|
|
||||||
newCaretPos++;
|
|
||||||
}
|
|
||||||
|
|
||||||
setCaretPosition(me[0], newCaretPos);
|
|
||||||
|
|
||||||
if (options && options.afterComplete) {
|
if (options && options.afterComplete) {
|
||||||
options.afterComplete(text, event);
|
options.afterComplete(options.textHandler.value, event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -429,9 +414,7 @@ export default function (options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
let vOffset = 0;
|
let vOffset = 0;
|
||||||
let pos = me.caretPosition({
|
let pos = options.textHandler.getCaretCoords(completeStart);
|
||||||
pos: completeStart + 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (options.treatAsTextarea) {
|
if (options.treatAsTextarea) {
|
||||||
vOffset = -32;
|
vOffset = -32;
|
||||||
|
@ -539,7 +522,11 @@ export default function (options) {
|
||||||
closeAutocomplete();
|
closeAutocomplete();
|
||||||
});
|
});
|
||||||
|
|
||||||
async function checkTriggerRule(opts) {
|
async function checkTriggerRule(_opts) {
|
||||||
|
const opts = {
|
||||||
|
..._opts,
|
||||||
|
inCodeBlock: () => options.textHandler.inCodeBlock(),
|
||||||
|
};
|
||||||
const shouldTrigger = await options.triggerRule?.(me[0], opts);
|
const shouldTrigger = await options.triggerRule?.(me[0], opts);
|
||||||
return shouldTrigger ?? true;
|
return shouldTrigger ?? true;
|
||||||
}
|
}
|
||||||
|
@ -557,12 +544,12 @@ export default function (options) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let cp = caretPosition(me[0]);
|
let cp = options.textHandler.getCaretPosition();
|
||||||
const key = me[0].value[cp - 1];
|
const key = options.textHandler.value[cp - 1];
|
||||||
|
|
||||||
if (options.key) {
|
if (options.key) {
|
||||||
if (options.onKeyUp && key !== options.key) {
|
if (options.onKeyUp && key !== options.key) {
|
||||||
let match = options.onKeyUp(me.val(), cp);
|
let match = options.onKeyUp(options.textHandler.value, cp);
|
||||||
|
|
||||||
if (match) {
|
if (match) {
|
||||||
completeStart = cp - match[0].length;
|
completeStart = cp - match[0].length;
|
||||||
|
@ -574,10 +561,9 @@ export default function (options) {
|
||||||
|
|
||||||
if (completeStart === null && cp > 0) {
|
if (completeStart === null && cp > 0) {
|
||||||
if (key === options.key) {
|
if (key === options.key) {
|
||||||
let prevChar = me.val().charAt(cp - 2);
|
let prevChar = options.textHandler.value.charAt(cp - 2);
|
||||||
const shouldTrigger = await checkTriggerRule();
|
|
||||||
if (
|
if (
|
||||||
shouldTrigger &&
|
(await checkTriggerRule()) &&
|
||||||
(!prevChar || ALLOWED_LETTERS_REGEXP.test(prevChar))
|
(!prevChar || ALLOWED_LETTERS_REGEXP.test(prevChar))
|
||||||
) {
|
) {
|
||||||
completeStart = cp - 1;
|
completeStart = cp - 1;
|
||||||
|
@ -585,7 +571,10 @@ export default function (options) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (completeStart !== null) {
|
} else if (completeStart !== null) {
|
||||||
let term = me.val().substring(completeStart + (options.key ? 1 : 0), cp);
|
let term = options.textHandler.value.substring(
|
||||||
|
completeStart + (options.key ? 1 : 0),
|
||||||
|
cp
|
||||||
|
);
|
||||||
updateAutoComplete(dataSource(term, options));
|
updateAutoComplete(dataSource(term, options));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -593,10 +582,9 @@ export default function (options) {
|
||||||
async function guessCompletePosition(opts) {
|
async function guessCompletePosition(opts) {
|
||||||
let prev, stopFound, term;
|
let prev, stopFound, term;
|
||||||
let prevIsGood = true;
|
let prevIsGood = true;
|
||||||
let element = me[0];
|
|
||||||
let backSpace = opts?.backSpace;
|
let backSpace = opts?.backSpace;
|
||||||
let completeTermOption = opts?.completeTerm;
|
let completeTermOption = opts?.completeTerm;
|
||||||
let caretPos = caretPosition(element);
|
let caretPos = options.textHandler.getCaretPosition();
|
||||||
|
|
||||||
if (backSpace) {
|
if (backSpace) {
|
||||||
caretPos -= 1;
|
caretPos -= 1;
|
||||||
|
@ -609,12 +597,12 @@ export default function (options) {
|
||||||
|
|
||||||
while (prevIsGood && caretPos >= 0) {
|
while (prevIsGood && caretPos >= 0) {
|
||||||
caretPos -= 1;
|
caretPos -= 1;
|
||||||
prev = element.value[caretPos];
|
prev = options.textHandler.value[caretPos];
|
||||||
|
|
||||||
stopFound = prev === options.key;
|
stopFound = prev === options.key;
|
||||||
|
|
||||||
if (stopFound) {
|
if (stopFound) {
|
||||||
prev = element.value[caretPos - 1];
|
prev = options.textHandler.value[caretPos - 1];
|
||||||
const shouldTrigger = await checkTriggerRule({ backSpace });
|
const shouldTrigger = await checkTriggerRule({ backSpace });
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -622,7 +610,10 @@ export default function (options) {
|
||||||
(prev === undefined || ALLOWED_LETTERS_REGEXP.test(prev))
|
(prev === undefined || ALLOWED_LETTERS_REGEXP.test(prev))
|
||||||
) {
|
) {
|
||||||
start = caretPos;
|
start = caretPos;
|
||||||
term = element.value.substring(caretPos + 1, initialCaretPos);
|
term = options.textHandler.value.substring(
|
||||||
|
caretPos + 1,
|
||||||
|
initialCaretPos
|
||||||
|
);
|
||||||
end = caretPos + term.length;
|
end = caretPos + term.length;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -653,9 +644,10 @@ export default function (options) {
|
||||||
inputSelectedItems.push("");
|
inputSelectedItems.push("");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof inputSelectedItems[0] === "string" && me.val().length > 0) {
|
const value = options.textHandler.value;
|
||||||
|
if (typeof inputSelectedItems[0] === "string" && value.length > 0) {
|
||||||
inputSelectedItems.pop();
|
inputSelectedItems.pop();
|
||||||
inputSelectedItems.push(me.val());
|
inputSelectedItems.push(value);
|
||||||
if (options.onChangeItems) {
|
if (options.onChangeItems) {
|
||||||
options.onChangeItems(inputSelectedItems);
|
options.onChangeItems(inputSelectedItems);
|
||||||
}
|
}
|
||||||
|
@ -693,10 +685,13 @@ export default function (options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (completeStart !== null) {
|
if (completeStart !== null) {
|
||||||
cp = caretPosition(me[0]);
|
cp = options.textHandler.getCaretPosition();
|
||||||
|
|
||||||
// allow people to right arrow out of completion
|
// allow people to right arrow out of completion
|
||||||
if (e.which === keys.rightArrow && me[0].value[cp] === " ") {
|
if (
|
||||||
|
e.which === keys.rightArrow &&
|
||||||
|
options.textHandler.value[cp] === " "
|
||||||
|
) {
|
||||||
closeAutocomplete();
|
closeAutocomplete();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -771,7 +766,10 @@ export default function (options) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
term = me.val().substring(completeStart + (options.key ? 1 : 0), cp);
|
term = options.textHandler.value.substring(
|
||||||
|
completeStart + (options.key ? 1 : 0),
|
||||||
|
cp
|
||||||
|
);
|
||||||
|
|
||||||
if (completeStart === cp && term === options.key) {
|
if (completeStart === cp && term === options.key) {
|
||||||
closeAutocomplete();
|
closeAutocomplete();
|
||||||
|
|
|
@ -4,11 +4,7 @@ import { ajax } from "discourse/lib/ajax";
|
||||||
import { CANCELLED_STATUS } from "discourse/lib/autocomplete";
|
import { CANCELLED_STATUS } from "discourse/lib/autocomplete";
|
||||||
import { getHashtagTypeClasses as getHashtagTypeClassesNew } from "discourse/lib/hashtag-type-registry";
|
import { getHashtagTypeClasses as getHashtagTypeClassesNew } from "discourse/lib/hashtag-type-registry";
|
||||||
import { emojiUnescape } from "discourse/lib/text";
|
import { emojiUnescape } from "discourse/lib/text";
|
||||||
import {
|
import { escapeExpression } from "discourse/lib/utilities";
|
||||||
caretPosition,
|
|
||||||
escapeExpression,
|
|
||||||
inCodeBlock,
|
|
||||||
} from "discourse/lib/utilities";
|
|
||||||
import { INPUT_DELAY, isTesting } from "discourse-common/config/environment";
|
import { INPUT_DELAY, isTesting } from "discourse-common/config/environment";
|
||||||
import discourseDebounce from "discourse-common/lib/debounce";
|
import discourseDebounce from "discourse-common/lib/debounce";
|
||||||
import discourseLater from "discourse-common/lib/later";
|
import discourseLater from "discourse-common/lib/later";
|
||||||
|
@ -50,8 +46,8 @@ export function setupHashtagAutocomplete(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function hashtagTriggerRule(textarea) {
|
export async function hashtagTriggerRule(textarea, { inCodeBlock }) {
|
||||||
return !(await inCodeBlock(textarea.value, caretPosition(textarea)));
|
return !(await inCodeBlock());
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hashtagAutocompleteOptions(
|
export function hashtagAutocompleteOptions(
|
||||||
|
@ -62,8 +58,6 @@ export function hashtagAutocompleteOptions(
|
||||||
return {
|
return {
|
||||||
template: findRawTemplate("hashtag-autocomplete"),
|
template: findRawTemplate("hashtag-autocomplete"),
|
||||||
key: "#",
|
key: "#",
|
||||||
afterComplete: autocompleteOptions.afterComplete,
|
|
||||||
treatAsTextarea: autocompleteOptions.treatAsTextarea,
|
|
||||||
scrollElementSelector: ".hashtag-autocomplete__fadeout",
|
scrollElementSelector: ".hashtag-autocomplete__fadeout",
|
||||||
autoSelectFirstSuggestion: true,
|
autoSelectFirstSuggestion: true,
|
||||||
transformComplete: (obj) => obj.ref,
|
transformComplete: (obj) => obj.ref,
|
||||||
|
@ -75,6 +69,7 @@ export function hashtagAutocompleteOptions(
|
||||||
},
|
},
|
||||||
triggerRule: async (textarea, opts) =>
|
triggerRule: async (textarea, opts) =>
|
||||||
await hashtagTriggerRule(textarea, opts),
|
await hashtagTriggerRule(textarea, opts),
|
||||||
|
...autocompleteOptions,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import {
|
||||||
clipboardHelpers,
|
clipboardHelpers,
|
||||||
determinePostReplaceSelection,
|
determinePostReplaceSelection,
|
||||||
inCodeBlock,
|
inCodeBlock,
|
||||||
|
setCaretPosition,
|
||||||
} from "discourse/lib/utilities";
|
} from "discourse/lib/utilities";
|
||||||
import { isTesting } from "discourse-common/config/environment";
|
import { isTesting } from "discourse-common/config/environment";
|
||||||
import { bind } from "discourse-common/utils/decorators";
|
import { bind } from "discourse-common/utils/decorators";
|
||||||
|
@ -52,6 +53,7 @@ export default class TextareaTextManipulation {
|
||||||
textarea;
|
textarea;
|
||||||
$textarea;
|
$textarea;
|
||||||
|
|
||||||
|
autocompleteHandler;
|
||||||
placeholder;
|
placeholder;
|
||||||
|
|
||||||
constructor(owner, { markdownOptions, textarea, eventPrefix = "composer" }) {
|
constructor(owner, { markdownOptions, textarea, eventPrefix = "composer" }) {
|
||||||
|
@ -62,6 +64,8 @@ export default class TextareaTextManipulation {
|
||||||
this.textarea = textarea;
|
this.textarea = textarea;
|
||||||
this.$textarea = $(textarea);
|
this.$textarea = $(textarea);
|
||||||
|
|
||||||
|
this.autocompleteHandler = new TextareaAutocompleteHandler(textarea);
|
||||||
|
|
||||||
generateLinkifyFunction(markdownOptions || {}).then((linkify) => {
|
generateLinkifyFunction(markdownOptions || {}).then((linkify) => {
|
||||||
// When pasting links, we should use the same rules to match links as we do when creating links for a cooked post.
|
// When pasting links, we should use the same rules to match links as we do when creating links for a cooked post.
|
||||||
this._cachedLinkify = linkify;
|
this._cachedLinkify = linkify;
|
||||||
|
@ -349,13 +353,7 @@ export default class TextareaTextManipulation {
|
||||||
}
|
}
|
||||||
|
|
||||||
_insertAt(start, end, text) {
|
_insertAt(start, end, text) {
|
||||||
this.textarea.setSelectionRange(start, end);
|
insertAtTextarea(this.textarea, start, end, text);
|
||||||
this.textarea.focus();
|
|
||||||
if (start !== end && text === "") {
|
|
||||||
document.execCommand("delete", false);
|
|
||||||
} else {
|
|
||||||
document.execCommand("insertText", false, text);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extractTable(text) {
|
extractTable(text) {
|
||||||
|
@ -742,7 +740,6 @@ export default class TextareaTextManipulation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@bind
|
|
||||||
async inCodeBlock() {
|
async inCodeBlock() {
|
||||||
return inCodeBlock(
|
return inCodeBlock(
|
||||||
this.$textarea.value ?? this.$textarea.val(),
|
this.$textarea.value ?? this.$textarea.val(),
|
||||||
|
@ -829,8 +826,57 @@ export default class TextareaTextManipulation {
|
||||||
putCursorAtEnd(this.textarea);
|
putCursorAtEnd(this.textarea);
|
||||||
}
|
}
|
||||||
|
|
||||||
autocomplete() {
|
autocomplete(options) {
|
||||||
return this.$textarea.autocomplete(...arguments);
|
return this.$textarea.autocomplete(
|
||||||
|
options instanceof Object
|
||||||
|
? { textHandler: this.autocompleteHandler, ...options }
|
||||||
|
: options
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertAtTextarea(textarea, start, end, text) {
|
||||||
|
textarea.setSelectionRange(start, end);
|
||||||
|
textarea.focus();
|
||||||
|
if (start !== end && text === "") {
|
||||||
|
document.execCommand("delete", false);
|
||||||
|
} else {
|
||||||
|
document.execCommand("insertText", false, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TextareaAutocompleteHandler {
|
||||||
|
textarea;
|
||||||
|
$textarea;
|
||||||
|
|
||||||
|
constructor(textarea) {
|
||||||
|
this.textarea = textarea;
|
||||||
|
this.$textarea = $(textarea);
|
||||||
|
}
|
||||||
|
|
||||||
|
get value() {
|
||||||
|
return this.textarea.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceTerm({ start, end, term }) {
|
||||||
|
const space = this.value.substring(end + 1, end + 2) === " " ? "" : " ";
|
||||||
|
insertAtTextarea(this.textarea, start, end + 1, term + space);
|
||||||
|
setCaretPosition(this.textarea, start + 1 + term.trim().length);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCaretPosition() {
|
||||||
|
return caretPosition(this.textarea);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCaretCoords(start) {
|
||||||
|
return this.$textarea.caretPosition({ pos: start + 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
async inCodeBlock() {
|
||||||
|
return inCodeBlock(
|
||||||
|
this.$textarea.value ?? this.$textarea.val(),
|
||||||
|
caretPosition(this.$textarea)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -151,4 +151,22 @@ module("Unit | Utility | autocomplete", function (hooks) {
|
||||||
assert.dom("#ac-testing li a.selected").exists({ count: 1 });
|
assert.dom("#ac-testing li a.selected").exists({ count: 1 });
|
||||||
assert.dom("#ac-testing li a.selected").hasText("test1");
|
assert.dom("#ac-testing li a.selected").hasText("test1");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Autocomplete doesn't reset undo history", async function (assert) {
|
||||||
|
const element = textArea();
|
||||||
|
|
||||||
|
$(element).autocomplete({
|
||||||
|
key: "@",
|
||||||
|
template,
|
||||||
|
dataSource: () => ["test1", "test2"],
|
||||||
|
});
|
||||||
|
|
||||||
|
await simulateKeys(element, "@t\r");
|
||||||
|
|
||||||
|
assert.strictEqual(element.value, "@test1 ");
|
||||||
|
|
||||||
|
document.execCommand("undo");
|
||||||
|
|
||||||
|
assert.strictEqual(element.value, "@t");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue
Block a user