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