import { lazy, Suspense, useEffect, useState } from 'react' import Modal from '../../../components/Modal.jsx' import WikiHistory from './WikiHistory.jsx' import { api } from '../../../api/client.js' // Admin-only and heavy (TipTap) — load as its own chunk so the public bundle // never pays for it. const RichTextEditor = lazy(() => import('../../../components/RichTextEditor.jsx')) export default function WikiEditor({ slug, onClose, onSaved }) { const isEdit = Boolean(slug) const [form, setForm] = useState({ slug: '', title: '', body: '', excerpt: '', category_id: '', published: true, tags: '', }) const [categories, setCategories] = useState([]) const [pages, setPages] = useState([]) const [loading, setLoading] = useState(isEdit) const [busy, setBusy] = useState(false) const [error, setError] = useState('') const [showHistory, setShowHistory] = useState(false) // Categories (dropdown) + pages (internal-link picker), for both new and edit. useEffect(() => { let active = true api.admin .listWikiCategories() .then((cats) => active && setCategories(cats)) .catch(() => {}) api.admin .listWiki() .then((list) => active && setPages(list.map((p) => ({ slug: p.slug, title: p.title })))) .catch(() => {}) return () => { active = false } }, []) useEffect(() => { if (!isEdit) return let active = true api.admin .getWiki(slug) .then( (p) => active && setForm({ slug: p.slug, title: p.title, body: p.body || '', excerpt: p.excerpt || '', category_id: p.category_id != null ? String(p.category_id) : '', published: Boolean(p.published), tags: (p.tags || []).map((t) => t.label).join(', '), }), ) .catch(() => active && setError('Could not load this page.')) .finally(() => active && setLoading(false)) return () => { active = false } }, [slug, isEdit]) const set = (k) => (e) => setForm((f) => ({ ...f, [k]: e.target.value })) function payload() { return { title: form.title.trim(), body: form.body, excerpt: form.excerpt.trim(), category_id: form.category_id ? Number(form.category_id) : null, published: form.published, tags: form.tags .split(',') .map((t) => t.trim()) .filter(Boolean), } } async function save() { if (!form.title.trim()) return setError('Title is required.') if (!isEdit && !/^[a-z0-9-]+$/.test(form.slug)) { return setError('Slug must be lowercase letters, numbers, and dashes.') } setBusy(true) setError('') try { if (isEdit) await api.admin.updateWiki(slug, payload()) else await api.admin.createWiki({ slug: form.slug, ...payload() }) onSaved() } catch (err) { setError(err.message || 'Could not save.') setBusy(false) } } async function remove() { if (!confirm('Delete this wiki page?')) return setBusy(true) try { await api.admin.deleteWiki(slug) onSaved() } catch (err) { setError(err.message || 'Could not delete.') setBusy(false) } } return ( <> {isEdit && ( )} {isEdit && ( )} } > {loading ? ( ) : (
{error &&

{error}

}
Body (use Heading 2 for table-of-contents sections) }> setForm((f) => ({ ...f, body: html }))} pages={pages.filter((p) => p.slug !== form.slug)} />
)}
{showHistory && ( setShowHistory(false)} onRestored={() => { setShowHistory(false) onSaved() }} /> )} ) } const delStyle = { border: '1px solid #6e3b38', borderRadius: 999, padding: '7px 16px', background: 'rgba(110,59,56,0.18)', color: '#d98b84', fontSize: '0.86rem', cursor: 'pointer', marginRight: 'auto', }