import ReactMarkdown, { type UrlTransform } from 'react-markdown'; import rehypeRaw from 'rehype-raw'; import rehypeSanitize, { defaultSchema } from 'rehype-sanitize'; import remarkGfm from 'remark-gfm'; // rehype-sanitize defaults are conservative — markdown authors lean on raw // HTML for layout (centered headers, collapsible sections, image // dimensions). Extend the schema to permit those tags/attributes while // still blocking script-y or interactive content (iframe, object, etc.). const sanitizeSchema = { ...defaultSchema, tagNames: [ ...(defaultSchema.tagNames ?? []), 'details', 'summary', 'picture', 'source', 'kbd', 'sub', 'sup', 'mark', 'abbr', 'cite', 'figure', 'figcaption', 'center', ], attributes: { ...defaultSchema.attributes, '*': [ ...((defaultSchema.attributes && defaultSchema.attributes['*']) || []), 'align', 'style', ], a: [ ...((defaultSchema.attributes && defaultSchema.attributes.a) || []), 'target', 'rel', ], img: [ ...((defaultSchema.attributes && defaultSchema.attributes.img) || []), 'width', 'height', 'align', 'srcset', ], source: ['srcset', 'media', 'type'], details: ['open'], }, }; interface Props { text: string; /** Rewrite URLs (e.g. resolve relative image paths to forge raw URLs). */ urlTransform?: UrlTransform; } /** GFM + sanitized embedded HTML, shared by READMEs and blog posts. */ export function Markdown({ text, urlTransform }: Props) { return ( {text} ); }