Support backspace after emoji completion
Co-authored-by: Jamie Kyle <jamie@signal.org>
This commit is contained in:
parent
85a75cb28f
commit
4ec69ee3a0
3 changed files with 62 additions and 46 deletions
|
@ -66,7 +66,7 @@ export class AutoSubstituteAsciiEmojis {
|
|||
}
|
||||
|
||||
onTextChange(): void {
|
||||
if (!window.storage.get('autoConvertEmoji', false)) {
|
||||
if (!window.storage.get('autoConvertEmoji', true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,14 @@ type EmojiPickerOptions = {
|
|||
search: SearchFnType;
|
||||
};
|
||||
|
||||
export type InsertEmojiOptionsType = Readonly<{
|
||||
shortName: string;
|
||||
index: number;
|
||||
range: number;
|
||||
withTrailingSpace?: boolean;
|
||||
justPressedColon?: boolean;
|
||||
}>;
|
||||
|
||||
export class EmojiCompletion {
|
||||
results: Array<string>;
|
||||
|
||||
|
@ -84,10 +92,13 @@ export class EmojiCompletion {
|
|||
() => this.onTextChange(true)
|
||||
);
|
||||
|
||||
this.quill.on(
|
||||
'text-change',
|
||||
_.debounce(() => this.onTextChange(), 100)
|
||||
);
|
||||
const debouncedOnTextChange = _.debounce(() => this.onTextChange(), 100);
|
||||
|
||||
this.quill.on('text-change', (_now, _before, source) => {
|
||||
if (source === 'user') {
|
||||
debouncedOnTextChange();
|
||||
}
|
||||
});
|
||||
this.quill.on('selection-change', this.onSelectionChange.bind(this));
|
||||
}
|
||||
|
||||
|
@ -139,11 +150,12 @@ export class EmojiCompletion {
|
|||
if (isShortName(leftTokenText)) {
|
||||
const numberOfColons = isSelfClosing ? 2 : 1;
|
||||
|
||||
this.insertEmoji(
|
||||
leftTokenText,
|
||||
range.index - leftTokenText.length - numberOfColons,
|
||||
leftTokenText.length + numberOfColons
|
||||
);
|
||||
this.insertEmoji({
|
||||
shortName: leftTokenText,
|
||||
index: range.index - leftTokenText.length - numberOfColons,
|
||||
range: leftTokenText.length + numberOfColons,
|
||||
justPressedColon,
|
||||
});
|
||||
return INTERCEPT;
|
||||
}
|
||||
this.reset();
|
||||
|
@ -155,11 +167,12 @@ export class EmojiCompletion {
|
|||
const tokenText = leftTokenText + rightTokenText;
|
||||
|
||||
if (isShortName(tokenText)) {
|
||||
this.insertEmoji(
|
||||
tokenText,
|
||||
range.index - leftTokenText.length - 1,
|
||||
tokenText.length + 2
|
||||
);
|
||||
this.insertEmoji({
|
||||
shortName: tokenText,
|
||||
index: range.index - leftTokenText.length - 1,
|
||||
range: tokenText.length + 2,
|
||||
justPressedColon,
|
||||
});
|
||||
return INTERCEPT;
|
||||
}
|
||||
}
|
||||
|
@ -212,27 +225,33 @@ export class EmojiCompletion {
|
|||
|
||||
const [, tokenText] = tokenTextMatch;
|
||||
|
||||
this.insertEmoji(
|
||||
emoji,
|
||||
range.index - tokenText.length - 1,
|
||||
tokenText.length + 1,
|
||||
true
|
||||
);
|
||||
this.insertEmoji({
|
||||
shortName: emoji,
|
||||
index: range.index - tokenText.length - 1,
|
||||
range: tokenText.length + 1,
|
||||
withTrailingSpace: true,
|
||||
});
|
||||
}
|
||||
|
||||
insertEmoji(
|
||||
shortName: string,
|
||||
index: number,
|
||||
range: number,
|
||||
withTrailingSpace = false
|
||||
): void {
|
||||
insertEmoji({
|
||||
shortName,
|
||||
index,
|
||||
range,
|
||||
withTrailingSpace = false,
|
||||
justPressedColon = false,
|
||||
}: InsertEmojiOptionsType): void {
|
||||
const emoji = convertShortName(shortName, this.options.skinTone);
|
||||
|
||||
let source = this.quill.getText(index, range);
|
||||
if (justPressedColon) {
|
||||
source += ':';
|
||||
}
|
||||
|
||||
const delta = new Delta()
|
||||
.retain(index)
|
||||
.delete(range)
|
||||
.insert({
|
||||
emoji: { value: emoji },
|
||||
emoji: { value: emoji, source },
|
||||
});
|
||||
|
||||
if (withTrailingSpace) {
|
||||
|
|
|
@ -5,6 +5,7 @@ import { assert } from 'chai';
|
|||
import sinon from 'sinon';
|
||||
|
||||
import { EmojiCompletion } from '../../../quill/emoji/completion';
|
||||
import type { InsertEmojiOptionsType } from '../../../quill/emoji/completion';
|
||||
import { createSearch } from '../../../components/emoji/lib';
|
||||
|
||||
describe('emojiCompletion', () => {
|
||||
|
@ -15,6 +16,7 @@ describe('emojiCompletion', () => {
|
|||
beforeEach(function (this: Mocha.Context) {
|
||||
mockQuill = {
|
||||
getLeaf: sinon.stub(),
|
||||
getText: sinon.stub(),
|
||||
getSelection: sinon.stub(),
|
||||
keyboard: {
|
||||
addBinding: sinon.stub(),
|
||||
|
@ -55,10 +57,7 @@ describe('emojiCompletion', () => {
|
|||
});
|
||||
|
||||
describe('onTextChange', () => {
|
||||
let insertEmojiStub: sinon.SinonStub<
|
||||
[string, number, number, (boolean | undefined)?],
|
||||
void
|
||||
>;
|
||||
let insertEmojiStub: sinon.SinonStub<[InsertEmojiOptionsType], void>;
|
||||
|
||||
beforeEach(() => {
|
||||
emojiCompletion.results = ['joy'];
|
||||
|
@ -194,9 +193,9 @@ describe('emojiCompletion', () => {
|
|||
});
|
||||
|
||||
it('inserts the emoji at the current cursor position', () => {
|
||||
const [emoji, index, range] = insertEmojiStub.args[0];
|
||||
const [{ shortName, index, range }] = insertEmojiStub.args[0];
|
||||
|
||||
assert.equal(emoji, 'smile');
|
||||
assert.equal(shortName, 'smile');
|
||||
assert.equal(index, 0);
|
||||
assert.equal(range, 7);
|
||||
});
|
||||
|
@ -223,9 +222,9 @@ describe('emojiCompletion', () => {
|
|||
});
|
||||
|
||||
it('inserts the emoji at the current cursor position', () => {
|
||||
const [emoji, index, range] = insertEmojiStub.args[0];
|
||||
const [{ shortName, index, range }] = insertEmojiStub.args[0];
|
||||
|
||||
assert.equal(emoji, 'smile');
|
||||
assert.equal(shortName, 'smile');
|
||||
assert.equal(index, 7);
|
||||
assert.equal(range, 7);
|
||||
});
|
||||
|
@ -283,9 +282,9 @@ describe('emojiCompletion', () => {
|
|||
});
|
||||
|
||||
it('inserts the emoji at the current cursor position', () => {
|
||||
const [emoji, index, range] = insertEmojiStub.args[0];
|
||||
const [{ shortName, index, range }] = insertEmojiStub.args[0];
|
||||
|
||||
assert.equal(emoji, 'smile');
|
||||
assert.equal(shortName, 'smile');
|
||||
assert.equal(index, 0);
|
||||
assert.equal(range, validEmoji.length);
|
||||
});
|
||||
|
@ -332,9 +331,9 @@ describe('emojiCompletion', () => {
|
|||
});
|
||||
|
||||
it('inserts the emoji at the current cursor position', () => {
|
||||
const [emoji, index, range] = insertEmojiStub.args[0];
|
||||
const [{ shortName, index, range }] = insertEmojiStub.args[0];
|
||||
|
||||
assert.equal(emoji, 'smile');
|
||||
assert.equal(shortName, 'smile');
|
||||
assert.equal(index, 0);
|
||||
assert.equal(range, 6);
|
||||
});
|
||||
|
@ -347,10 +346,7 @@ describe('emojiCompletion', () => {
|
|||
});
|
||||
|
||||
describe('completeEmoji', () => {
|
||||
let insertEmojiStub: sinon.SinonStub<
|
||||
[string, number, number, (boolean | undefined)?],
|
||||
void
|
||||
>;
|
||||
let insertEmojiStub: sinon.SinonStub<[InsertEmojiOptionsType], void>;
|
||||
|
||||
beforeEach(() => {
|
||||
emojiCompletion.results = ['smile', 'smile_cat'];
|
||||
|
@ -377,9 +373,10 @@ describe('emojiCompletion', () => {
|
|||
});
|
||||
|
||||
it('inserts the currently selected emoji at the current cursor position', () => {
|
||||
const [emoji, insertIndex, range] = insertEmojiStub.args[0];
|
||||
const [{ shortName, index: insertIndex, range }] =
|
||||
insertEmojiStub.args[0];
|
||||
|
||||
assert.equal(emoji, 'smile_cat');
|
||||
assert.equal(shortName, 'smile_cat');
|
||||
assert.equal(insertIndex, 0);
|
||||
assert.equal(range, text.length);
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue