import { useMemo } from 'react' import { Link, useParams } from 'react-router-dom' import PublicLayout from '../../components/PublicLayout.jsx' import { Loading, ErrorState } from '../../components/PageState.jsx' import { useAsync } from '../../lib/useAsync.js' import { longDate } from '../../lib/format.js' import { api } from '../../api/client.js' function slugify(text) { return text .toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/(^-|-$)/g, '') } // Parse the stored body HTML: assign ids to

headings and collect a TOC. function buildArticle(body) { if (!body) return { html: '', toc: [] } if (typeof window === 'undefined' || !window.DOMParser) return { html: body, toc: [] } const doc = new DOMParser().parseFromString(body, 'text/html') const toc = [] doc.querySelectorAll('h2').forEach((h, i) => { const id = slugify(h.textContent || '') || `section-${i}` h.id = id toc.push({ id, label: h.textContent }) }) return { html: doc.body.innerHTML, toc } } export default function WikiArticle() { const { slug } = useParams() const { loading, error, data: page } = useAsync(() => api.wikiPage(slug), [slug]) const { html, toc } = useMemo(() => buildArticle(page?.body), [page]) return (
{loading && } {error && ( )} {page && (
{toc.length > 0 && ( )}

Wiki / {page.title}

{page.title}

Last updated {longDate(page.updated_at) || '—'}

{html ? (
) : (

This page has no content yet.

)}
)}
) }