[Glitch] Adds new HTMLBlock component

Port e07b9dfdc1 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
This commit is contained in:
Echo
2025-09-26 11:50:59 +02:00
committed by Claire
parent 106b0a9d93
commit 8cccabb714
6 changed files with 208 additions and 77 deletions

View File

@@ -0,0 +1,40 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import { expect } from 'storybook/test';
import { HTMLBlock } from './index';
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>',
},
render(args) {
return (
// Just for visual clarity in Storybook.
<div
style={{
border: '1px solid black',
padding: '1rem',
minWidth: '300px',
}}
>
<HTMLBlock {...args} />
</div>
);
},
} satisfies Meta<typeof HTMLBlock>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
async play({ canvas }) {
const link = canvas.queryByRole('link');
await expect(link).toBeInTheDocument();
const button = canvas.queryByRole('button');
await expect(button).not.toBeInTheDocument();
},
};

View File

@@ -0,0 +1,50 @@
import type { FC, ReactNode } from 'react';
import { useMemo } 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 { htmlStringToComponents } from '../../utils/html';
// 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 },
});
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;
}