(({ content, className }, ref) => {
const router = useRouter();
const remarkPlugins = useMemo((): PluggableList => {
return [
remarkGfm,
remarkReferencesPlugin,
remarkTocExtractor,
];
}, []);
const rehypePlugins = useMemo((): PluggableList => {
return [
rehypeRaw,
[
rehypeSanitize,
{
...defaultSchema,
attributes: {
...defaultSchema.attributes,
span: [...(defaultSchema.attributes?.span ?? []), 'role', 'className', 'data*'],
},
strip: [],
} satisfies SanitizeSchema,
],
annotateCodeBlocks,
];
}, []);
const renderPre = useCallback(({ children, node, ...rest }: React.JSX.IntrinsicElements['pre'] & { node?: Element }) => {
if (node?.properties && node.properties.isBlock === true) {
return children;
}
return (
{children}
)
}, []);
const renderCode = useCallback(({ className, children, node, ...rest }: React.JSX.IntrinsicElements['code'] & { node?: Element }) => {
const text = children?.toString().trimEnd() ?? '';
if (node?.properties && node.properties.isBlock === true) {
const match = /language-(\w+)/.exec(className || '');
const language = match ? match[1] : undefined;
return (
)
}
return (
{children}
{/* Invisible bridge to prevent hover gap */}
)
}, [router]);
return (
*:first-child]:mt-0", className)}
>
{content}
);
});
MarkdownRendererComponent.displayName = 'MarkdownRenderer';
export const MarkdownRenderer = memo(MarkdownRendererComponent, isEqual);