[Glitch] Emoji Rendering Efficiency

Port 6bca52453a to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
This commit is contained in:
Echo
2025-07-31 19:30:14 +02:00
committed by Claire
parent c5144c0c7d
commit 0d54a47ec3
18 changed files with 891 additions and 324 deletions

View File

@@ -1,81 +1,31 @@
import type { HTMLAttributes } from 'react';
import { useEffect, useMemo, useState } from 'react';
import type { ComponentPropsWithoutRef, ElementType } from 'react';
import type { List as ImmutableList } from 'immutable';
import { isList } from 'immutable';
import { useEmojify } from './hooks';
import type { CustomEmojiMapArg } from './types';
import type { ApiCustomEmojiJSON } from '@/flavours/glitch/api_types/custom_emoji';
import type { CustomEmoji } from '@/flavours/glitch/models/custom_emoji';
import { isModernEmojiEnabled } from '@/flavours/glitch/utils/environment';
import { useEmojiAppState } from './hooks';
import { emojifyElement } from './render';
import type { ExtraCustomEmojiMap } from './types';
type EmojiHTMLProps = Omit<
HTMLAttributes<HTMLDivElement>,
type EmojiHTMLProps<Element extends ElementType = 'div'> = Omit<
ComponentPropsWithoutRef<Element>,
'dangerouslySetInnerHTML'
> & {
htmlString: string;
extraEmojis?: ExtraCustomEmojiMap | ImmutableList<CustomEmoji>;
extraEmojis?: CustomEmojiMapArg;
as?: Element;
};
export const EmojiHTML: React.FC<EmojiHTMLProps> = ({
htmlString,
export const EmojiHTML = <Element extends ElementType>({
extraEmojis,
htmlString,
as: asElement, // Rename for syntax highlighting
...props
}) => {
if (isModernEmojiEnabled()) {
return (
<ModernEmojiHTML
htmlString={htmlString}
extraEmojis={extraEmojis}
{...props}
/>
);
}
return <div dangerouslySetInnerHTML={{ __html: htmlString }} {...props} />;
};
}: EmojiHTMLProps<Element>) => {
const Wrapper = asElement ?? 'div';
const emojifiedHtml = useEmojify(htmlString, extraEmojis);
const ModernEmojiHTML: React.FC<EmojiHTMLProps> = ({
extraEmojis: rawEmojis,
htmlString: text,
...props
}) => {
const appState = useEmojiAppState();
const [innerHTML, setInnerHTML] = useState('');
const extraEmojis: ExtraCustomEmojiMap = useMemo(() => {
if (!rawEmojis) {
return {};
}
if (isList(rawEmojis)) {
return (
rawEmojis.toJS() as ApiCustomEmojiJSON[]
).reduce<ExtraCustomEmojiMap>(
(acc, emoji) => ({ ...acc, [emoji.shortcode]: emoji }),
{},
);
}
return rawEmojis;
}, [rawEmojis]);
useEffect(() => {
if (!text) {
return;
}
const cb = async () => {
const div = document.createElement('div');
div.innerHTML = text;
const ele = await emojifyElement(div, appState, extraEmojis);
setInnerHTML(ele.innerHTML);
};
void cb();
}, [text, appState, extraEmojis]);
if (!innerHTML) {
if (emojifiedHtml === null) {
return null;
}
return <div {...props} dangerouslySetInnerHTML={{ __html: innerHTML }} />;
return (
<Wrapper {...props} dangerouslySetInnerHTML={{ __html: emojifiedHtml }} />
);
};