diff --git a/ts/components/CallScreen.tsx b/ts/components/CallScreen.tsx index 9c0c1c0e81..244f78e6dc 100644 --- a/ts/components/CallScreen.tsx +++ b/ts/components/CallScreen.tsx @@ -1271,7 +1271,7 @@ function useReactionsToast(props: UseReactionsToastType): void { diff --git a/ts/components/ProfileEditor.tsx b/ts/components/ProfileEditor.tsx index a1b6d9ce18..0d57f45634 100644 --- a/ts/components/ProfileEditor.tsx +++ b/ts/components/ProfileEditor.tsx @@ -166,7 +166,7 @@ function BioEmoji(props: { emoji: EmojiVariantKey }) { return ( diff --git a/ts/components/conversation/Emojify.tsx b/ts/components/conversation/Emojify.tsx index 185bd6b2c2..87d06a352a 100644 --- a/ts/components/conversation/Emojify.tsx +++ b/ts/components/conversation/Emojify.tsx @@ -54,7 +54,7 @@ export function Emojify({ // eslint-disable-next-line react/no-array-index-key key={index} role="img" - aria-label={emojiLocalizer(variantKey)} + aria-label={emojiLocalizer.getLocaleShortName(variantKey)} emoji={variant} size={fontSizeOverride} /> diff --git a/ts/components/conversation/ReactionViewer.tsx b/ts/components/conversation/ReactionViewer.tsx index d9e74e9094..e82f7e6e96 100644 --- a/ts/components/conversation/ReactionViewer.tsx +++ b/ts/components/conversation/ReactionViewer.tsx @@ -86,7 +86,7 @@ function ReactionViewerEmoji(props: { return ( diff --git a/ts/components/emoji/lib.ts b/ts/components/emoji/lib.ts index 4417e31133..45e1221fd7 100644 --- a/ts/components/emoji/lib.ts +++ b/ts/components/emoji/lib.ts @@ -3,15 +3,7 @@ // Camelcase disabled due to emoji-datasource using snake_case /* eslint-disable camelcase */ -import { - compact, - flatMap, - groupBy, - keyBy, - map, - mapValues, - sortBy, -} from 'lodash'; +import { groupBy, keyBy, mapValues, sortBy } from 'lodash'; import { getOwn } from '../../util/getOwn'; import { EMOJI_SKIN_TONE_TO_KEY, @@ -152,15 +144,6 @@ export function getEmojiData( return base; } -const shortNames = new Set([ - ...map(data, 'short_name'), - ...compact(flatMap(data, 'short_names')), -]); - -export function isShortName(name: string): boolean { - return shortNames.has(name); -} - export function unifiedToEmoji(unified: string): string { return unified .split('-') diff --git a/ts/components/fun/FunButton.tsx b/ts/components/fun/FunButton.tsx index 3d1e48544d..eb23167e79 100644 --- a/ts/components/fun/FunButton.tsx +++ b/ts/components/fun/FunButton.tsx @@ -57,7 +57,7 @@ export function FunEmojiPickerButton( ) : ( diff --git a/ts/components/fun/data/emojis.ts b/ts/components/fun/data/emojis.ts index 85350feedf..e0bb80fb0a 100644 --- a/ts/components/fun/data/emojis.ts +++ b/ts/components/fun/data/emojis.ts @@ -252,7 +252,10 @@ type EmojiIndex = Readonly<{ pickerCategories: Record>; defaultEnglishSearchIndex: Array; - defaultEnglishLocalizerIndex: Map; + defaultEnglishLocalizerIndex: { + parentKeyToLocaleShortName: Map; + localeShortNameToParentKey: Map; + }; }>; /** @internal */ @@ -288,7 +291,10 @@ const EMOJI_INDEX: EmojiIndex = { [EmojiPickerCategory.Flags]: [], }, defaultEnglishSearchIndex: [], - defaultEnglishLocalizerIndex: new Map(), + defaultEnglishLocalizerIndex: { + parentKeyToLocaleShortName: new Map(), + localeShortNameToParentKey: new Map(), + }, }; function addParent(parent: EmojiParentData, rank: number) { @@ -320,10 +326,14 @@ function addParent(parent: EmojiParentData, rank: number) { emoticons: parent.emoticons, }); - EMOJI_INDEX.defaultEnglishLocalizerIndex.set( + EMOJI_INDEX.defaultEnglishLocalizerIndex.parentKeyToLocaleShortName.set( parent.key, parent.englishShortNameDefault ); + EMOJI_INDEX.defaultEnglishLocalizerIndex.localeShortNameToParentKey.set( + parent.englishShortNameDefault, + parent.key + ); } function addVariant(parentKey: EmojiParentKey, variant: EmojiVariantData) { diff --git a/ts/components/fun/panels/FunPanelEmojis.tsx b/ts/components/fun/panels/FunPanelEmojis.tsx index 4e8479f08b..c559f07ee1 100644 --- a/ts/components/fun/panels/FunPanelEmojis.tsx +++ b/ts/components/fun/panels/FunPanelEmojis.tsx @@ -641,7 +641,7 @@ const Cell = memo(function Cell(props: CellProps): JSX.Element { ); const emojiName = useMemo(() => { - return emojiLocalizer(emojiVariant.key); + return emojiLocalizer.getLocaleShortName(emojiVariant.key); }, [emojiVariant.key, emojiLocalizer]); const emojiShortNameDisplay = useMemo(() => { diff --git a/ts/components/fun/useFunEmojiLocalizer.tsx b/ts/components/fun/useFunEmojiLocalizer.tsx index e847496e0f..6baeece5d9 100644 --- a/ts/components/fun/useFunEmojiLocalizer.tsx +++ b/ts/components/fun/useFunEmojiLocalizer.tsx @@ -12,13 +12,21 @@ import type { LocaleEmojiListType } from '../../types/emoji'; import { strictAssert } from '../../util/assert'; import { useFunEmojiLocalization } from './FunEmojiLocalizationProvider'; -export type FunEmojiLocalizerIndex = ReadonlyMap; -export type FunEmojiLocalizer = (key: EmojiVariantKey) => string; +export type FunEmojiLocalizerIndex = Readonly<{ + parentKeyToLocaleShortName: ReadonlyMap; + localeShortNameToParentKey: ReadonlyMap; +}>; + +export type FunEmojiLocalizer = Readonly<{ + getLocaleShortName: (key: EmojiVariantKey) => string; + getParentKeyForText: (text: string) => EmojiParentKey | null; +}>; export function createFunEmojiLocalizerIndex( localeEmojiList: LocaleEmojiListType ): FunEmojiLocalizerIndex { - const index = new Map(); + const parentKeyToLocaleShortName = new Map(); + const localeShortNameToParentKey = new Map(); for (const entry of localeEmojiList) { strictAssert( @@ -29,26 +37,35 @@ export function createFunEmojiLocalizerIndex( const variantKey = getEmojiVariantKeyByValue(entry.emoji); const parentKey = getEmojiParentKeyByVariantKey(variantKey); const localizedShortName = entry.tags.at(0) ?? entry.shortName; - index.set(parentKey, localizedShortName); + parentKeyToLocaleShortName.set(parentKey, localizedShortName); + localeShortNameToParentKey.set(localizedShortName, parentKey); } - return index; + return { parentKeyToLocaleShortName, localeShortNameToParentKey }; } /** @internal exported for tests */ export function _createFunEmojiLocalizer( emojiLocalizerIndex: FunEmojiLocalizerIndex ): FunEmojiLocalizer { - return variantKey => { + function getLocaleShortName(variantKey: EmojiVariantKey) { const parentKey = getEmojiParentKeyByVariantKey(variantKey); - const localeShortName = emojiLocalizerIndex.get(parentKey); + const localeShortName = + emojiLocalizerIndex.parentKeyToLocaleShortName.get(parentKey); if (localeShortName != null) { return localeShortName; } // Fallback to english short name const parent = getEmojiParentByKey(parentKey); return parent.englishShortNameDefault; - }; + } + + function getParentKeyForText(text: string): EmojiParentKey | null { + const parentKey = emojiLocalizerIndex.localeShortNameToParentKey.get(text); + return parentKey ?? null; + } + + return { getLocaleShortName, getParentKeyForText }; } export function useFunEmojiLocalizer(): FunEmojiLocalizer { diff --git a/ts/quill/emoji/completion.tsx b/ts/quill/emoji/completion.tsx index d41a2ab7af..05db9fe606 100644 --- a/ts/quill/emoji/completion.tsx +++ b/ts/quill/emoji/completion.tsx @@ -6,12 +6,11 @@ import Emitter from '@signalapp/quill-cjs/core/emitter'; import React from 'react'; import _, { isNumber } from 'lodash'; import type Quill from '@signalapp/quill-cjs'; - import { Popper } from 'react-popper'; import classNames from 'classnames'; import { createPortal } from 'react-dom'; import type { VirtualElement } from '@popperjs/core'; -import { convertShortName, isShortName } from '../../components/emoji/lib'; +import { convertShortName } from '../../components/emoji/lib'; import type { EmojiPickDataType } from '../../components/emoji/EmojiPicker'; import { getBlotTextPartitions, matchBlotTextPartitions } from '../util'; import { handleOutsideClick } from '../../util/handleOutsideClick'; @@ -23,8 +22,10 @@ import { getEmojiVariantByParentKeyAndSkinTone, normalizeShortNameCompletionDisplay, } from '../../components/fun/data/emojis'; -import type { FunEmojiSearchResult } from '../../components/fun/useFunEmojiSearch'; -import { type FunEmojiSearch } from '../../components/fun/useFunEmojiSearch'; +import type { + FunEmojiSearchResult, + FunEmojiSearch, +} from '../../components/fun/useFunEmojiSearch'; import { type FunEmojiLocalizer } from '../../components/fun/useFunEmojiLocalizer'; export type EmojiCompletionOptions = { @@ -164,11 +165,14 @@ export class EmojiCompletion { const [, leftTokenText, isSelfClosing] = leftTokenTextMatch; if (isSelfClosing || justPressedColon) { - if (isShortName(leftTokenText)) { + const parentKey = + this.options.emojiLocalizer.getParentKeyForText(leftTokenText); + if (parentKey != null) { const numberOfColons = isSelfClosing ? 2 : 1; + const emoji = getEmojiParentByKey(parentKey); this.insertEmoji({ - shortName: leftTokenText, + shortName: emoji.englishShortNameDefault, index: range.index - leftTokenText.length - numberOfColons, range: leftTokenText.length + numberOfColons, justPressedColon, @@ -182,10 +186,13 @@ export class EmojiCompletion { if (rightTokenTextMatch) { const [, rightTokenText] = rightTokenTextMatch; const tokenText = leftTokenText + rightTokenText; + const parentKey = + this.options.emojiLocalizer.getParentKeyForText(tokenText); - if (isShortName(tokenText)) { + if (parentKey != null) { + const emoji = getEmojiParentByKey(parentKey); this.insertEmoji({ - shortName: tokenText, + shortName: emoji.englishShortNameDefault, index: range.index - leftTokenText.length - 1, range: tokenText.length + 2, justPressedColon, @@ -377,9 +384,10 @@ export class EmojiCompletion { this.options.emojiSkinToneDefault ?? EmojiSkinTone.None ); - const localeShortName = this.options.emojiLocalizer( - emojiVariant.key - ); + const localeShortName = + this.options.emojiLocalizer.getLocaleShortName( + emojiVariant.key + ); const normalized = normalizeShortNameCompletionDisplay(localeShortName); diff --git a/ts/state/selectors/emojis.ts b/ts/state/selectors/emojis.ts index b3cac523ea..74b33dcee7 100644 --- a/ts/state/selectors/emojis.ts +++ b/ts/state/selectors/emojis.ts @@ -3,13 +3,12 @@ import { createSelector } from 'reselect'; import { useSelector } from 'react-redux'; - import type { StateType } from '../reducer'; -import { isShortName } from '../../components/emoji/lib'; +import { isEmojiEnglishShortName } from '../../components/fun/data/emojis'; export const selectRecentEmojis = createSelector( ({ emojis }: StateType) => emojis.recents, - recents => recents.filter(isShortName) + recents => recents.filter(isEmojiEnglishShortName) ); export const useRecentEmojis = (): Array =>