50 lines
1.8 KiB
TypeScript
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>;
|
|
}
|