/* global React */ // ============================================================== // questions.jsx — renderers for every question type // ============================================================== var { useState, useEffect, useRef, useMemo } = React; // ---------- Media blocks (image, code, table, audio) ---------- const QuestionMedia = ({ q }) => ( <> {q.image && (
{q.image.alt {q.image.caption &&
{q.image.caption}
}
)} {q.audio && (
)} {q.code && (
    )}
    {q.table && (
      {q.table.headers.map((h, i) => )}
          {q.table.rows.map((row, i) => (
            {row.map((cell, j) => )}
          ))}
        
{h}
{cell}
)} {q.svg &&
} ); // ---------- Multiple Choice ---------- const MultipleChoice = ({ q, value, onChange, locked, graded }) => (
{q.choices.map((c, i) => { const isSelected = value === c.id; let cls = 'choice'; if (isSelected) cls += ' selected'; if (graded) { if (c.id === q.answer) cls += ' correct'; else if (isSelected) cls += ' wrong'; } return ( ); })}
); // ---------- Multiple Select ---------- const MultipleSelect = ({ q, value = [], onChange, locked, graded }) => { const toggle = (id) => { if (locked) return; onChange(value.includes(id) ? value.filter((v) => v !== id) : [...value, id]); }; const answerSet = new Set(q.answer || []); return (
{q.choices.map((c, i) => { const isSelected = value.includes(c.id); let cls = 'choice'; if (isSelected) cls += ' selected'; if (graded) { if (answerSet.has(c.id)) cls += ' correct'; else if (isSelected) cls += ' wrong'; } return ( ); })}
); }; // ---------- Short Answer ---------- const ShortAnswer = ({ q, value = '', onChange, locked }) => (
onChange(e.target.value)} placeholder="Type your answer…" disabled={locked} autoComplete="off" spellCheck={q.type === 'translation'} /> {q.type === 'translation' && q.targetLanguage && (
Answer in {q.targetLanguage}
)}
); // ---------- Math Input ---------- const MathInput = ({ q, value = '', onChange, locked }) => { const [preview, setPreview] = useState(''); useEffect(() => { if (!value) { setPreview(''); return; } try { setPreview(window.katex.renderToString(value, { throwOnError: false, displayMode: false })); } catch { setPreview(''); } }, [value]); return (
onChange(e.target.value)} placeholder="LaTeX, e.g. 2x\ln x + x" disabled={locked} autoComplete="off" spellCheck={false} />
Preview {preview ? : Live LaTeX preview appears here}
); }; // ---------- Essay ---------- const Essay = ({ q, value = {}, onChange, locked }) => { const text = typeof value === 'string' ? value : (value.text || ''); const wordCount = text.trim() ? text.trim().split(/\s+/).length : 0; const minOK = !q.minWords || wordCount >= q.minWords; return (