import { useMemo } from 'react'
import { Link, useParams } from 'react-router-dom'
import DOMPurify from 'dompurify'
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: sanitize (defense in depth — the server also
// sanitizes on save), assign ids to
headings and collect a TOC, and mark
// internal links to pages that don't exist as "red links".
function buildArticle(body, missing) {
if (!body) return { html: '', toc: [] }
if (typeof window === 'undefined' || !window.DOMParser) return { html: '', toc: [] }
const safe = DOMPurify.sanitize(body)
const doc = new DOMParser().parseFromString(safe, '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 })
})
doc.querySelectorAll('a[href^="/wiki/"]').forEach((a) => {
const target = a.getAttribute('href').replace(/^\/wiki\//, '').replace(/[#?].*$/, '')
a.removeAttribute('target') // internal links stay in-app
if (missing.has(target)) {
a.classList.add('wiki-red-link')
a.setAttribute('title', 'This page does not exist yet')
}
})
return { html: doc.body.innerHTML, toc }
}
export default function WikiArticle() {
const { slug } = useParams()
const { loading, error, data: page } = useAsync(() => api.wikiPage(slug), [slug])
const missing = useMemo(() => new Set(page?.missing_links || []), [page])
const { html, toc } = useMemo(() => buildArticle(page?.body, missing), [page, missing])
return (
{loading && }
{error && (
)}
{page && (
{toc.length > 0 && (
)}
Wiki
{page.category_title && (
<>
/
{page.category_title}
>
)}
/{page.title}