feat(input): added ascii to emoji conversion

This commit is contained in:
dasois 2023-08-03 09:44:52 +02:00
parent 68dfc46185
commit e4ab71eb78
2 changed files with 99 additions and 0 deletions

View file

@ -61,12 +61,14 @@ import {
matchStrikethrough, matchStrikethrough,
} from '../quill/formatting/matchers'; } from '../quill/formatting/matchers';
import { missingCaseError } from '../util/missingCaseError'; import { missingCaseError } from '../util/missingCaseError';
import { AutoSubstituteAsciiEmojis } from '../quill/auto-substitute-ascii-emojis';
Quill.register('formats/emoji', EmojiBlot); Quill.register('formats/emoji', EmojiBlot);
Quill.register('formats/mention', MentionBlot); Quill.register('formats/mention', MentionBlot);
Quill.register('formats/block', DirectionalBlot); Quill.register('formats/block', DirectionalBlot);
Quill.register('formats/monospace', MonospaceBlot); Quill.register('formats/monospace', MonospaceBlot);
Quill.register('formats/spoiler', SpoilerBlot); Quill.register('formats/spoiler', SpoilerBlot);
Quill.register('modules/autoSubstituteAsciiEmojis', AutoSubstituteAsciiEmojis);
Quill.register('modules/emojiCompletion', EmojiCompletion); Quill.register('modules/emojiCompletion', EmojiCompletion);
Quill.register('modules/mentionCompletion', MentionCompletion); Quill.register('modules/mentionCompletion', MentionCompletion);
Quill.register('modules/formattingMenu', FormattingMenu); Quill.register('modules/formattingMenu', FormattingMenu);
@ -767,6 +769,7 @@ export function CompositionInput(props: Props): React.ReactElement {
callbacksRef.current.onPickEmoji(emoji), callbacksRef.current.onPickEmoji(emoji),
skinTone, skinTone,
}, },
autoSubstituteAsciiEmojis: true,
formattingMenu: { formattingMenu: {
i18n, i18n,
isMenuEnabled: isFormattingEnabled, isMenuEnabled: isFormattingEnabled,

View file

@ -0,0 +1,96 @@
// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type Quill from 'quill';
import Delta from 'quill-delta';
import _ from 'lodash';
import type { EmojiData } from '../../components/emoji/lib';
import {
convertShortName,
convertShortNameToData,
} from '../../components/emoji/lib';
type AutoSubstituteAsciiEmojisOptions = {
skinTone: number;
};
const emojiMap: Record<string, string> = {
':)': 'slightly_smiling_face',
':-)': 'slightly_smiling_face',
':(': 'slightly_frowning_face',
':-(': 'slightly_frowning_face',
':D': 'smiley',
':-D': 'smiley',
':*': 'kissing',
':-*': 'kissing',
':P': 'stuck_out_tongue',
':-P': 'stuck_out_tongue',
';P': 'stuck_out_tongue_winking_eye',
';-P': 'stuck_out_tongue_winking_eye',
'D:': 'anguished',
"D-':": 'anguished',
':O': 'open_mouth',
':-O': 'open_mouth',
":'(": 'cry',
":'-(": 'cry',
':/': 'confused',
':-/': 'confused',
';)': 'wink',
';-)': 'wink',
'(Y)': '+1',
'(N)': '-1',
};
export class AutoSubstituteAsciiEmojis {
options: AutoSubstituteAsciiEmojisOptions;
quill: Quill;
constructor(quill: Quill, options: AutoSubstituteAsciiEmojisOptions) {
this.options = options;
this.quill = quill;
this.quill.on(
'text-change',
_.debounce(() => this.onTextChange(), 100)
);
}
onTextChange(): void {
const range = this.quill.getSelection();
if (!range) {
return;
}
const [blot, index] = this.quill.getLeaf(range.index);
if (blot !== undefined && blot.text !== undefined) {
const blotText: string = blot.text;
Object.entries(emojiMap).some(([textEmoji, emojiName]) => {
if (blotText.substring(0, index).endsWith(textEmoji)) {
const emojiData = convertShortNameToData(
emojiName,
this.options.skinTone
);
if (emojiData) {
this.insertEmoji(
emojiData,
range.index - textEmoji.length,
textEmoji.length
);
return true;
}
}
return false;
});
}
}
insertEmoji(emojiData: EmojiData, index: number, range: number): void {
const emoji = convertShortName(emojiData.short_name, this.options.skinTone);
const delta = new Delta().retain(index).delete(range).insert({ emoji });
this.quill.updateContents(delta, 'user');
this.quill.setSelection(index + 1, 0);
}
}