/ tutorials / laplace-dirichlet
Sign in
tutorials / laplace-dirichlet
laplace-dirichlet / src / ui / Markdown.tsx
35 lines · 1.5 KBBlameHistoryRaw
1// Prose is written as markdown with TeX between dollar signs. KaTeX renders
2// the maths first, into placeholders that marked will not touch, and the
3// result is put in with dangerouslySetInnerHTML: the source is ours, written
4// at build time, not anything a reader supplies.
5import { useMemo } from 'react'
6import { marked } from 'marked'
7import katex from 'katex'
8import 'katex/dist/katex.min.css'
10function renderMath(src: string): { text: string; slots: string[] } {
11 const slots: string[] = []
12 const keep = (html: string) => {
13 slots.push(html)
14 return `@@MATH${slots.length - 1}@@`
15 }
16 // display maths first, so that $$...$$ is not eaten by the inline rule
17 let text = src.replace(/\$\$([\s\S]+?)\$\$/g, (_m, tex: string) =>
18 keep(katex.renderToString(tex.trim(), { displayMode: true, throwOnError: false })),
19 )
20 text = text.replace(/(^|[^\\$])\$([^$\n]+?)\$/g, (_m, pre: string, tex: string) =>
21 pre + keep(katex.renderToString(tex.trim(), { displayMode: false, throwOnError: false })),
22 )
23 return { text, slots }
26export function markdownToHtml(src: string): string {
27 const { text, slots } = renderMath(src)
28 const html = marked.parse(text, { async: false }) as string
29 return html.replace(/@@MATH(\d+)@@/g, (_m, i: string) => slots[Number(i)])
32export default function Markdown({ src }: { src: string }) {
33 const html = useMemo(() => markdownToHtml(src), [src])
34 return <div className="prose" dangerouslySetInnerHTML={{ __html: html }} />
↑↓ move↵openescclose