@xiwebdesign/xicms-core 5.2.0 → 5.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/XiCMSProvider.d.ts +11 -2
- package/dist/XiCMSProvider.js +10 -5
- package/dist/XiCMSProvider.js.map +1 -1
- package/dist/admin/blockEditors/GenericEditor.js +18 -8
- package/dist/admin/blockEditors/GenericEditor.js.map +1 -1
- package/dist/admin/blockEditors/index.js +2 -2
- package/dist/admin/blockEditors/index.js.map +1 -1
- package/dist/admin/customBlocks/CustomBlocksPanel.js +12 -2
- package/dist/admin/customBlocks/CustomBlocksPanel.js.map +1 -1
- package/dist/admin/lib/pagesClient.js +4 -1
- package/dist/admin/lib/pagesClient.js.map +1 -1
- package/dist/admin/lib/upload.js +14 -7
- package/dist/admin/lib/upload.js.map +1 -1
- package/dist/admin/media/MediaManager.js +60 -28
- package/dist/admin/media/MediaManager.js.map +1 -1
- package/dist/admin/media/MediaPicker.js +25 -15
- package/dist/admin/media/MediaPicker.js.map +1 -1
- package/dist/admin/pages/PageEditor.d.ts +2 -1
- package/dist/admin/pages/PageEditor.js +16 -2
- package/dist/admin/pages/PageEditor.js.map +1 -1
- package/dist/admin/pages/PagesPanel.js +9 -5
- package/dist/admin/pages/PagesPanel.js.map +1 -1
- package/dist/admin/visual/VisualEditor.js +11 -2
- package/dist/admin/visual/VisualEditor.js.map +1 -1
- package/dist/agentFiles/files.d.ts +15 -3
- package/dist/agentFiles/files.js +57 -0
- package/dist/agentFiles/files.js.map +1 -1
- package/dist/auth/AuthProvider.js +3 -3
- package/dist/auth/AuthProvider.js.map +1 -1
- package/dist/blocks/builtins/Booking.js +1 -1
- package/dist/blocks/builtins/Booking.js.map +1 -1
- package/dist/blocks/builtins/Columns.js +1 -1
- package/dist/blocks/builtins/Columns.js.map +1 -1
- package/dist/blocks/builtins/Embed.js +2 -0
- package/dist/blocks/builtins/Embed.js.map +1 -1
- package/dist/blocks/builtins/Gallery.js +2 -2
- package/dist/blocks/builtins/Gallery.js.map +1 -1
- package/dist/blocks/builtins/Hero.js +46 -7
- package/dist/blocks/builtins/Hero.js.map +1 -1
- package/dist/blocks/builtins/ImageBlock.js +14 -3
- package/dist/blocks/builtins/ImageBlock.js.map +1 -1
- package/dist/blocks/builtins/Jobs.d.ts +2 -2
- package/dist/blocks/builtins/Jobs.js +1 -1
- package/dist/blocks/builtins/Jobs.js.map +1 -1
- package/dist/blocks/builtins/Newsletter.js +18 -5
- package/dist/blocks/builtins/Newsletter.js.map +1 -1
- package/dist/blocks/builtins/Pricing.js +3 -1
- package/dist/blocks/builtins/Pricing.js.map +1 -1
- package/dist/blocks/builtins/Sections.js +4 -4
- package/dist/blocks/builtins/Sections.js.map +1 -1
- package/dist/blocks/builtins/Slider.js +9 -4
- package/dist/blocks/builtins/Slider.js.map +1 -1
- package/dist/blocks/builtins/SymbolRef.js.map +1 -1
- package/dist/blocks/builtins/Video.js +1 -1
- package/dist/blocks/builtins/Video.js.map +1 -1
- package/dist/blocks/builtins/Widgets.js +2 -1
- package/dist/blocks/builtins/Widgets.js.map +1 -1
- package/dist/components/PageView.js +4 -3
- package/dist/components/PageView.js.map +1 -1
- package/dist/demo/index.d.ts +7 -0
- package/dist/demo/index.js +23 -0
- package/dist/demo/index.js.map +1 -0
- package/dist/index.d.ts +2 -4
- package/dist/index.js +0 -20
- package/dist/index.js.map +1 -1
- package/dist/modules/registry.d.ts +3 -4
- package/dist/modules/registry.js +2 -3
- package/dist/modules/registry.js.map +1 -1
- package/dist/schema/blocks.d.ts +27 -90
- package/dist/schema/blocks.js +4 -6
- package/dist/schema/blocks.js.map +1 -1
- package/dist/schema/settings.d.ts +1 -34
- package/dist/schema/settings.js +2 -8
- package/dist/schema/settings.js.map +1 -1
- package/dist/schema/siteConfig.d.ts +3 -71
- package/dist/schema/siteConfig.js +3 -8
- package/dist/schema/siteConfig.js.map +1 -1
- package/dist/schema/symbols.d.ts +2 -2
- package/dist/schema/symbols.js.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +2 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/pages.d.ts +23 -3
- package/dist/server/pages.js +37 -3
- package/dist/server/pages.js.map +1 -1
- package/dist/server/seo.js +5 -3
- package/dist/server/seo.js.map +1 -1
- package/dist/settings/useSiteSettings.d.ts +15 -3
- package/dist/settings/useSiteSettings.js +26 -5
- package/dist/settings/useSiteSettings.js.map +1 -1
- package/dist/styles.css +64 -9
- package/package.json +6 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/pages/PageEditor.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useMemo, useRef, useState, type ComponentType } from \"react\";\nimport { useRegistry } from \"../../blocks/context\";\nimport { buildBlockAddOptions } from \"../../blocks/registry\";\nimport { useModules } from \"../../modules/context\";\nimport { moduleIdForBlockType } from \"../../modules/registry\";\nimport { Button, Card, Field, Input, Textarea, Badge } from \"../ui/primitives\";\nimport { Select } from \"../ui/select\";\nimport { useToast } from \"../ui/toast\";\nimport { useConfirm } from \"../ui/ConfirmDialog\";\nimport { builtinBlockEditors, GenericEditor, type BlockEditorProps } from \"../blockEditors\";\nimport { StyleEditor } from \"../blockEditors/StyleEditor\";\nimport { PatternInserter } from \"../patterns/PatternInserter\";\nimport type { BlockStyle } from \"../../schema/blocks\";\nimport { saveDraft, publishPage, subscribeVersions, restoreVersion, type DraftPage } from \"../lib/pagesClient\";\nimport { slugify } from \"../lib/slugify\";\n\ntype Block = Record<string, unknown> & { _key: string; _type: string };\ntype SaveState = \"idle\" | \"saving\" | \"saved\" | \"error\";\n\ninterface VersionRow {\n id: string;\n createdAt?: unknown;\n kind?: string;\n label?: string;\n}\n\nfunction newKey() {\n return (globalThis.crypto?.randomUUID?.() ?? `b${Date.now()}${Math.random()}`).slice(0, 12);\n}\n\nfunction formatVersionDate(createdAt: unknown): string {\n const d =\n createdAt && typeof (createdAt as { toDate?: () => Date }).toDate === \"function\"\n ? (createdAt as { toDate: () => Date }).toDate()\n : typeof createdAt === \"string\"\n ? new Date(createdAt)\n : null;\n if (!d || Number.isNaN(d.getTime())) return \"\";\n return d.toLocaleString(\"nl-NL\", { dateStyle: \"medium\", timeStyle: \"short\" });\n}\n\nexport function PageEditor({ siteId, page }: { siteId: string; page: DraftPage }) {\n const registry = useRegistry();\n const { activeIds, defs } = useModules();\n const { toast } = useToast();\n const confirm = useConfirm();\n const [draft, setDraft] = useState<DraftPage>(page);\n const [busy, setBusy] = useState(false);\n const [saveState, setSaveState] = useState<SaveState>(\"idle\");\n const [versions, setVersions] = useState<VersionRow[]>([]);\n const firstRender = useRef(true);\n const slugTouched = useRef(Boolean(page.slug));\n\n // Wissel van pagina → herlaad lokale state.\n useEffect(() => {\n setDraft(page);\n firstRender.current = true;\n slugTouched.current = Boolean(page.slug);\n }, [page.id]); // eslint-disable-line react-hooks/exhaustive-deps\n\n useEffect(() => subscribeVersions(siteId, page.id, (v) => setVersions(v)), [siteId, page.id]);\n\n function persist(d: DraftPage) {\n setSaveState(\"saving\");\n return saveDraft(siteId, d.id, {\n title: d.title,\n slug: d.slug,\n path: d.path,\n seo: d.seo,\n blocks: d.blocks,\n order: d.order,\n })\n .then(() => setSaveState(\"saved\"))\n .catch(() => {\n setSaveState(\"error\");\n toast({ title: \"Opslaan mislukt\", description: \"Wijzigingen zijn nog niet opgeslagen.\", tone: \"error\" });\n });\n }\n\n // Autosave (debounced) bij wijzigingen.\n useEffect(() => {\n if (firstRender.current) {\n firstRender.current = false;\n return;\n }\n const t = setTimeout(() => void persist(draft), 800);\n return () => clearTimeout(t);\n }, [draft]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Waarschuw bij verlaten van de pagina met niet-opgeslagen/mislukte wijzigingen.\n useEffect(() => {\n const onBeforeUnload = (e: BeforeUnloadEvent) => {\n if (saveState === \"saving\" || saveState === \"error\") {\n e.preventDefault();\n e.returnValue = \"\";\n }\n };\n window.addEventListener(\"beforeunload\", onBeforeUnload);\n return () => window.removeEventListener(\"beforeunload\", onBeforeUnload);\n }, [saveState]);\n\n const blocks = (draft.blocks ?? []) as Block[];\n\n function update(next: DraftPage) {\n setDraft(next);\n }\n function setBlocks(next: Block[]) {\n update({ ...draft, blocks: next });\n }\n function setTitle(title: string) {\n // Auto-slug zolang de gebruiker de slug niet handmatig heeft aangeraakt.\n const slug = slugTouched.current ? draft.slug : slugify(title);\n update({ ...draft, title, slug });\n }\n function addBlock(type: string) {\n const def = registry.get(type);\n const base = def?.defaults?.() ?? {};\n setBlocks([...blocks, { _key: newKey(), _type: type, ...base } as Block]);\n }\n function addBlocks(newBlocks: Array<Record<string, unknown>>) {\n setBlocks([...blocks, ...(newBlocks as Block[])]);\n }\n function updateBlock(i: number, next: Record<string, unknown>) {\n const copy = [...blocks];\n copy[i] = { ...(next as Block), _key: blocks[i]._key, _type: blocks[i]._type };\n setBlocks(copy);\n }\n async function removeBlock(i: number) {\n const ok = await confirm({\n title: \"Blok verwijderen?\",\n description: \"Dit blok wordt uit de pagina verwijderd.\",\n confirmLabel: \"Verwijderen\",\n tone: \"danger\",\n });\n if (!ok) return;\n setBlocks(blocks.filter((_, idx) => idx !== i));\n }\n function move(i: number, dir: -1 | 1) {\n const j = i + dir;\n if (j < 0 || j >= blocks.length) return;\n const copy = [...blocks];\n [copy[i], copy[j]] = [copy[j], copy[i]];\n setBlocks(copy);\n }\n\n async function onPublish() {\n const ok = await confirm({\n title: \"Pagina publiceren?\",\n description: `De gepubliceerde site wordt bijgewerkt (${draft.path}).`,\n confirmLabel: \"Publiceren\",\n });\n if (!ok) return;\n setBusy(true);\n try {\n await saveDraft(siteId, draft.id, { title: draft.title, slug: draft.slug, path: draft.path, seo: draft.seo, blocks: draft.blocks });\n await publishPage(siteId, draft.id);\n toast({ title: \"Gepubliceerd\", description: draft.path, tone: \"success\" });\n } catch (e) {\n toast({ title: \"Publiceren mislukt\", description: (e as Error).message, tone: \"error\" });\n } finally {\n setBusy(false);\n }\n }\n\n async function onRestore(versionId: string) {\n const ok = await confirm({\n title: \"Versie herstellen?\",\n description: \"De huidige werkversie wordt overschreven met deze versie. Publiceer daarna om het live te zetten.\",\n confirmLabel: \"Herstellen\",\n });\n if (!ok) return;\n try {\n const restored = await restoreVersion(siteId, draft.id, versionId);\n setDraft({ ...draft, ...restored });\n firstRender.current = true; // herstelde versie is al opgeslagen; geen extra autosave nodig\n toast({ title: \"Versie hersteld\", description: \"Controleer en publiceer om live te zetten.\", tone: \"success\" });\n } catch (e) {\n toast({ title: \"Herstellen mislukt\", description: (e as Error).message, tone: \"error\" });\n }\n }\n\n const addOptions = useMemo(() => buildBlockAddOptions(registry.values()), [registry]);\n\n // Blocks waarvan de module is uitgeschakeld → tonen niet op de live site.\n const disabledBlocks = useMemo(() => {\n const seen = new Set<string>();\n const out: string[] = [];\n for (const b of blocks) {\n if (registry.get(b._type)) continue; // actief block-type → ok\n const modId = moduleIdForBlockType(defs, b._type);\n if (modId && !activeIds.has(modId) && !seen.has(b._type)) {\n seen.add(b._type);\n out.push(b._type);\n }\n }\n return out;\n }, [blocks, registry, defs, activeIds]);\n\n return (\n <div className=\"grid gap-4\">\n <Card>\n <div className=\"mb-3 flex flex-wrap items-center justify-between gap-2\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"text-base font-semibold\">Pagina-instellingen</h3>\n {draft.published ? <Badge tone=\"green\">gepubliceerd</Badge> : <Badge tone=\"amber\">concept</Badge>}\n </div>\n <div className=\"flex items-center gap-3\">\n <span className=\"text-xs text-slate-500\" aria-live=\"polite\">\n {saveState === \"saving\" ? \"Opslaan…\" : saveState === \"saved\" ? \"✓ Opgeslagen\" : \"\"}\n </span>\n {saveState === \"error\" && (\n <Button variant=\"secondary\" size=\"sm\" onClick={() => void persist(draft)}>\n Opnieuw opslaan\n </Button>\n )}\n <Button onClick={onPublish} disabled={busy}>\n {busy ? \"Bezig…\" : \"Publiceren\"}\n </Button>\n </div>\n </div>\n {saveState === \"error\" && (\n <div className=\"mb-3 rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-700\">\n Niet alle wijzigingen zijn opgeslagen. Controleer je verbinding en klik “Opnieuw opslaan”.\n </div>\n )}\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Titel\">\n <Input value={draft.title} onChange={(e) => setTitle(e.target.value)} />\n </Field>\n <Field label=\"Pad\" hint=\"bv. / of /over-ons\">\n <Input value={draft.path} onChange={(e) => update({ ...draft, path: e.target.value })} />\n </Field>\n <Field label=\"SEO-titel\">\n <Input value={draft.seo?.title ?? \"\"} onChange={(e) => update({ ...draft, seo: { ...draft.seo, title: e.target.value } })} />\n </Field>\n <Field label=\"SEO-omschrijving\">\n <Textarea rows={2} value={draft.seo?.description ?? \"\"} onChange={(e) => update({ ...draft, seo: { ...draft.seo, description: e.target.value } })} />\n </Field>\n </div>\n </Card>\n\n {disabledBlocks.length > 0 && (\n <div className=\"rounded-lg border border-amber-300 bg-amber-50 p-3 text-sm text-amber-800\">\n Deze pagina bevat blokken van uitgeschakelde modules ({disabledBlocks.join(\", \")}). Ze worden\n <strong> niet</strong> op de live site getoond. Zet de module aan onder Instellingen → Modules,\n of verwijder de blokken.\n </div>\n )}\n\n <div className=\"grid gap-3\">\n {blocks.map((b, i) => {\n const def = registry.get(b._type);\n const Editor: ComponentType<BlockEditorProps> =\n (def?.Editor as ComponentType<BlockEditorProps> | undefined) ?? builtinBlockEditors[b._type] ?? GenericEditor;\n return (\n <Card key={b._key}>\n <div className=\"mb-3 flex items-center justify-between\">\n <span className=\"text-sm font-semibold\">{def?.label ?? b._type}</span>\n <div className=\"flex items-center gap-1\">\n <Button variant=\"ghost\" size=\"sm\" onClick={() => move(i, -1)} aria-label=\"Omhoog\">↑</Button>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => move(i, 1)} aria-label=\"Omlaag\">↓</Button>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => void removeBlock(i)} aria-label=\"Verwijderen\">🗑</Button>\n </div>\n </div>\n <Editor block={b} siteId={siteId} onChange={(next) => updateBlock(i, next)} />\n <div className=\"mt-3\">\n <StyleEditor\n value={(b as Block & { _style?: BlockStyle })._style}\n onChange={(style) => updateBlock(i, { ...b, _style: style })}\n />\n </div>\n </Card>\n );\n })}\n {blocks.length === 0 && <p className=\"text-sm text-slate-500\">Nog geen blokken. Voeg er hieronder een toe.</p>}\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"text-sm text-slate-600\">Toevoegen:</span>\n <div className=\"w-56\">\n <Select value=\"\" placeholder=\"Kies een blok…\" onValueChange={(v) => v && addBlock(v)} options={addOptions} />\n </div>\n <PatternInserter onInsert={addBlocks} />\n </div>\n\n {versions.length > 0 && (\n <Card>\n <h4 className=\"mb-2 text-sm font-semibold\">Versiegeschiedenis</h4>\n <ul className=\"grid gap-1 text-sm text-slate-600\">\n {versions.slice(0, 10).map((v) => (\n <li key={v.id} className=\"flex items-center justify-between border-b border-slate-100 py-1 last:border-0\">\n <span>\n {v.label ?? v.kind ?? \"snapshot\"}\n {formatVersionDate(v.createdAt) && <span className=\"text-slate-400\"> · {formatVersionDate(v.createdAt)}</span>}\n </span>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => void onRestore(v.id)}>\n Herstellen\n </Button>\n </li>\n ))}\n </ul>\n </Card>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/pages/PageEditor.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useMemo, useRef, useState, type ComponentType } from \"react\";\nimport { useRegistry } from \"../../blocks/context\";\nimport { buildBlockAddOptions } from \"../../blocks/registry\";\nimport { useModules } from \"../../modules/context\";\nimport { moduleIdForBlockType } from \"../../modules/registry\";\nimport { Button, Card, Field, Input, Textarea, Badge } from \"../ui/primitives\";\nimport { Select } from \"../ui/select\";\nimport { useToast } from \"../ui/toast\";\nimport { useConfirm } from \"../ui/ConfirmDialog\";\nimport { builtinBlockEditors, GenericEditor, type BlockEditorProps } from \"../blockEditors\";\nimport { StyleEditor } from \"../blockEditors/StyleEditor\";\nimport { PatternInserter } from \"../patterns/PatternInserter\";\nimport type { BlockStyle } from \"../../schema/blocks\";\nimport { saveDraft, publishPage, subscribeVersions, restoreVersion, type DraftPage } from \"../lib/pagesClient\";\nimport { slugify } from \"../lib/slugify\";\n\ntype Block = Record<string, unknown> & { _key: string; _type: string };\ntype SaveState = \"idle\" | \"saving\" | \"saved\" | \"error\";\n\ninterface VersionRow {\n id: string;\n createdAt?: unknown;\n kind?: string;\n label?: string;\n}\n\nfunction newKey() {\n return (globalThis.crypto?.randomUUID?.() ?? `b${Date.now()}${Math.random()}`).slice(0, 12);\n}\n\nfunction formatVersionDate(createdAt: unknown): string {\n const d =\n createdAt && typeof (createdAt as { toDate?: () => Date }).toDate === \"function\"\n ? (createdAt as { toDate: () => Date }).toDate()\n : typeof createdAt === \"string\"\n ? new Date(createdAt)\n : null;\n if (!d || Number.isNaN(d.getTime())) return \"\";\n return d.toLocaleString(\"nl-NL\", { dateStyle: \"medium\", timeStyle: \"short\" });\n}\n\nexport function PageEditor({ siteId, page, allPages }: { siteId: string; page: DraftPage; allPages?: DraftPage[] }) {\n const registry = useRegistry();\n const { activeIds, defs } = useModules();\n const { toast } = useToast();\n const confirm = useConfirm();\n const [draft, setDraft] = useState<DraftPage>(page);\n const [busy, setBusy] = useState(false);\n const [saveState, setSaveState] = useState<SaveState>(\"idle\");\n const [versions, setVersions] = useState<VersionRow[]>([]);\n const firstRender = useRef(true);\n const slugTouched = useRef(Boolean(page.slug));\n\n // Wissel van pagina → herlaad lokale state.\n useEffect(() => {\n setDraft(page);\n firstRender.current = true;\n slugTouched.current = Boolean(page.slug);\n }, [page.id]); // eslint-disable-line react-hooks/exhaustive-deps\n\n useEffect(() => subscribeVersions(siteId, page.id, (v) => setVersions(v)), [siteId, page.id]);\n\n function persist(d: DraftPage) {\n setSaveState(\"saving\");\n return saveDraft(siteId, d.id, {\n title: d.title,\n slug: d.slug,\n path: d.path,\n seo: d.seo,\n blocks: d.blocks,\n order: d.order,\n })\n .then(() => setSaveState(\"saved\"))\n .catch(() => {\n setSaveState(\"error\");\n toast({ title: \"Opslaan mislukt\", description: \"Wijzigingen zijn nog niet opgeslagen.\", tone: \"error\" });\n });\n }\n\n // Autosave (debounced) bij wijzigingen.\n useEffect(() => {\n if (firstRender.current) {\n firstRender.current = false;\n return;\n }\n const t = setTimeout(() => void persist(draft), 800);\n return () => clearTimeout(t);\n }, [draft]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Waarschuw bij verlaten van de pagina met niet-opgeslagen/mislukte wijzigingen.\n useEffect(() => {\n const onBeforeUnload = (e: BeforeUnloadEvent) => {\n if (saveState === \"saving\" || saveState === \"error\") {\n e.preventDefault();\n e.returnValue = \"\";\n }\n };\n window.addEventListener(\"beforeunload\", onBeforeUnload);\n return () => window.removeEventListener(\"beforeunload\", onBeforeUnload);\n }, [saveState]);\n\n const blocks = (draft.blocks ?? []) as Block[];\n\n function update(next: DraftPage) {\n setDraft(next);\n }\n function setBlocks(next: Block[]) {\n update({ ...draft, blocks: next });\n }\n function setTitle(title: string) {\n // Auto-slug zolang de gebruiker de slug niet handmatig heeft aangeraakt.\n const slug = slugTouched.current ? draft.slug : slugify(title);\n update({ ...draft, title, slug });\n }\n function addBlock(type: string) {\n const def = registry.get(type);\n const base = def?.defaults?.() ?? {};\n setBlocks([...blocks, { _key: newKey(), _type: type, ...base } as Block]);\n }\n function addBlocks(newBlocks: Array<Record<string, unknown>>) {\n setBlocks([...blocks, ...(newBlocks as Block[])]);\n }\n function updateBlock(i: number, next: Record<string, unknown>) {\n const copy = [...blocks];\n copy[i] = { ...(next as Block), _key: blocks[i]._key, _type: blocks[i]._type };\n setBlocks(copy);\n }\n async function removeBlock(i: number) {\n const ok = await confirm({\n title: \"Blok verwijderen?\",\n description: \"Dit blok wordt uit de pagina verwijderd.\",\n confirmLabel: \"Verwijderen\",\n tone: \"danger\",\n });\n if (!ok) return;\n setBlocks(blocks.filter((_, idx) => idx !== i));\n }\n function move(i: number, dir: -1 | 1) {\n const j = i + dir;\n if (j < 0 || j >= blocks.length) return;\n const copy = [...blocks];\n [copy[i], copy[j]] = [copy[j], copy[i]];\n setBlocks(copy);\n }\n\n async function onPublish() {\n const ok = await confirm({\n title: \"Pagina publiceren?\",\n description: `De gepubliceerde site wordt bijgewerkt (${draft.path}).`,\n confirmLabel: \"Publiceren\",\n });\n if (!ok) return;\n setBusy(true);\n try {\n await saveDraft(siteId, draft.id, { title: draft.title, slug: draft.slug, path: draft.path, seo: draft.seo, blocks: draft.blocks });\n await publishPage(siteId, draft.id);\n toast({ title: \"Gepubliceerd\", description: draft.path, tone: \"success\" });\n } catch (e) {\n toast({ title: \"Publiceren mislukt\", description: (e as Error).message, tone: \"error\" });\n } finally {\n setBusy(false);\n }\n }\n\n async function onRestore(versionId: string) {\n const ok = await confirm({\n title: \"Versie herstellen?\",\n description: \"De huidige werkversie wordt overschreven met deze versie. Publiceer daarna om het live te zetten.\",\n confirmLabel: \"Herstellen\",\n });\n if (!ok) return;\n try {\n const restored = await restoreVersion(siteId, draft.id, versionId);\n setDraft({ ...draft, ...restored });\n firstRender.current = true; // herstelde versie is al opgeslagen; geen extra autosave nodig\n toast({ title: \"Versie hersteld\", description: \"Controleer en publiceer om live te zetten.\", tone: \"success\" });\n } catch (e) {\n toast({ title: \"Herstellen mislukt\", description: (e as Error).message, tone: \"error\" });\n }\n }\n\n const addOptions = useMemo(() => buildBlockAddOptions(registry.values()), [registry]);\n\n // Blocks waarvan de module is uitgeschakeld → tonen niet op de live site.\n const disabledBlocks = useMemo(() => {\n const seen = new Set<string>();\n const out: string[] = [];\n for (const b of blocks) {\n if (registry.get(b._type)) continue; // actief block-type → ok\n const modId = moduleIdForBlockType(defs, b._type);\n if (modId && !activeIds.has(modId) && !seen.has(b._type)) {\n seen.add(b._type);\n out.push(b._type);\n }\n }\n return out;\n }, [blocks, registry, defs, activeIds]);\n\n return (\n <div className=\"grid gap-4\">\n <Card>\n <div className=\"mb-3 flex flex-wrap items-center justify-between gap-2\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"text-base font-semibold\">Pagina-instellingen</h3>\n {draft.published ? <Badge tone=\"green\">gepubliceerd</Badge> : <Badge tone=\"amber\">concept</Badge>}\n </div>\n <div className=\"flex items-center gap-3\">\n <span className=\"text-xs text-slate-500\" aria-live=\"polite\">\n {saveState === \"saving\" ? \"Opslaan…\" : saveState === \"saved\" ? \"✓ Opgeslagen\" : \"\"}\n </span>\n {saveState === \"error\" && (\n <Button variant=\"secondary\" size=\"sm\" onClick={() => void persist(draft)}>\n Opnieuw opslaan\n </Button>\n )}\n <Button onClick={onPublish} disabled={busy}>\n {busy ? \"Bezig…\" : \"Publiceren\"}\n </Button>\n </div>\n </div>\n {saveState === \"error\" && (\n <div className=\"mb-3 rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-700\">\n Niet alle wijzigingen zijn opgeslagen. Controleer je verbinding en klik “Opnieuw opslaan”.\n </div>\n )}\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Titel\">\n <Input value={draft.title} onChange={(e) => setTitle(e.target.value)} />\n </Field>\n <Field label=\"Pad\" hint=\"bv. / of /over-ons\">\n {/* Vrije tekst brak routing stil (spaties, ontbrekende /, duplicaat-pad → één van de twee\n pagina's onbereikbaar). Normaliseren op blur + waarschuwen bij een botsing. */}\n <Input\n value={draft.path}\n onChange={(e) => update({ ...draft, path: e.target.value })}\n onBlur={() => {\n const parts = draft.path.split(\"/\").map((s) => slugify(s)).filter(Boolean);\n const normalized = \"/\" + parts.join(\"/\");\n if (normalized !== draft.path) update({ ...draft, path: normalized });\n }}\n />\n {allPages?.some((p) => p.id !== draft.id && p.path === draft.path) && (\n <p className=\"mt-1 text-xs text-red-600\" role=\"alert\">\n ⚠ Er bestaat al een pagina met dit pad — één van beide wordt dan onbereikbaar.\n </p>\n )}\n </Field>\n <Field label=\"SEO-titel\">\n <Input value={draft.seo?.title ?? \"\"} onChange={(e) => update({ ...draft, seo: { ...draft.seo, title: e.target.value } })} />\n </Field>\n <Field label=\"SEO-omschrijving\">\n <Textarea rows={2} value={draft.seo?.description ?? \"\"} onChange={(e) => update({ ...draft, seo: { ...draft.seo, description: e.target.value } })} />\n </Field>\n </div>\n </Card>\n\n {disabledBlocks.length > 0 && (\n <div className=\"rounded-lg border border-amber-300 bg-amber-50 p-3 text-sm text-amber-800\">\n Deze pagina bevat blokken van uitgeschakelde modules ({disabledBlocks.join(\", \")}). Ze worden\n <strong> niet</strong> op de live site getoond. Zet de module aan onder Instellingen → Modules,\n of verwijder de blokken.\n </div>\n )}\n\n <div className=\"grid gap-3\">\n {blocks.map((b, i) => {\n const def = registry.get(b._type);\n const Editor: ComponentType<BlockEditorProps> =\n (def?.Editor as ComponentType<BlockEditorProps> | undefined) ?? builtinBlockEditors[b._type] ?? GenericEditor;\n return (\n <Card key={b._key}>\n <div className=\"mb-3 flex items-center justify-between\">\n <span className=\"text-sm font-semibold\">{def?.label ?? b._type}</span>\n <div className=\"flex items-center gap-1\">\n <Button variant=\"ghost\" size=\"sm\" onClick={() => move(i, -1)} aria-label=\"Omhoog\">↑</Button>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => move(i, 1)} aria-label=\"Omlaag\">↓</Button>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => void removeBlock(i)} aria-label=\"Verwijderen\">🗑</Button>\n </div>\n </div>\n <Editor block={b} siteId={siteId} onChange={(next) => updateBlock(i, next)} />\n <div className=\"mt-3\">\n <StyleEditor\n value={(b as Block & { _style?: BlockStyle })._style}\n onChange={(style) => updateBlock(i, { ...b, _style: style })}\n />\n </div>\n </Card>\n );\n })}\n {blocks.length === 0 && <p className=\"text-sm text-slate-500\">Nog geen blokken. Voeg er hieronder een toe.</p>}\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"text-sm text-slate-600\">Toevoegen:</span>\n <div className=\"w-56\">\n <Select value=\"\" placeholder=\"Kies een blok…\" onValueChange={(v) => v && addBlock(v)} options={addOptions} />\n </div>\n <PatternInserter onInsert={addBlocks} />\n </div>\n\n {versions.length > 0 && (\n <Card>\n <h4 className=\"mb-2 text-sm font-semibold\">Versiegeschiedenis</h4>\n <ul className=\"grid gap-1 text-sm text-slate-600\">\n {versions.slice(0, 10).map((v) => (\n <li key={v.id} className=\"flex items-center justify-between border-b border-slate-100 py-1 last:border-0\">\n <span>\n {v.label ?? v.kind ?? \"snapshot\"}\n {formatVersionDate(v.createdAt) && <span className=\"text-slate-400\"> · {formatVersionDate(v.createdAt)}</span>}\n </span>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => void onRestore(v.id)}>\n Herstellen\n </Button>\n </li>\n ))}\n </ul>\n </Card>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -26,10 +26,14 @@ function PagesPanel({ siteId }) {
|
|
|
26
26
|
if (!title) return;
|
|
27
27
|
const slug = slugify(title) || "pagina";
|
|
28
28
|
const path = slug === "home" ? "/" : `/${slug}`;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
try {
|
|
30
|
+
const id = await createPage(siteId, { title, slug, path });
|
|
31
|
+
setNewTitle("");
|
|
32
|
+
setSelected(id);
|
|
33
|
+
toast({ title: "Pagina aangemaakt", description: path });
|
|
34
|
+
} catch (e) {
|
|
35
|
+
toast({ title: "Pagina aanmaken mislukt", description: e.message, tone: "error" });
|
|
36
|
+
}
|
|
33
37
|
}
|
|
34
38
|
return /* @__PURE__ */ jsxs("div", { className: "grid gap-4 md:grid-cols-[260px_1fr]", children: [
|
|
35
39
|
/* @__PURE__ */ jsxs(Card, { className: "h-fit", children: [
|
|
@@ -82,7 +86,7 @@ function PagesPanel({ siteId }) {
|
|
|
82
86
|
children: "Verwijderen"
|
|
83
87
|
}
|
|
84
88
|
) }),
|
|
85
|
-
/* @__PURE__ */ jsx(PageEditor, { siteId, page: current })
|
|
89
|
+
/* @__PURE__ */ jsx(PageEditor, { siteId, page: current, allPages: pages })
|
|
86
90
|
] }) : /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx("p", { className: "text-sm text-slate-500", children: "Selecteer of maak een pagina om te bewerken." }) }) })
|
|
87
91
|
] });
|
|
88
92
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/pages/PagesPanel.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { Button, Card, Field, Input, Badge } from \"../ui/primitives\";\nimport { Dialog, DialogTrigger, DialogContent, DialogClose } from \"../ui/dialog\";\nimport { useToast } from \"../ui/toast\";\nimport { useConfirm } from \"../ui/ConfirmDialog\";\nimport { subscribePages, createPage, deletePage, type DraftPage } from \"../lib/pagesClient\";\nimport { PageEditor } from \"./PageEditor\";\n\nfunction slugify(s: string): string {\n return s\n .toLowerCase()\n .normalize(\"NFD\")\n .replace(/[̀-ͯ]/g, \"\")\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/(^-|-$)/g, \"\");\n}\n\nexport function PagesPanel({ siteId }: { siteId: string }) {\n const { toast } = useToast();\n const confirm = useConfirm();\n const [pages, setPages] = useState<DraftPage[]>([]);\n const [selected, setSelected] = useState<string | null>(null);\n const [newTitle, setNewTitle] = useState(\"\");\n\n useEffect(() => subscribePages(siteId, setPages), [siteId]);\n useEffect(() => {\n if (!selected && pages.length) setSelected(pages[0].id);\n }, [pages, selected]);\n\n const current = pages.find((p) => p.id === selected) ?? null;\n\n async function create() {\n const title = newTitle.trim();\n if (!title) return;\n const slug = slugify(title) || \"pagina\";\n const path = slug === \"home\" ? \"/\" : `/${slug}`;\n const id = await createPage(siteId, { title, slug, path });\n
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/pages/PagesPanel.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { Button, Card, Field, Input, Badge } from \"../ui/primitives\";\nimport { Dialog, DialogTrigger, DialogContent, DialogClose } from \"../ui/dialog\";\nimport { useToast } from \"../ui/toast\";\nimport { useConfirm } from \"../ui/ConfirmDialog\";\nimport { subscribePages, createPage, deletePage, type DraftPage } from \"../lib/pagesClient\";\nimport { PageEditor } from \"./PageEditor\";\n\nfunction slugify(s: string): string {\n return s\n .toLowerCase()\n .normalize(\"NFD\")\n .replace(/[̀-ͯ]/g, \"\")\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/(^-|-$)/g, \"\");\n}\n\nexport function PagesPanel({ siteId }: { siteId: string }) {\n const { toast } = useToast();\n const confirm = useConfirm();\n const [pages, setPages] = useState<DraftPage[]>([]);\n const [selected, setSelected] = useState<string | null>(null);\n const [newTitle, setNewTitle] = useState(\"\");\n\n useEffect(() => subscribePages(siteId, setPages), [siteId]);\n useEffect(() => {\n if (!selected && pages.length) setSelected(pages[0].id);\n }, [pages, selected]);\n\n const current = pages.find((p) => p.id === selected) ?? null;\n\n async function create() {\n const title = newTitle.trim();\n if (!title) return;\n const slug = slugify(title) || \"pagina\";\n const path = slug === \"home\" ? \"/\" : `/${slug}`;\n try {\n const id = await createPage(siteId, { title, slug, path });\n setNewTitle(\"\");\n setSelected(id);\n toast({ title: \"Pagina aangemaakt\", description: path });\n } catch (e) {\n // De dialoog sluit sowieso (DialogClose) — zonder deze toast dacht de klant dat de pagina\n // bestond terwijl de write (rules/offline) faalde.\n toast({ title: \"Pagina aanmaken mislukt\", description: (e as Error).message, tone: \"error\" });\n }\n }\n\n return (\n <div className=\"grid gap-4 md:grid-cols-[260px_1fr]\">\n <Card className=\"h-fit\">\n <div className=\"mb-3 flex items-center justify-between\">\n <h3 className=\"text-sm font-semibold\">Pagina's</h3>\n <Dialog>\n <DialogTrigger asChild>\n <Button size=\"sm\">+ Nieuw</Button>\n </DialogTrigger>\n <DialogContent title=\"Nieuwe pagina\">\n <div className=\"grid gap-3\">\n <Field label=\"Titel\">\n <Input value={newTitle} onChange={(e) => setNewTitle(e.target.value)} placeholder=\"bv. Over ons\" />\n </Field>\n <div className=\"flex justify-end gap-2\">\n <DialogClose asChild>\n <Button variant=\"secondary\">Annuleren</Button>\n </DialogClose>\n <DialogClose asChild>\n <Button onClick={create}>Aanmaken</Button>\n </DialogClose>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n </div>\n <ul className=\"grid gap-1\">\n {pages.map((p) => (\n <li key={p.id}>\n <button\n onClick={() => setSelected(p.id)}\n className={`flex w-full items-center justify-between rounded-lg px-3 py-2 text-left text-sm ${\n p.id === selected ? \"bg-brand-50 text-brand-700\" : \"hover:bg-slate-100\"\n }`}\n >\n <span className=\"truncate\">{p.title}</span>\n {p.published ? <Badge tone=\"green\">live</Badge> : <Badge tone=\"amber\">concept</Badge>}\n </button>\n </li>\n ))}\n {pages.length === 0 && <li className=\"px-3 py-2 text-sm text-slate-500\">Nog geen pagina's.</li>}\n </ul>\n </Card>\n\n <div>\n {current ? (\n <>\n <div className=\"mb-3 flex justify-end\">\n <Button\n variant=\"danger\"\n size=\"sm\"\n onClick={async () => {\n const ok = await confirm({\n title: `Pagina \"${current.title}\" verwijderen?`,\n description:\n \"De pagina en de bijbehorende werkversie worden verwijderd. Als de pagina gepubliceerd is, verdwijnt hij ook van de live site. Dit kan niet ongedaan worden gemaakt.\",\n confirmLabel: \"Verwijderen\",\n tone: \"danger\",\n });\n if (!ok) return;\n await deletePage(siteId, current.id);\n setSelected(null);\n toast({ title: \"Pagina verwijderd\" });\n }}\n >\n Verwijderen\n </Button>\n </div>\n <PageEditor siteId={siteId} page={current} allPages={pages} />\n </>\n ) : (\n <Card>\n <p className=\"text-sm text-slate-500\">Selecteer of maak een pagina om te bewerken.</p>\n </Card>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -347,6 +347,7 @@ function VisualEditorInner({ onClose }) {
|
|
|
347
347
|
firstSave.current = false;
|
|
348
348
|
return;
|
|
349
349
|
}
|
|
350
|
+
if (conflict) return;
|
|
350
351
|
dirtyRef.current = true;
|
|
351
352
|
dirtySinceRef.current ??= Date.now();
|
|
352
353
|
setStatus("saving");
|
|
@@ -364,7 +365,7 @@ function VisualEditorInner({ onClose }) {
|
|
|
364
365
|
}).catch(() => setStatus("error"));
|
|
365
366
|
}, delay);
|
|
366
367
|
return () => clearTimeout(t);
|
|
367
|
-
}, [draft, siteId]);
|
|
368
|
+
}, [draft, siteId, conflict]);
|
|
368
369
|
useEffect(() => {
|
|
369
370
|
const beforeUnload = (e) => {
|
|
370
371
|
if (dirtyRef.current) {
|
|
@@ -469,6 +470,10 @@ function VisualEditorInner({ onClose }) {
|
|
|
469
470
|
}
|
|
470
471
|
async function onPublish() {
|
|
471
472
|
if (!draft) return;
|
|
473
|
+
if (conflict) {
|
|
474
|
+
toast({ title: "Los eerst het conflict op", description: "Kies 'Nieuwste versie laden' of 'Mijn versie behouden' in de gele balk.", tone: "error" });
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
472
477
|
const ok = await confirm({ title: "Pagina publiceren?", description: `De live site wordt bijgewerkt (${draft.path}).`, confirmLabel: "Publiceren" });
|
|
473
478
|
if (!ok) return;
|
|
474
479
|
setPublishing(true);
|
|
@@ -672,7 +677,11 @@ function VisualEditorInner({ onClose }) {
|
|
|
672
677
|
if (!v) setImgPicker(null);
|
|
673
678
|
},
|
|
674
679
|
onSelect: (m) => {
|
|
675
|
-
if (imgPicker)
|
|
680
|
+
if (imgPicker) {
|
|
681
|
+
commitInline(imgPicker.key, imgPicker.field, m.url);
|
|
682
|
+
const altField = imgPicker.field.replace(/(^|\.)((image)?[uU]rl)$/, "$1alt");
|
|
683
|
+
if (altField !== imgPicker.field) commitInline(imgPicker.key, altField, m.alt ?? "");
|
|
684
|
+
}
|
|
676
685
|
setImgPicker(null);
|
|
677
686
|
}
|
|
678
687
|
}
|