vojo/src/app/components/message/RenderBody.tsx

50 lines
1.8 KiB
TypeScript

import React, { useMemo } from 'react';
import parse, { HTMLReactParserOptions } from 'html-react-parser';
import Linkify from 'linkify-react';
import { Opts } from 'linkifyjs';
import { MessageEmptyContent } from './content';
import { sanitizeCustomHtml } from '../../utils/sanitize';
import { highlightText, scaleSystemEmoji } from '../../plugins/react-custom-html-parser';
type RenderBodyProps = {
body: string;
customBody?: string;
highlightRegex?: RegExp;
htmlReactParserOptions: HTMLReactParserOptions;
linkifyOpts: Opts;
};
export function RenderBody({
body,
customBody,
highlightRegex,
htmlReactParserOptions,
linkifyOpts,
}: RenderBodyProps) {
// `sanitizeCustomHtml` (a full sanitize-html DOM pass) + `parse` are the
// hottest per-render cost on the timeline — RoomTimeline re-renders every
// visible Message on each live event. Both `customBody` and the memoized
// `htmlReactParserOptions` (RoomTimeline.tsx:654) are stable across those
// renders, so caching the parsed tree skips the work for unchanged rows.
const parsedCustomBody = useMemo(
() => (customBody ? parse(sanitizeCustomHtml(customBody), htmlReactParserOptions) : null),
[customBody, htmlReactParserOptions]
);
// Plaintext path: scaleSystemEmoji walks the string for unicode emoji and
// highlightText re-splits on the search regex — both pure functions of
// (body, highlightRegex), so memoize to match the custom-body fast path.
const plainBody = useMemo(
() =>
highlightRegex
? highlightText(highlightRegex, scaleSystemEmoji(body))
: scaleSystemEmoji(body),
[body, highlightRegex]
);
if (body === '') <MessageEmptyContent />;
if (customBody) {
if (customBody === '') <MessageEmptyContent />;
return parsedCustomBody;
}
return <Linkify options={linkifyOpts}>{plainBody}</Linkify>;
}