diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx index 93cb498d21..cba9eb79b3 100644 --- a/src/HtmlUtils.tsx +++ b/src/HtmlUtils.tsx @@ -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]}:` : ''; } diff --git a/src/autocomplete/EmojiProvider.tsx b/src/autocomplete/EmojiProvider.tsx index edf691e151..8a81acd498 100644 --- a/src/autocomplete/EmojiProvider.tsx +++ b/src/autocomplete/EmojiProvider.tsx @@ -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(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: ( - + { c.emoji.unicode } ), diff --git a/src/components/views/emojipicker/Preview.tsx b/src/components/views/emojipicker/Preview.tsx index bd9982e50f..da2f8dcd89 100644 --- a/src/components/views/emojipicker/Preview.tsx +++ b/src/components/views/emojipicker/Preview.tsx @@ -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 { render() { - const { - unicode = "", - annotation = "", - } = this.props.emoji; - const shortcode = getShortcodes(this.props.emoji)[0]; + const { unicode, annotation, shortcodes: [shortcode] } = this.props.emoji; return (
diff --git a/src/components/views/emojipicker/QuickReactions.tsx b/src/components/views/emojipicker/QuickReactions.tsx index 2d78e3e4cf..9321450fc1 100644 --- a/src/components/views/emojipicker/QuickReactions.tsx +++ b/src/components/views/emojipicker/QuickReactions.tsx @@ -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 { }; render() { - const shortcode = this.state.hover ? getShortcodes(this.state.hover)[0] : undefined; + const shortcode = this.state.hover?.shortcodes?.[0]; return (

diff --git a/src/emoji.ts b/src/emoji.ts index ac4de654f7..fe9e52d35f 100644 --- a/src/emoji.ts +++ b/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(); // not exported as gets for it are handled by getEmojiFromUnicode -export const EMOTICON_TO_EMOJI = new Map(); +const UNICODE_TO_EMOJI = new Map(); // not exported as gets for it are handled by getEmojiFromUnicode +export const EMOTICON_TO_EMOJI = new Map(); 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;