[Glitch] Emoji: Cleanup new code

Port 0c64e7f75e to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
This commit is contained in:
Echo
2025-10-14 11:36:25 +02:00
committed by Claire
parent a7f207604a
commit 8d1e67b6b2
14 changed files with 327 additions and 657 deletions

View File

@@ -7,23 +7,49 @@ const meta = {
title: 'Components/HTMLBlock',
component: HTMLBlock,
args: {
contents:
'<p>Hello, world!</p>\n<p><a href="#">A link</a></p>\n<p>This should be filtered out: <button>Bye!</button></p>',
htmlString: `<p>Hello, world!</p>
<p><a href="#">A link</a></p>
<p>This should be filtered out: <button>Bye!</button></p>
<p>This also has emoji: 🖤</p>`,
},
argTypes: {
extraEmojis: {
table: {
disable: true,
},
},
onElement: {
table: {
disable: true,
},
},
onAttribute: {
table: {
disable: true,
},
},
},
render(args) {
return (
// Just for visual clarity in Storybook.
<div
<HTMLBlock
{...args}
style={{
border: '1px solid black',
padding: '1rem',
minWidth: '300px',
}}
>
<HTMLBlock {...args} />
</div>
/>
);
},
// Force Twemoji to demonstrate emoji rendering.
parameters: {
state: {
meta: {
emoji_style: 'twemoji',
},
},
},
} satisfies Meta<typeof HTMLBlock>;
export default meta;

View File

@@ -1,50 +1,30 @@
import type { FC, ReactNode } from 'react';
import { useMemo } from 'react';
import { useCallback } from 'react';
import { cleanExtraEmojis } from '@/flavours/glitch/features/emoji/normalize';
import type { CustomEmojiMapArg } from '@/flavours/glitch/features/emoji/types';
import { createLimitedCache } from '@/flavours/glitch/utils/cache';
import type { OnElementHandler } from '@/flavours/glitch/utils/html';
import { polymorphicForwardRef } from '@/types/polymorphic';
import { htmlStringToComponents } from '../../utils/html';
import type { EmojiHTMLProps } from '../emoji/html';
import { ModernEmojiHTML } from '../emoji/html';
import { useElementHandledLink } from '../status/handled_link';
// Use a module-level cache to avoid re-rendering the same HTML multiple times.
const cache = createLimitedCache<ReactNode>({ maxSize: 1000 });
interface HTMLBlockProps {
contents: string;
extraEmojis?: CustomEmojiMapArg;
}
export const HTMLBlock: FC<HTMLBlockProps> = ({
contents: raw,
extraEmojis,
}) => {
const customEmojis = useMemo(
() => cleanExtraEmojis(extraEmojis),
[extraEmojis],
);
const contents = useMemo(() => {
const key = JSON.stringify({ raw, customEmojis });
if (cache.has(key)) {
return cache.get(key);
}
const rendered = htmlStringToComponents(raw, {
onText,
extraArgs: { customEmojis },
export const HTMLBlock = polymorphicForwardRef<
'div',
EmojiHTMLProps & Parameters<typeof useElementHandledLink>[0]
>(
({
onElement: onParentElement,
hrefToMention,
hashtagAccountId,
...props
}) => {
const { onElement: onLinkElement } = useElementHandledLink({
hrefToMention,
hashtagAccountId,
});
cache.set(key, rendered);
return rendered;
}, [raw, customEmojis]);
return contents;
};
function onText(
text: string,
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- Doesn't do anything, just showing how typing would work.
{ customEmojis }: { customEmojis: CustomEmojiMapArg | null },
) {
return text;
}
const onElement: OnElementHandler = useCallback(
(...args) => onParentElement?.(...args) ?? onLinkElement(...args),
[onLinkElement, onParentElement],
);
return <ModernEmojiHTML {...props} onElement={onElement} />;
},
);