This commit is contained in:
		
				commit
				
					
						14a2714c1e
					
				
			
		
					 2 changed files with 99 additions and 0 deletions
				
			
		| 
						 | 
				
			
			@ -62,12 +62,14 @@ import {
 | 
			
		|||
  matchStrikethrough,
 | 
			
		||||
} from '../quill/formatting/matchers';
 | 
			
		||||
import { missingCaseError } from '../util/missingCaseError';
 | 
			
		||||
import { AutoSubstituteAsciiEmojis } from '../quill/auto-substitute-ascii-emojis';
 | 
			
		||||
 | 
			
		||||
Quill.register('formats/emoji', EmojiBlot);
 | 
			
		||||
Quill.register('formats/mention', MentionBlot);
 | 
			
		||||
Quill.register('formats/block', DirectionalBlot);
 | 
			
		||||
Quill.register('formats/monospace', MonospaceBlot);
 | 
			
		||||
Quill.register('formats/spoiler', SpoilerBlot);
 | 
			
		||||
Quill.register('modules/autoSubstituteAsciiEmojis', AutoSubstituteAsciiEmojis);
 | 
			
		||||
Quill.register('modules/emojiCompletion', EmojiCompletion);
 | 
			
		||||
Quill.register('modules/mentionCompletion', MentionCompletion);
 | 
			
		||||
Quill.register('modules/formattingMenu', FormattingMenu);
 | 
			
		||||
| 
						 | 
				
			
			@ -729,6 +731,7 @@ export function CompositionInput(props: Props): React.ReactElement {
 | 
			
		|||
                callbacksRef.current.onPickEmoji(emoji),
 | 
			
		||||
              skinTone,
 | 
			
		||||
            },
 | 
			
		||||
            autoSubstituteAsciiEmojis: true,
 | 
			
		||||
            formattingMenu: {
 | 
			
		||||
              i18n,
 | 
			
		||||
              isMenuEnabled: isFormattingEnabled,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										96
									
								
								ts/quill/auto-substitute-ascii-emojis/index.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								ts/quill/auto-substitute-ascii-emojis/index.tsx
									
										
									
									
									
										Normal 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);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue