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 === '') ; if (customBody) { if (customBody === '') ; return parsedCustomBody; } return {plainBody}; }