feat: add markdown rendering support with marked and integrate into Tex component
Test / backend (push) Successful in 13s
Test / frontend (push) Failing after 21s

This commit is contained in:
LaurensBartelds committed 2026-05-02 19:57:42 +02:00
1 parent e906951b06
commit b58500901a
10 files changed
+112 -6

No files matched your search

+1 -1
View File
@@ -242,7 +242,7 @@ export default function ExamRunner({
</span>
</header>
<Tex tag="div" className="q-prompt">
<Tex tag="div" className="q-prompt" markdown>
{q.prompt}
</Tex>
<QuestionMedia q={q} />
+1 -1
View File
@@ -200,7 +200,7 @@ export default function Results({ exam, state, onRestart, onExit, onRedoFailed,
{openIdx === i && (
<div className="review-body">
<div className="q-prompt" style={{ fontSize: 20, marginBottom: 20 }}>
<Tex>{q.prompt}</Tex>
<Tex markdown>{q.prompt}</Tex>
</div>
<QuestionMedia q={q} />
<QuestionRenderer key={q.id} q={q} value={answers[q.id]} onChange={() => {}} locked graded />
+8 -2
View File
@@ -1,16 +1,22 @@
import type React from 'react'
import { renderLatex } from '../utils/latex'
import { renderMarkdown } from '../utils/markdown'
interface TexProps {
children?: string | null
tag?: keyof React.JSX.IntrinsicElements
className?: string
style?: React.CSSProperties
markdown?: boolean
}
// Safe: renderLatex only produces KaTeX-controlled HTML + HTML-escaped plain text.
export default function Tex({ children, tag = 'span', className, style }: Readonly<TexProps>) {
// When markdown is true, uses renderMarkdown which applies marked + KaTeX + DOMPurify.
export default function Tex({ children, tag = 'span', className, style, markdown }: Readonly<TexProps>) {
const Tag = tag as React.ElementType
const html = String(children ?? '').split('\n').map(renderLatex).join('<br/>')
const text = String(children ?? '')
const html = markdown
? renderMarkdown(text)
: text.split('\n').map(renderLatex).join('<br/>')
return <Tag className={className} style={style} dangerouslySetInnerHTML={{ __html: html }} />
}
@@ -1,3 +1,4 @@
import Tex from '../Tex'
import type { Question } from '../../types'
interface Props {
@@ -23,7 +24,7 @@ export default function Flashcard({ q, value, onChange, locked }: Props) {
return (
<div className="flashcard-wrap">
<div className="flashcard-prompt">
{q.prompt}
<Tex markdown>{q.prompt}</Tex>
</div>
{!revealed ? (
@@ -1,3 +1,4 @@
import Tex from '../Tex'
import type { Question } from '../../types'
interface Props {
@@ -9,6 +10,7 @@ interface Props {
}
export default function GrammarCorrection({ q, value = '', onChange, locked, graded }: Props) {
const displayText = q.textToCorrect || q.prompt
return (
<div className="answer-input-wrap">
<div className="grammar-text-wrap">
@@ -16,7 +18,7 @@ export default function GrammarCorrection({ q, value = '', onChange, locked, gra
Sentence to correct:
</div>
<div className="grammar-sentence">
{q.textToCorrect || q.prompt}
<Tex markdown>{displayText}</Tex>
</div>
</div>
<input