Zip shortcodes in with emoji objects
Signed-off-by: Robin Townsend <robin@robin.town>pull/21833/head
							parent
							
								
									8efb30eb07
								
							
						
					
					
						commit
						f88d5dd24e
					
				| 
						 | 
				
			
			@ -34,7 +34,7 @@ import { IExtendedSanitizeOptions } from './@types/sanitize-html';
 | 
			
		|||
import linkifyMatrix from './linkify-matrix';
 | 
			
		||||
import SettingsStore from './settings/SettingsStore';
 | 
			
		||||
import { tryTransformPermalinkToLocalHref } from "./utils/permalinks/Permalinks";
 | 
			
		||||
import { getEmojiFromUnicode, getShortcodes } from "./emoji";
 | 
			
		||||
import { getEmojiFromUnicode } from "./emoji";
 | 
			
		||||
import ReplyThread from "./components/views/elements/ReplyThread";
 | 
			
		||||
import { mediaFromMxc } from "./customisations/Media";
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -80,7 +80,7 @@ function mightContainEmoji(str: string): boolean {
 | 
			
		|||
 * @return {String} The shortcode (such as :thumbup:)
 | 
			
		||||
 */
 | 
			
		||||
export function unicodeToShortcode(char: string): string {
 | 
			
		||||
    const shortcodes = getShortcodes(getEmojiFromUnicode(char));
 | 
			
		||||
    const shortcodes = getEmojiFromUnicode(char).shortcodes;
 | 
			
		||||
    return shortcodes.length > 0 ? `:${shortcodes[0]}:` : '';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,7 +25,7 @@ import { PillCompletion } from './Components';
 | 
			
		|||
import { ICompletion, ISelectionRange } from './Autocompleter';
 | 
			
		||||
import { uniq, sortBy } from 'lodash';
 | 
			
		||||
import SettingsStore from "../settings/SettingsStore";
 | 
			
		||||
import { EMOJI, IEmoji, getShortcodes } from '../emoji';
 | 
			
		||||
import { EMOJI, IEmoji } from '../emoji';
 | 
			
		||||
 | 
			
		||||
import EMOTICON_REGEX from 'emojibase-regex/emoticon';
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -37,8 +37,6 @@ const EMOJI_REGEX = new RegExp('(' + EMOTICON_REGEX.source + '|(?:^|\\s):[+-\\w]
 | 
			
		|||
 | 
			
		||||
interface IEmojiShort {
 | 
			
		||||
    emoji: IEmoji;
 | 
			
		||||
    shortcode: string;
 | 
			
		||||
    altShortcodes: string[];
 | 
			
		||||
    _orderBy: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -47,16 +45,11 @@ const EMOJI_SHORTCODES: IEmojiShort[] = EMOJI.sort((a, b) => {
 | 
			
		|||
        return a.order - b.order;
 | 
			
		||||
    }
 | 
			
		||||
    return a.group - b.group;
 | 
			
		||||
}).map((emoji, index) => {
 | 
			
		||||
    const [shortcode, ...altShortcodes] = getShortcodes(emoji);
 | 
			
		||||
    return {
 | 
			
		||||
        emoji,
 | 
			
		||||
        shortcode: shortcode ? `:${shortcode}:` : undefined,
 | 
			
		||||
        altShortcodes: altShortcodes.map(s => `:${s}:`),
 | 
			
		||||
        // Include the index so that we can preserve the original order
 | 
			
		||||
        _orderBy: index,
 | 
			
		||||
    };
 | 
			
		||||
}).filter(emoji => emoji.shortcode);
 | 
			
		||||
}).map((emoji, index) => ({
 | 
			
		||||
    emoji,
 | 
			
		||||
    // Include the index so that we can preserve the original order
 | 
			
		||||
    _orderBy: index,
 | 
			
		||||
})).filter(o => o.emoji.shortcodes[0]);
 | 
			
		||||
 | 
			
		||||
function score(query, space) {
 | 
			
		||||
    const index = space.indexOf(query);
 | 
			
		||||
| 
						 | 
				
			
			@ -74,10 +67,8 @@ export default class EmojiProvider extends AutocompleteProvider {
 | 
			
		|||
    constructor() {
 | 
			
		||||
        super(EMOJI_REGEX);
 | 
			
		||||
        this.matcher = new QueryMatcher<IEmojiShort>(EMOJI_SHORTCODES, {
 | 
			
		||||
            keys: ['emoji.emoticon', 'shortcode'],
 | 
			
		||||
            funcs: [
 | 
			
		||||
                o => o.altShortcodes.join(" "), // aliases
 | 
			
		||||
            ],
 | 
			
		||||
            keys: ['emoji.emoticon'],
 | 
			
		||||
            funcs: [o => o.emoji.shortcodes.map(s => `:${s}:`).join(" ")],
 | 
			
		||||
            // For matching against ascii equivalents
 | 
			
		||||
            shouldMatchWordsOnly: false,
 | 
			
		||||
        });
 | 
			
		||||
| 
						 | 
				
			
			@ -112,16 +103,16 @@ export default class EmojiProvider extends AutocompleteProvider {
 | 
			
		|||
            sorters.push(c => score(matchedString, c.emoji.emoticon || ""));
 | 
			
		||||
 | 
			
		||||
            // then sort by score (Infinity if matchedString not in shortcode)
 | 
			
		||||
            sorters.push(c => score(matchedString, c.shortcode));
 | 
			
		||||
            sorters.push(c => score(matchedString, c.emoji.shortcodes[0]));
 | 
			
		||||
            // then sort by max score of all shortcodes, trim off the `:`
 | 
			
		||||
            sorters.push(c => Math.min(
 | 
			
		||||
                ...[c.shortcode, ...c.altShortcodes].map(s => score(matchedString.substring(1), s)),
 | 
			
		||||
                ...c.emoji.shortcodes.map(s => score(matchedString.substring(1), s)),
 | 
			
		||||
            ));
 | 
			
		||||
            // If the matchedString is not empty, sort by length of shortcode. Example:
 | 
			
		||||
            //  matchedString = ":bookmark"
 | 
			
		||||
            //  completions = [":bookmark:", ":bookmark_tabs:", ...]
 | 
			
		||||
            if (matchedString.length > 1) {
 | 
			
		||||
                sorters.push(c => c.shortcode.length);
 | 
			
		||||
                sorters.push(c => c.emoji.shortcodes[0].length);
 | 
			
		||||
            }
 | 
			
		||||
            // Finally, sort by original ordering
 | 
			
		||||
            sorters.push(c => c._orderBy);
 | 
			
		||||
| 
						 | 
				
			
			@ -130,7 +121,7 @@ export default class EmojiProvider extends AutocompleteProvider {
 | 
			
		|||
            completions = completions.map(c => ({
 | 
			
		||||
                completion: c.emoji.unicode,
 | 
			
		||||
                component: (
 | 
			
		||||
                    <PillCompletion title={c.shortcode} aria-label={c.emoji.unicode}>
 | 
			
		||||
                    <PillCompletion title={`:${c.emoji.shortcodes[0]}:`} aria-label={c.emoji.unicode}>
 | 
			
		||||
                        <span>{ c.emoji.unicode }</span>
 | 
			
		||||
                    </PillCompletion>
 | 
			
		||||
                ),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,7 +17,7 @@ limitations under the License.
 | 
			
		|||
 | 
			
		||||
import React from 'react';
 | 
			
		||||
 | 
			
		||||
import { IEmoji, getShortcodes } from "../../../emoji";
 | 
			
		||||
import { IEmoji } from "../../../emoji";
 | 
			
		||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
 | 
			
		||||
 | 
			
		||||
interface IProps {
 | 
			
		||||
| 
						 | 
				
			
			@ -27,11 +27,7 @@ interface IProps {
 | 
			
		|||
@replaceableComponent("views.emojipicker.Preview")
 | 
			
		||||
class Preview extends React.PureComponent<IProps> {
 | 
			
		||||
    render() {
 | 
			
		||||
        const {
 | 
			
		||||
            unicode = "",
 | 
			
		||||
            annotation = "",
 | 
			
		||||
        } = this.props.emoji;
 | 
			
		||||
        const shortcode = getShortcodes(this.props.emoji)[0];
 | 
			
		||||
        const { unicode, annotation, shortcodes: [shortcode] } = this.props.emoji;
 | 
			
		||||
 | 
			
		||||
        return (
 | 
			
		||||
            <div className="mx_EmojiPicker_footer mx_EmojiPicker_preview">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,7 +18,7 @@ limitations under the License.
 | 
			
		|||
import React from 'react';
 | 
			
		||||
 | 
			
		||||
import { _t } from '../../../languageHandler';
 | 
			
		||||
import { getEmojiFromUnicode, getShortcodes, IEmoji } from "../../../emoji";
 | 
			
		||||
import { getEmojiFromUnicode, IEmoji } from "../../../emoji";
 | 
			
		||||
import Emoji from "./Emoji";
 | 
			
		||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -62,7 +62,7 @@ class QuickReactions extends React.Component<IProps, IState> {
 | 
			
		|||
    };
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
        const shortcode = this.state.hover ? getShortcodes(this.state.hover)[0] : undefined;
 | 
			
		||||
        const shortcode = this.state.hover?.shortcodes?.[0];
 | 
			
		||||
        return (
 | 
			
		||||
            <section className="mx_EmojiPicker_footer mx_EmojiPicker_quick mx_EmojiPicker_category">
 | 
			
		||||
                <h2 className="mx_EmojiPicker_quick_header mx_EmojiPicker_category_label">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										38
									
								
								src/emoji.ts
								
								
								
								
							
							
						
						
									
										38
									
								
								src/emoji.ts
								
								
								
								
							| 
						 | 
				
			
			@ -22,26 +22,19 @@ export interface IEmoji {
 | 
			
		|||
    group?: number;
 | 
			
		||||
    hexcode: string;
 | 
			
		||||
    order?: number;
 | 
			
		||||
    shortcodes: string[];
 | 
			
		||||
    tags?: string[];
 | 
			
		||||
    unicode: string;
 | 
			
		||||
    emoticon?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface IEmojiWithFilterString extends IEmoji {
 | 
			
		||||
    filterString?: string;
 | 
			
		||||
    filterString: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// The unicode is stored without the variant selector
 | 
			
		||||
const UNICODE_TO_EMOJI = new Map<string, IEmojiWithFilterString>(); // not exported as gets for it are handled by getEmojiFromUnicode
 | 
			
		||||
export const EMOTICON_TO_EMOJI = new Map<string, IEmojiWithFilterString>();
 | 
			
		||||
const UNICODE_TO_EMOJI = new Map<string, IEmoji>(); // not exported as gets for it are handled by getEmojiFromUnicode
 | 
			
		||||
export const EMOTICON_TO_EMOJI = new Map<string, IEmoji>();
 | 
			
		||||
 | 
			
		||||
export const getEmojiFromUnicode = unicode => UNICODE_TO_EMOJI.get(stripVariation(unicode));
 | 
			
		||||
 | 
			
		||||
const toArray = (shortcodes?: string | string[]): string[] =>
 | 
			
		||||
    typeof shortcodes === "string" ? [shortcodes] : (shortcodes ?? []);
 | 
			
		||||
export const getShortcodes = (emoji: IEmoji): string[] =>
 | 
			
		||||
    toArray(SHORTCODES[emoji.hexcode]);
 | 
			
		||||
 | 
			
		||||
const EMOJIBASE_GROUP_ID_TO_CATEGORY = [
 | 
			
		||||
    "people", // smileys
 | 
			
		||||
    "people", // actually people
 | 
			
		||||
| 
						 | 
				
			
			@ -69,17 +62,24 @@ export const DATA_BY_CATEGORY = {
 | 
			
		|||
const ZERO_WIDTH_JOINER = "\u200D";
 | 
			
		||||
 | 
			
		||||
// Store various mappings from unicode/emoticon/shortcode to the Emoji objects
 | 
			
		||||
EMOJIBASE.forEach((emoji: IEmojiWithFilterString) => {
 | 
			
		||||
    const shortcodes = getShortcodes(emoji);
 | 
			
		||||
export const EMOJI: IEmoji[] = EMOJIBASE.map(emojiData => {
 | 
			
		||||
    const shortcodeData = SHORTCODES[emojiData.hexcode];
 | 
			
		||||
    // Homogenize shortcodes by ensuring that everything is an array
 | 
			
		||||
    const shortcodes = typeof shortcodeData === "string" ? [shortcodeData] : (shortcodeData ?? []);
 | 
			
		||||
 | 
			
		||||
    const emoji: IEmoji = {
 | 
			
		||||
        ...emojiData,
 | 
			
		||||
        shortcodes,
 | 
			
		||||
        // This is used as the string to match the query against when filtering emojis
 | 
			
		||||
        filterString: (`${emojiData.annotation}\n${shortcodes.join('\n')}}\n${emojiData.emoticon || ''}\n` +
 | 
			
		||||
            `${emojiData.unicode.split(ZERO_WIDTH_JOINER).join("\n")}`).toLowerCase(),
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const categoryId = EMOJIBASE_GROUP_ID_TO_CATEGORY[emoji.group];
 | 
			
		||||
    if (DATA_BY_CATEGORY.hasOwnProperty(categoryId)) {
 | 
			
		||||
        DATA_BY_CATEGORY[categoryId].push(emoji);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // This is used as the string to match the query against when filtering emojis
 | 
			
		||||
    emoji.filterString = (`${emoji.annotation}\n${shortcodes.join('\n')}}\n${emoji.emoticon || ''}\n` +
 | 
			
		||||
        `${emoji.unicode.split(ZERO_WIDTH_JOINER).join("\n")}`).toLowerCase();
 | 
			
		||||
 | 
			
		||||
    // Add mapping from unicode to Emoji object
 | 
			
		||||
    // The 'unicode' field that we use in emojibase has either
 | 
			
		||||
    // VS15 or VS16 appended to any characters that can take
 | 
			
		||||
| 
						 | 
				
			
			@ -93,6 +93,8 @@ EMOJIBASE.forEach((emoji: IEmojiWithFilterString) => {
 | 
			
		|||
        // Add mapping from emoticon to Emoji object
 | 
			
		||||
        EMOTICON_TO_EMOJI.set(emoji.emoticon, emoji);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return emoji;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
| 
						 | 
				
			
			@ -106,5 +108,3 @@ EMOJIBASE.forEach((emoji: IEmojiWithFilterString) => {
 | 
			
		|||
function stripVariation(str) {
 | 
			
		||||
    return str.replace(/[\uFE00-\uFE0F]$/, "");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const EMOJI: IEmoji[] = EMOJIBASE;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue