import type { FC } from 'react'; import { useContext, useEffect, useState } from 'react'; import classNames from 'classnames'; import { EMOJI_TYPE_CUSTOM, EMOJI_TYPE_UNICODE, } from '@/mastodon/features/emoji/constants'; import { useEmojiAppState } from '@/mastodon/features/emoji/mode'; import { emojiToInversionClassName, unicodeHexToUrl, } from '@/mastodon/features/emoji/normalize'; import { isStateLoaded, loadEmojiDataToState, shouldRenderImage, stringToEmojiState, tokenizeText, } from '@/mastodon/features/emoji/render'; import type { ExtraCustomEmojiMap } from '@/mastodon/features/emoji/types'; import { AnimateEmojiContext, CustomEmojiContext } from './context'; interface EmojiProps { code: string; showFallback?: boolean; showLoading?: boolean; customEmoji?: ExtraCustomEmojiMap | null; } export const Emoji: FC = ({ code, showFallback = true, showLoading = true, customEmoji: customEmojiOverride, }) => { let customEmoji = useContext(CustomEmojiContext); if (customEmojiOverride) { customEmoji = customEmojiOverride; } // First, set the emoji state based on the input code. const [state, setState] = useState(() => stringToEmojiState(code, customEmoji), ); // If we don't have data, then load emoji data asynchronously. const appState = useEmojiAppState(); useEffect(() => { if (state !== null) { void loadEmojiDataToState(state, appState.currentLocale).then(setState); } }, [appState.currentLocale, state]); const animate = useContext(AnimateEmojiContext); const fallback = showFallback ? code : null; // If the code is invalid or we otherwise know it's not valid, show the fallback. if (!state) { return fallback; } if (!isStateLoaded(state)) { if (showLoading) { return ; } return fallback; } const inversionClass = state.type === EMOJI_TYPE_UNICODE && emojiToInversionClassName(state.data.unicode); if (!shouldRenderImage(state, appState.mode)) { if (state.type === EMOJI_TYPE_UNICODE) { return state.data.unicode; } return code; } if (state.type === EMOJI_TYPE_CUSTOM) { const shortcode = `:${state.code}:`; return ( {shortcode} ); } const src = unicodeHexToUrl({ unicodeHex: state.code, ...appState, }); return ( {state.data.unicode} ); }; /** * Takes a text string and converts it to an array of React nodes. * @param text The text to be tokenized and converted. */ export function textToEmojis(text: string) { return tokenizeText(text).map((token, index) => { if (typeof token === 'string') { return token; } return ; }); }