feat: add QuestionMedia with DOMPurify SVG sanitization

This commit is contained in:
LaurensBartelds committed 2026-04-21 21:37:27 +02:00
1 parent b0efb524b1
commit b5e53c97e6
1 file changed
+53
+53
View File
@@ -0,0 +1,53 @@
import DOMPurify from 'dompurify'
import { highlightCode } from '../utils/latex'
import type { Question } from '../types'
interface Props {
q: Question
}
export default function QuestionMedia({ q }: Props) {
return (
<>
{q.image && (
<figure className="q-media">
<img src={q.image.src} alt={q.image.alt ?? ''} />
{q.image.caption && (
<figcaption className="q-media-caption">{q.image.caption}</figcaption>
)}
</figure>
)}
{q.audio && (
<div className="q-audio">
<audio controls src={q.audio.src}>Your browser does not support audio.</audio>
</div>
)}
{q.code && (
// highlightCode runs escapeHtml on the source first — output is safe
<pre
className="q-code"
dangerouslySetInnerHTML={{ __html: highlightCode(q.code.content, q.code.language) }}
/>
)}
{q.table && (
<table className="q-table">
<thead>
<tr>{q.table.headers.map((h, i) => <th key={i}>{h}</th>)}</tr>
</thead>
<tbody>
{q.table.rows.map((row, i) => (
<tr key={i}>{row.map((cell, j) => <td key={j}>{cell}</td>)}</tr>
))}
</tbody>
</table>
)}
{q.svg && (
// SVG is user-provided — sanitize with DOMPurify before rendering
<div
className="q-media"
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(q.svg) }}
/>
)}
</>
)
}