Barazo default frontend barazo.forum
at main 87 lines 2.1 kB view raw
1/** 2 * MarkdownContent - Renders markdown content with DOMPurify sanitization. 3 * Used for topic and reply content display. 4 * @see specs/prd-web.md Section 4 (Topic Components) 5 */ 6 7import { sanitize } from 'isomorphic-dompurify' 8import { marked } from 'marked' 9import { cn } from '@/lib/utils' 10 11interface MarkdownContentProps { 12 content: string 13 className?: string 14} 15 16// Configure marked for safe defaults 17marked.setOptions({ 18 breaks: true, 19 gfm: true, 20}) 21 22// Configure marked renderer for links 23const renderer = new marked.Renderer() 24renderer.link = ({ href, text }: { href: string; text: string }) => { 25 return `<a href="${href}" rel="noopener noreferrer">${text}</a>` 26} 27 28marked.use({ renderer }) 29 30/** 31 * Renders markdown content, sanitized against XSS. 32 * Supports: headings, bold, italic, links, code blocks, lists, blockquotes. 33 */ 34export function MarkdownContent({ content, className }: MarkdownContentProps) { 35 const rawHtml = marked.parse(content, { async: false }) as string 36 37 const cleanHtml = sanitize(rawHtml, { 38 ALLOWED_TAGS: [ 39 'p', 40 'br', 41 'strong', 42 'em', 43 'a', 44 'code', 45 'pre', 46 'blockquote', 47 'ul', 48 'ol', 49 'li', 50 'h1', 51 'h2', 52 'h3', 53 'h4', 54 'h5', 55 'h6', 56 'hr', 57 'img', 58 'table', 59 'thead', 60 'tbody', 61 'tr', 62 'th', 63 'td', 64 'del', 65 'sup', 66 'sub', 67 'span', 68 ], 69 ALLOWED_ATTR: ['href', 'src', 'alt', 'title', 'class', 'rel', 'target'], 70 ALLOW_DATA_ATTR: false, 71 }) 72 73 return ( 74 <div 75 className={cn( 76 'prose prose-sm max-w-none dark:prose-invert', 77 'prose-headings:text-foreground prose-p:text-foreground', 78 'prose-a:text-primary prose-a:underline prose-a:decoration-primary/50 hover:prose-a:decoration-primary', 79 'prose-code:rounded prose-code:bg-muted prose-code:px-1 prose-code:py-0.5 prose-code:text-sm', 80 'prose-pre:bg-muted prose-pre:rounded-lg', 81 'prose-blockquote:border-l-primary', 82 className 83 )} 84 dangerouslySetInnerHTML={{ __html: cleanHtml }} 85 /> 86 ) 87}