// A chapter: prose with its labs and figures dropped in where the text calls // for them. // // The prose is one markdown file per chapter, with a line // // // // // wherever a lab or a static figure belongs. Splitting on those markers keeps // the writing in one readable file instead of scattered through JSX. import { useMemo } from 'react' import Markdown from './Markdown.tsx' import Lab from './Lab.tsx' import type { Chapter } from './types.ts' import { FIGURES } from '../figures/index.ts' const MARKER = //gi export default function ChapterView({ chapter }: { chapter: Chapter }) { const pieces = useMemo(() => { const out: ({ md: string } | { labId: string } | { figId: string })[] = [] let last = 0 MARKER.lastIndex = 0 let m: RegExpExecArray | null while ((m = MARKER.exec(chapter.text)) !== null) { out.push({ md: chapter.text.slice(last, m.index) }) out.push(m[1] === 'fig' ? { figId: m[2] } : { labId: m[2] }) last = m.index + m[0].length } out.push({ md: chapter.text.slice(last) }) return out }, [chapter.text]) return (
{chapter.part}

{chapter.title}

{chapter.blurb}

{pieces.map((p, i) => { if ('md' in p) return p.md.trim() === '' ? null : if ('figId' in p) { const Fig = FIGURES[p.figId] if (!Fig) return

missing figure: {p.figId}

return } const lab = chapter.labs.find((l) => l.id === p.labId) if (!lab) return

missing lab: {p.labId}

return })}
) }