@xiwebdesign/xicms-core 4.17.1 → 4.18.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.
Files changed (81) hide show
  1. package/dist/XiCMSProvider.js +3 -1
  2. package/dist/XiCMSProvider.js.map +1 -1
  3. package/dist/admin/blockEditors/index.js +47 -14
  4. package/dist/admin/blockEditors/index.js.map +1 -1
  5. package/dist/admin/lib/compressImage.d.ts +4 -2
  6. package/dist/admin/lib/compressImage.js +7 -5
  7. package/dist/admin/lib/compressImage.js.map +1 -1
  8. package/dist/admin/lib/mediaUsage.d.ts +4 -3
  9. package/dist/admin/lib/mediaUsage.js +21 -10
  10. package/dist/admin/lib/mediaUsage.js.map +1 -1
  11. package/dist/admin/lib/upload.d.ts +4 -0
  12. package/dist/admin/lib/upload.js +26 -6
  13. package/dist/admin/lib/upload.js.map +1 -1
  14. package/dist/admin/media/MediaManager.js +23 -7
  15. package/dist/admin/media/MediaManager.js.map +1 -1
  16. package/dist/admin/media/MediaPicker.js +12 -2
  17. package/dist/admin/media/MediaPicker.js.map +1 -1
  18. package/dist/admin/settings/SettingsPanel.js +4 -1
  19. package/dist/admin/settings/SettingsPanel.js.map +1 -1
  20. package/dist/admin/visual/VisualEditor.js +4 -1
  21. package/dist/admin/visual/VisualEditor.js.map +1 -1
  22. package/dist/blocks/builtins/Agenda.d.ts +6 -0
  23. package/dist/blocks/builtins/Agenda.js +14 -6
  24. package/dist/blocks/builtins/Agenda.js.map +1 -1
  25. package/dist/blocks/builtins/Breadcrumbs.js +5 -1
  26. package/dist/blocks/builtins/Breadcrumbs.js.map +1 -1
  27. package/dist/blocks/builtins/Cards.js +3 -2
  28. package/dist/blocks/builtins/Cards.js.map +1 -1
  29. package/dist/blocks/builtins/Contact.js +2 -1
  30. package/dist/blocks/builtins/Contact.js.map +1 -1
  31. package/dist/blocks/builtins/Faq.js +5 -4
  32. package/dist/blocks/builtins/Faq.js.map +1 -1
  33. package/dist/blocks/builtins/Gallery.js +2 -2
  34. package/dist/blocks/builtins/Gallery.js.map +1 -1
  35. package/dist/blocks/builtins/Jobs.d.ts +8 -1
  36. package/dist/blocks/builtins/Jobs.js +37 -21
  37. package/dist/blocks/builtins/Jobs.js.map +1 -1
  38. package/dist/blocks/builtins/MapBlock.js +31 -4
  39. package/dist/blocks/builtins/MapBlock.js.map +1 -1
  40. package/dist/blocks/builtins/PdfBlock.js +2 -3
  41. package/dist/blocks/builtins/PdfBlock.js.map +1 -1
  42. package/dist/blocks/builtins/Portfolio.js +2 -2
  43. package/dist/blocks/builtins/Portfolio.js.map +1 -1
  44. package/dist/blocks/builtins/Pricing.d.ts +2 -0
  45. package/dist/blocks/builtins/Pricing.js +18 -10
  46. package/dist/blocks/builtins/Pricing.js.map +1 -1
  47. package/dist/blocks/builtins/Sections.js +3 -2
  48. package/dist/blocks/builtins/Sections.js.map +1 -1
  49. package/dist/blocks/builtins/Slider.js +21 -3
  50. package/dist/blocks/builtins/Slider.js.map +1 -1
  51. package/dist/blocks/builtins/Testimonials.js +21 -18
  52. package/dist/blocks/builtins/Testimonials.js.map +1 -1
  53. package/dist/blocks/builtins/WhatsappChat.js +1 -1
  54. package/dist/blocks/builtins/WhatsappChat.js.map +1 -1
  55. package/dist/blocks/builtins/Widgets.js +2 -3
  56. package/dist/blocks/builtins/Widgets.js.map +1 -1
  57. package/dist/blocks/builtins/lib/ics.js +1 -1
  58. package/dist/blocks/builtins/lib/ics.js.map +1 -1
  59. package/dist/blocks/builtins/lib/openingHours.d.ts +4 -1
  60. package/dist/blocks/builtins/lib/openingHours.js +11 -5
  61. package/dist/blocks/builtins/lib/openingHours.js.map +1 -1
  62. package/dist/demo/demoStore.d.ts +11 -1
  63. package/dist/demo/demoStore.js +50 -0
  64. package/dist/demo/demoStore.js.map +1 -1
  65. package/dist/firebase/client.js +2 -1
  66. package/dist/firebase/client.js.map +1 -1
  67. package/dist/index.d.ts +1 -1
  68. package/dist/index.js +2 -0
  69. package/dist/index.js.map +1 -1
  70. package/dist/modules/builtins.js +1 -1
  71. package/dist/modules/builtins.js.map +1 -1
  72. package/dist/modules/consent/useConsent.js +15 -3
  73. package/dist/modules/consent/useConsent.js.map +1 -1
  74. package/dist/schema/blocks.d.ts +24 -3
  75. package/dist/schema/blocks.js +4 -1
  76. package/dist/schema/blocks.js.map +1 -1
  77. package/dist/server/seo.d.ts +2 -0
  78. package/dist/server/seo.js +1 -1
  79. package/dist/server/seo.js.map +1 -1
  80. package/dist/styles.css +35 -6
  81. package/package.json +1 -1
@@ -280,7 +280,10 @@ function VisualEditorInner({ onClose }) {
280
280
  };
281
281
  const onKeyDown = (e) => {
282
282
  if (e.key === "Escape") {
283
- handleClose();
283
+ const t2 = e.target;
284
+ const inField2 = !!t2 && (t2.isContentEditable || t2.tagName === "INPUT" || t2.tagName === "TEXTAREA" || t2.tagName === "SELECT");
285
+ const dialogOpen = document.querySelector('[role="dialog"][data-state="open"], [role="alertdialog"][data-state="open"]');
286
+ if (!inField2 && !dialogOpen) handleClose();
284
287
  return;
285
288
  }
286
289
  const t = e.target;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/visual/VisualEditor.tsx"],"sourcesContent":["\"use client\";\n\n// De admin-stijlen (Tailwind, scoped onder .xicms-admin) horen bij dit lazy-geladen chunk, zodat\n// publieke bezoekers ze nooit downloaden — alleen wanneer een editor de pagina-editor opent.\nimport \"@xiwebdesign/xicms-core/admin.css\";\nimport { useCallback, useEffect, useMemo, useRef, useState, type ComponentType } from \"react\";\nimport { useSiteConfig } from \"../../config\";\nimport { useRegistry } from \"../../blocks/context\";\nimport { BlockErrorBoundary } from \"../../blocks/BlockErrorBoundary\";\nimport { InlineImageEditProvider } from \"../../media/ImageContext\";\nimport { MediaPickerDialog } from \"../media/MediaPicker\";\nimport { EmptyState } from \"@astryxdesign/core/EmptyState\";\nimport { buildBlockAddOptions } from \"../../blocks/registry\";\nimport { getDraftByPath, saveDraft, publishPage, subscribeDraft, subscribeVersions, restoreVersion, type DraftPage } from \"../lib/pagesClient\";\nimport { isDemoActive } from \"../../demo/demoStore\";\nimport { builtinBlockEditors, GenericEditor, type BlockEditorProps } from \"../blockEditors\";\nimport { StyleEditor } from \"../blockEditors/StyleEditor\";\nimport { reorderBlocks } from \"./reorder\";\nimport { PatternInserter } from \"../patterns/PatternInserter\";\nimport type { BlockStyle } from \"../../schema/blocks\";\nimport { Button, Field, Input, Textarea } from \"../ui/primitives\";\nimport { Select } from \"../ui/select\";\nimport { ToastProvider, useToast } from \"../ui/toast\";\nimport { ConfirmHost, useConfirm } from \"../ui/ConfirmDialog\";\n\ntype Block = Record<string, unknown> & { _key: string; _type: string };\ntype Status = \"loading\" | \"none\" | \"idle\" | \"saving\" | \"saved\" | \"error\";\n\n/**\n * Welke tekstvelden per bloktype **inline** (direct in het canvas) bewerkbaar zijn, gemapt op hun\n * stabiele DOM-klasse — zo hoeft de blok-Render niet aangepast te worden. `html: true` synct innerHTML\n * (rich text), anders platte tekst. Elk blok kan ook zelf velden markeren met `data-xicms-edit=\"veld\"`.\n */\nconst INLINE_FIELDS: Record<string, { selector: string; field: string; html?: boolean }[]> = {\n hero: [\n { selector: \".xicms-hero__title\", field: \"title\" },\n { selector: \".xicms-hero__sub\", field: \"subtitle\" },\n ],\n};\n\n/** Selectors die in ELK blok inline bewerkbaar zijn: rich-text-inhoud (`.xicms-richtext`, gebruikt door\n * richText én feature) en velden die een blok zelf markeert met `data-xicms-edit=\"veld\"`. */\nconst GLOBAL_INLINE = [\n { selector: \".xicms-richtext\", field: \"html\", html: true as const },\n { selector: \"[data-xicms-edit]\", field: \"\", html: false as const, fromData: true as const },\n];\n\n/* Lees/schrijf een waarde op een gepunt pad (bv. \"items.0.title\") — zodat ook lijst-items inline\n bewerkt kunnen worden. setPath kloont onderweg (immutable) zodat React de wijziging detecteert. */\nfunction getPath(obj: unknown, path: string): unknown {\n return path.split(\".\").reduce<unknown>((o, k) => (o == null ? undefined : (o as Record<string, unknown>)[k]), obj);\n}\nfunction setPath<T>(obj: T, path: string, value: unknown): T {\n const keys = path.split(\".\");\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const clone = (v: unknown): any => (Array.isArray(v) ? [...v] : v && typeof v === \"object\" ? { ...(v as object) } : v);\n const root = clone(obj);\n let cur = root;\n for (let i = 0; i < keys.length - 1; i++) {\n cur[keys[i]] = clone(cur[keys[i]] ?? {});\n cur = cur[keys[i]];\n }\n cur[keys[keys.length - 1]] = value;\n return root as T;\n}\n\nfunction newKey() {\n return (globalThis.crypto?.randomUUID?.() ?? `b${Date.now()}${Math.random()}`).slice(0, 12);\n}\n\n/** JSON.stringify met gesorteerde sleutels — voor betrouwbare inhoudsvergelijking (conflictdetectie):\n * Firestore kan velden in een andere volgorde teruggeven dan wij ze lokaal opbouwden. */\nfunction stableStringify(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(stableStringify).join(\",\")}]`;\n if (value && typeof value === \"object\") {\n const entries = Object.entries(value as Record<string, unknown>)\n .filter(([, v]) => v !== undefined)\n .sort(([a], [b]) => (a < b ? -1 : 1));\n return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`).join(\",\")}}`;\n }\n return JSON.stringify(value) ?? \"null\";\n}\n\n/**\n * Visuele/inline pagina-editor: opent als overlay over de live pagina, laadt de werkversie van de\n * huidige route, en laat de editor blokken op een live canvas selecteren, herordenen, dupliceren,\n * verwijderen en toevoegen — met autosave en publiceren. De zware code laadt pas bij openen\n * (lazy, via EditorProvider), dus bezoekers downloaden hem nooit.\n */\nexport default function VisualEditor({ onClose }: { onClose: () => void }) {\n return (\n <div className=\"xicms-admin xicms-ve\">\n <ToastProvider>\n <ConfirmHost>\n <VisualEditorInner onClose={onClose} />\n </ConfirmHost>\n </ToastProvider>\n </div>\n );\n}\n\nfunction VisualEditorInner({ onClose }: { onClose: () => void }) {\n const { siteId, name } = useSiteConfig();\n const registry = useRegistry();\n const { toast } = useToast();\n const confirm = useConfirm();\n\n const path = useMemo(() => (typeof window !== \"undefined\" ? window.location.pathname || \"/\" : \"/\"), []);\n const [draft, setDraft] = useState<DraftPage | null>(null);\n const [status, setStatus] = useState<Status>(\"loading\");\n const [selectedKey, setSelectedKey] = useState<string | null>(null);\n const [publishing, setPublishing] = useState(false);\n const [imgPicker, setImgPicker] = useState<{ key: string; field: string } | null>(null);\n const firstSave = useRef(true);\n const canvasRef = useRef<HTMLDivElement>(null);\n const [conflict, setConflict] = useState(false);\n\n // Drag-and-drop herordenen: dragIndex = het gesleepte blok, dropIndex = invoegpunt (0..length).\n // draggingRef pauzeert de inline-bind MutationObserver tijdens het slepen (re-bind-ruis).\n const [dragIndex, setDragIndex] = useState<number | null>(null);\n const [dropIndex, setDropIndex] = useState<number | null>(null);\n const draggingRef = useRef(false);\n const endDrag = useCallback(() => {\n draggingRef.current = false;\n setDragIndex(null);\n setDropIndex(null);\n }, []);\n\n // Altijd de laatste draft + \"zijn er niet-opgeslagen wijzigingen\" bij de hand houden, zodat we\n // bij sluiten/wegnavigeren nog kunnen flushen (de debounce zou de laatste 700ms anders weggooien).\n const draftRef = useRef<DraftPage | null>(null);\n const dirtyRef = useRef(false);\n // Inhoud (stable-stringified) van de laatst succesvol opgeslagen payload — voor conflictdetectie.\n const lastSavedRef = useRef<string | null>(null);\n useEffect(() => {\n draftRef.current = draft;\n }, [draft]);\n\n // ── Undo/redo ──────────────────────────────────────────────────────────────\n // History over draft-snapshots. Tekstedits (per toetsaanslag) worden ge-coalesced: binnen één\n // typ-burst (<800ms) staat het pre-burst-snapshot al op de stack, dus niet opnieuw pushen —\n // Ctrl+Z springt dan per burst terug i.p.v. per letter. `histVersion` forceert re-render van\n // de knop-states (de stacks leven in een ref).\n const historyRef = useRef<{ past: DraftPage[]; future: DraftPage[]; lastPush: number }>({ past: [], future: [], lastPush: 0 });\n const [, setHistVersion] = useState(0);\n\n const pushHistory = useCallback((prev: DraftPage, coalesce: boolean) => {\n const h = historyRef.current;\n const now = Date.now();\n if (!(coalesce && now - h.lastPush < 800 && h.past.length > 0)) {\n h.past.push(prev);\n if (h.past.length > 100) h.past.shift();\n }\n h.lastPush = now;\n h.future = [];\n setHistVersion((v) => v + 1);\n }, []);\n\n /** Enige route waarlangs de draft muteert: bewaart eerst een undo-snapshot. */\n const mutateDraft = useCallback(\n (updater: (d: DraftPage) => DraftPage, opts?: { coalesce?: boolean }) => {\n const d = draftRef.current;\n if (!d) return;\n const next = updater(d);\n if (next === d) return;\n pushHistory(d, opts?.coalesce ?? false);\n draftRef.current = next;\n setDraft(next);\n },\n [pushHistory],\n );\n\n const undo = useCallback(() => {\n const h = historyRef.current;\n const d = draftRef.current;\n const prev = h.past.pop();\n if (!prev || !d) return;\n h.future.push(d);\n h.lastPush = 0;\n draftRef.current = prev;\n setDraft(prev);\n setHistVersion((v) => v + 1);\n }, []);\n\n const redo = useCallback(() => {\n const h = historyRef.current;\n const d = draftRef.current;\n const next = h.future.pop();\n if (!next || !d) return;\n h.past.push(d);\n h.lastPush = 0;\n draftRef.current = next;\n setDraft(next);\n setHistVersion((v) => v + 1);\n }, []);\n\n const draftPayload = (d: DraftPage) => ({\n blocks: d.blocks,\n title: d.title,\n slug: d.slug,\n path: d.path,\n seo: d.seo,\n });\n\n // Schrijf eventuele pending wijziging direct weg (gebruikt bij Sluiten, tab-wissel en retry).\n const flushSave = useCallback(async () => {\n const d = draftRef.current;\n if (!d || !dirtyRef.current) return;\n const payload = draftPayload(d);\n await saveDraft(siteId, d.id, payload);\n lastSavedRef.current = stableStringify(payload);\n // Alleen \"schoon\" markeren als er intussen niets nieuws is getypt (anders zou een trage\n // write nieuwere wijzigingen als opgeslagen bestempelen).\n if (draftRef.current === d) {\n dirtyRef.current = false;\n setStatus(\"saved\");\n }\n }, [siteId]);\n\n const handleClose = useCallback(() => {\n void flushSave().finally(() => onClose());\n }, [flushSave, onClose]);\n\n useEffect(() => {\n let active = true;\n getDraftByPath(siteId, path)\n .then((d) => {\n if (!active) return;\n if (d) {\n setDraft(d);\n lastSavedRef.current = stableStringify({ blocks: d.blocks, title: d.title, slug: d.slug, path: d.path, seo: d.seo });\n setStatus(\"idle\");\n } else {\n setStatus(\"none\");\n }\n })\n .catch(() => active && setStatus(\"none\"));\n return () => {\n active = false;\n };\n }, [siteId, path]);\n\n // ── Conflictdetectie ───────────────────────────────────────────────────────\n // Luister naar de draft in Firestore. Een server-snapshot die afwijkt van zowel onze laatst\n // opgeslagen payload als onze huidige lokale staat, betekent: iemand anders (tab/collega)\n // heeft deze pagina gewijzigd. Waarschuw i.p.v. elkaars werk stil te overschrijven.\n const draftId = draft?.id;\n useEffect(() => {\n if (!draftId) return;\n return subscribeDraft(siteId, draftId, (remote, meta) => {\n if (!meta.fromServer || !remote) return;\n const remotePayload = stableStringify({ blocks: remote.blocks, title: remote.title, slug: remote.slug, path: remote.path, seo: remote.seo });\n const local = draftRef.current;\n const localPayload = local ? stableStringify(draftPayload(local)) : null;\n if (remotePayload !== lastSavedRef.current && remotePayload !== localPayload) setConflict(true);\n });\n }, [siteId, draftId]);\n\n const blocks = (draft?.blocks ?? []) as Block[];\n\n // Schrijf een inline (in-canvas) tekstwijziging terug naar het juiste blokveld.\n const commitInline = useCallback(\n (key: string, field: string, value: string) => {\n mutateDraft(\n (d) => {\n const bl = d.blocks as Block[];\n const idx = bl.findIndex((x) => x._key === key);\n if (idx < 0 || getPath(bl[idx], field) === value) return d;\n const next = [...bl];\n next[idx] = setPath(next[idx], field, value);\n return { ...d, blocks: next };\n },\n { coalesce: true },\n );\n },\n [mutateDraft],\n );\n\n // Maak de tekstvelden van het GESELECTEERDE blok ter plekke bewerkbaar (contentEditable) en synct\n // op blur terug — zo bewerk je tekst inline op het canvas i.p.v. via de zijbalk. We binden via een\n // MutationObserver én her-binden zodra React de DOM-node vervangt (re-render), met een dataset-flag\n // zodat we nooit dubbele listeners zetten.\n useEffect(() => {\n if (!selectedKey) return;\n const host = canvasRef.current?.querySelector<HTMLElement>(`[data-ve-content=\"${selectedKey}\"]`);\n if (!host) return;\n const type = blocks.find((b) => b._key === selectedKey)?._type ?? \"\";\n const specs = [...(INLINE_FIELDS[type] ?? []), ...GLOBAL_INLINE];\n\n const bound = new Set<HTMLElement>();\n const bind = (el: HTMLElement, field: string, html: boolean) => {\n if (el.dataset.veInline === \"1\") return;\n el.dataset.veInline = \"1\";\n el.setAttribute(\"contenteditable\", \"true\");\n el.setAttribute(\"spellcheck\", \"true\");\n el.classList.add(\"xicms-ve-inline\");\n el.addEventListener(\"blur\", () => commitInline(selectedKey, field, html ? el.innerHTML : (el.textContent ?? \"\")));\n el.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Escape\") el.blur();\n else if (!html && e.key === \"Enter\") { e.preventDefault(); el.blur(); }\n });\n bound.add(el);\n };\n const apply = () => {\n for (const m of specs) {\n host.querySelectorAll<HTMLElement>(m.selector).forEach((el) =>\n bind(el, \"fromData\" in m && m.fromData ? (el.dataset.xicmsEdit as string) : m.field, !!m.html),\n );\n }\n };\n\n apply();\n // Her-bind na elke re-render die de tekstnodes vervangt (niet tijdens slepen: dan muteert de\n // DOM continu door de drop-indicatoren en zou her-binden alleen maar ruis geven).\n const obs = new MutationObserver(() => {\n if (!draggingRef.current) apply();\n });\n obs.observe(host, { childList: true, subtree: true });\n return () => {\n obs.disconnect();\n bound.forEach((el) => {\n el.removeAttribute(\"contenteditable\");\n el.classList.remove(\"xicms-ve-inline\");\n delete el.dataset.veInline;\n });\n };\n }, [selectedKey, blocks, commitInline]);\n\n // Autosave naar de draft (publiceren blijft een aparte, bewuste stap).\n // Debounce 700ms ná de laatste wijziging, met een MAX-WAIT van 3s: de debounce herstart bij elke\n // toetsaanslag, dus zonder max-wait zou iemand die blijft doortypen nooit tussentijds opslaan.\n const dirtySinceRef = useRef<number | null>(null);\n useEffect(() => {\n if (!draft) return;\n if (firstSave.current) {\n firstSave.current = false;\n return;\n }\n dirtyRef.current = true;\n dirtySinceRef.current ??= Date.now();\n setStatus(\"saving\");\n const waited = Date.now() - dirtySinceRef.current;\n const delay = Math.max(0, Math.min(700, 3000 - waited));\n const t = setTimeout(() => {\n dirtySinceRef.current = null;\n const payload = draftPayload(draft);\n void saveDraft(siteId, draft.id, payload)\n .then(() => {\n lastSavedRef.current = stableStringify(payload);\n // Alleen \"schoon\"/\"saved\" als dit nog de nieuwste versie is (zie flushSave).\n if (draftRef.current === draft) {\n dirtyRef.current = false;\n setStatus(\"saved\");\n }\n })\n .catch(() => setStatus(\"error\"));\n }, delay);\n return () => clearTimeout(t);\n }, [draft, siteId]);\n\n // Waarschuw bij het verlaten van de pagina met niet-opgeslagen wijzigingen; flush bij tab-wissel/\n // verbergen (visibilitychange) en bij unmount. Escape sluit de editor (na flush).\n useEffect(() => {\n const beforeUnload = (e: BeforeUnloadEvent) => {\n if (dirtyRef.current) {\n e.preventDefault();\n e.returnValue = \"\";\n }\n };\n const onVisibility = () => {\n // Tab-wissel/minimaliseren: beste moment om pending werk veilig te stellen.\n if (document.visibilityState === \"hidden\") void flushSave().catch(() => {});\n };\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") {\n handleClose();\n return;\n }\n // Undo/redo op canvas-niveau. In invoervelden/contentEditable laten we de native\n // tekst-undo van de browser zijn werk doen (anders vecht Ctrl+Z met het veld zelf).\n const t = e.target as HTMLElement | null;\n const inField = !!t && (t.isContentEditable || t.tagName === \"INPUT\" || t.tagName === \"TEXTAREA\" || t.tagName === \"SELECT\");\n if ((e.metaKey || e.ctrlKey) && !inField && (e.key.toLowerCase() === \"z\" || e.key.toLowerCase() === \"y\")) {\n e.preventDefault();\n if (e.key.toLowerCase() === \"y\" || e.shiftKey) redo();\n else undo();\n }\n };\n window.addEventListener(\"beforeunload\", beforeUnload);\n document.addEventListener(\"visibilitychange\", onVisibility);\n window.addEventListener(\"keydown\", onKeyDown);\n return () => {\n window.removeEventListener(\"beforeunload\", beforeUnload);\n document.removeEventListener(\"visibilitychange\", onVisibility);\n window.removeEventListener(\"keydown\", onKeyDown);\n const d = draftRef.current;\n if (d && dirtyRef.current) {\n // Laatste redmiddel bij unmount; log een mislukking i.p.v. stil verliezen.\n void saveDraft(siteId, d.id, draftPayload(d)).catch((err) =>\n console.warn(\"[xicms] autosave bij sluiten mislukt:\", err),\n );\n }\n };\n }, [siteId, handleClose, flushSave, undo, redo]);\n\n function setBlocks(next: Block[], opts?: { coalesce?: boolean }) {\n mutateDraft((d) => ({ ...d, blocks: next }), opts);\n }\n function updateByKey(key: string, next: Record<string, unknown>) {\n // Zijpaneel-edits komen per toetsaanslag binnen → coalesce voor zinnige undo-stappen.\n setBlocks(blocks.map((b) => (b._key === key ? ({ ...(next as Block), _key: b._key, _type: b._type }) : b)), { coalesce: true });\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 function duplicate(i: number) {\n const copy = [...blocks];\n copy.splice(i + 1, 0, { ...blocks[i], _key: newKey() });\n setBlocks(copy);\n }\n async function remove(i: number) {\n const ok = await confirm({ title: \"Blok verwijderen?\", confirmLabel: \"Verwijderen\", tone: \"danger\" });\n if (!ok) return;\n const removed = blocks[i];\n setBlocks(blocks.filter((_, idx) => idx !== i));\n if (selectedKey === removed._key) setSelectedKey(null);\n }\n function addBlock(type: string) {\n const def = registry.get(type);\n const base = def?.defaults?.() ?? {};\n const block = { _key: newKey(), _type: type, ...base } as Block;\n insertAfterSelected([block]);\n setSelectedKey(block._key);\n }\n function insertAfterSelected(newBlocks: Array<Record<string, unknown>>) {\n const sel = blocks.findIndex((b) => b._key === selectedKey);\n const copy = [...blocks];\n copy.splice(sel >= 0 ? sel + 1 : blocks.length, 0, ...(newBlocks as Block[]));\n setBlocks(copy);\n }\n\n async function onPublish() {\n if (!draft) return;\n const ok = await confirm({ title: \"Pagina publiceren?\", description: `De live site wordt bijgewerkt (${draft.path}).`, confirmLabel: \"Publiceren\" });\n if (!ok) return;\n setPublishing(true);\n try {\n await saveDraft(siteId, draft.id, { blocks: draft.blocks, title: draft.title, slug: draft.slug, path: draft.path, seo: draft.seo });\n await publishPage(siteId, draft.id);\n toast({ title: \"Gepubliceerd\", description: \"Herlaad de pagina om het live resultaat te zien.\", tone: \"success\" });\n } catch (e) {\n toast({ title: \"Publiceren mislukt\", description: (e as Error).message, tone: \"error\" });\n } finally {\n setPublishing(false);\n }\n }\n\n const addOptions = useMemo(() => buildBlockAddOptions(registry.values()), [registry]);\n\n const selectedIndex = blocks.findIndex((b) => b._key === selectedKey);\n const selected = selectedIndex >= 0 ? blocks[selectedIndex] : null;\n const canUndo = historyRef.current.past.length > 0;\n const canRedo = historyRef.current.future.length > 0;\n\n /** Laad de nieuwste (remote) versie in de editor — bij een gedetecteerd conflict. */\n const reloadRemote = useCallback(async () => {\n const remote = await getDraftByPath(siteId, path).catch(() => null);\n if (remote) {\n mutateDraft(() => remote);\n lastSavedRef.current = stableStringify(draftPayload(remote));\n dirtyRef.current = false;\n setStatus(\"idle\");\n }\n setConflict(false);\n }, [siteId, path, mutateDraft]);\n\n return (\n <div className=\"xicms-ve__shell\" role=\"dialog\" aria-modal=\"true\" aria-label={`Pagina bewerken: ${path}`}>\n <header className=\"xicms-ve__bar\">\n <strong className=\"xicms-ve__title\">Bewerken · {name}</strong>\n <span className=\"xicms-ve__path\">{path}</span>\n <button type=\"button\" className=\"xicms-ve__histbtn\" disabled={!canUndo} onClick={undo} title=\"Ongedaan maken (Ctrl+Z)\" aria-label=\"Ongedaan maken (Ctrl+Z)\">↶</button>\n <button type=\"button\" className=\"xicms-ve__histbtn\" disabled={!canRedo} onClick={redo} title=\"Opnieuw (Ctrl+Shift+Z)\" aria-label=\"Opnieuw (Ctrl+Shift+Z)\">↷</button>\n <span className=\"xicms-ve__status\" aria-live=\"polite\">\n {status === \"saving\" ? \"Opslaan…\" : status === \"saved\" ? \"✓ Opgeslagen\" : status === \"error\" ? \"⚠ Niet opgeslagen\" : \"\"}\n </span>\n {status === \"error\" && (\n <Button size=\"sm\" variant=\"secondary\" onClick={() => void flushSave().catch(() => setStatus(\"error\"))}>\n Opnieuw opslaan\n </Button>\n )}\n <div className=\"xicms-ve__bar-actions\">\n <Button size=\"sm\" disabled={publishing || !draft} onClick={onPublish}>\n {publishing ? \"Bezig…\" : \"Publiceren\"}\n </Button>\n <Button size=\"sm\" variant=\"secondary\" onClick={handleClose}>Sluiten</Button>\n </div>\n </header>\n\n {conflict && (\n <div className=\"xicms-ve__conflict\" role=\"alert\">\n <span>⚠ Deze pagina is intussen ergens anders gewijzigd (andere tab of collega). Doorwerken kan die wijzigingen overschrijven.</span>\n <Button size=\"sm\" onClick={() => void reloadRemote()}>Nieuwste versie laden</Button>\n <Button size=\"sm\" variant=\"secondary\" onClick={() => setConflict(false)}>Mijn versie behouden</Button>\n </div>\n )}\n\n <div className=\"xicms-ve__body\">\n <main className=\"xicms-ve__canvas\">\n {status === \"loading\" && <p className=\"xicms-ve__hint\">Laden…</p>}\n {status === \"none\" && (\n <EmptyState\n icon=\"📄\"\n title=\"Geen werkversie gevonden\"\n description={`Voor deze pagina (${path}) bestaat nog geen werkversie. Maak de pagina eerst aan in /beheer → Pagina's.`}\n />\n )}\n {draft && (\n <div className=\"container\" ref={canvasRef}>\n {blocks.length === 0 && (\n <EmptyState icon=\"🧱\" title=\"Nog geen blokken\" description=\"Voeg hieronder je eerste blok toe.\" />\n )}\n {blocks.map((b, i) => {\n const def = registry.get(b._type);\n const Render = def?.Render;\n // Coerce via het schema zodat defaults zijn toegepast (zoals de live pagina via\n // validateBlocks doet). Ruwe drafts missen anders defaults (bv. hero.buttons) en\n // zouden de Render laten throwen. Lukt het parsen niet, val terug op het ruwe blok\n // (de error-boundary hieronder vangt een eventuele fout dan netjes op).\n const safe = (() => {\n const parsed = def?.schema?.safeParse(b);\n return parsed?.success ? (parsed.data as Block) : b;\n })();\n const dropClass = dropIndex === i ? \" is-drop-before\" : dropIndex === i + 1 ? \" is-drop-after\" : \"\";\n return (\n <div\n key={b._key}\n className={`xicms-ve-block${b._key === selectedKey ? \" is-selected\" : \"\"}${dragIndex === i ? \" is-dragging\" : \"\"}${dropClass}`}\n onDragOver={(e) => {\n if (dragIndex === null) return;\n e.preventDefault();\n e.dataTransfer.dropEffect = \"move\";\n const rect = e.currentTarget.getBoundingClientRect();\n setDropIndex(e.clientY < rect.top + rect.height / 2 ? i : i + 1);\n }}\n onDrop={(e) => {\n if (dragIndex === null || dropIndex === null) return;\n e.preventDefault();\n setBlocks(reorderBlocks(blocks, dragIndex, dropIndex));\n endDrag();\n }}\n >\n <div className=\"xicms-ve-block__toolbar\">\n {/* Sleep-greep: apart element (niet het hele blok) zodat slepen nooit botst met\n het inline contentEditable-bewerken; ↑/↓ blijven het toetsenbord-alternatief. */}\n <button\n type=\"button\"\n className=\"xicms-ve-block__drag\"\n draggable\n onDragStart={(e) => {\n draggingRef.current = true;\n setDragIndex(i);\n e.dataTransfer.effectAllowed = \"move\";\n e.dataTransfer.setData(\"text/plain\", String(i));\n const blockEl = e.currentTarget.closest(\".xicms-ve-block\");\n if (blockEl) e.dataTransfer.setDragImage(blockEl, 24, 24);\n }}\n onDragEnd={endDrag}\n aria-label={`Versleep ${def?.label ?? b._type}`}\n title=\"Slepen om te verplaatsen\"\n >\n ⋮⋮\n </button>\n <span className=\"xicms-ve-block__label\">{def?.label ?? b._type}</span>\n <button type=\"button\" onClick={() => setSelectedKey(b._key)} aria-label={`Bewerk ${def?.label ?? b._type}`} title=\"Bewerken\">✏️</button>\n <button type=\"button\" disabled={i === 0} onClick={() => move(i, -1)} aria-label=\"Omhoog\">↑</button>\n <button type=\"button\" disabled={i === blocks.length - 1} onClick={() => move(i, 1)} aria-label=\"Omlaag\">↓</button>\n <button type=\"button\" onClick={() => duplicate(i)} aria-label=\"Dupliceren\">⎘</button>\n <button type=\"button\" onClick={() => void remove(i)} aria-label=\"Verwijderen\">🗑</button>\n </div>\n {/* Altijd renderen (geen conditie) → geen remount van de inhoud bij selecteren,\n anders verliest het inline-bewerken zijn contentEditable. Bij selectie zet de\n CSS deze overlay op pointer-events:none zodat je de tekst kunt bewerken. */}\n <button type=\"button\" className=\"xicms-ve-block__select\" aria-label={`Selecteer ${def?.label ?? b._type}`} onClick={() => setSelectedKey(b._key)} />\n <div className=\"xicms-ve-block__content\" data-ve-content={b._key}>\n {/* Net als BlockRenderer: elk blok in een per-blok error-boundary zodat één\n falend (maatwerk-)blok de hele editor niet blank maakt. mode=\"preview\". */}\n {Render ? (\n <BlockErrorBoundary mode=\"preview\" type={b._type} blockKey={b._key}>\n {/* Alleen het geselecteerde blok krijgt de inline beeld-vervangen-overlay. */}\n <InlineImageEditProvider onReplace={b._key === selectedKey ? (field) => setImgPicker({ key: b._key, field }) : null}>\n <Render block={safe} siteId={siteId} mode=\"preview\" />\n </InlineImageEditProvider>\n </BlockErrorBoundary>\n ) : (\n <p className=\"xicms-ve__hint\">Onbekend blok: {b._type}</p>\n )}\n </div>\n </div>\n );\n })}\n <div className=\"xicms-ve__inserter\">\n <span>Toevoegen{selected ? \" (na selectie)\" : \"\"}:</span>\n <div className=\"xicms-ve__inserter-select\">\n <Select value=\"\" placeholder=\"Kies een blok…\" onValueChange={(v) => v && addBlock(v)} options={addOptions} />\n </div>\n <PatternInserter onInsert={insertAfterSelected} />\n </div>\n </div>\n )}\n </main>\n\n <aside className=\"xicms-ve__panel\">\n {selected ? (\n <SelectedEditor key={selected._key} block={selected} siteId={siteId} registry={registry} onChange={(next) => updateByKey(selected._key, next)} />\n ) : draft ? (\n <div className=\"grid gap-4\">\n <p className=\"xicms-ve__hint\">Klik op een blok om het te bewerken.</p>\n <PageSettings draft={draft} onChange={(part) => mutateDraft((d) => ({ ...d, ...part }), { coalesce: true })} />\n {!isDemoActive() && (\n <VersionsPanel\n siteId={siteId}\n pageId={draft.id}\n onRestore={(restored) => {\n mutateDraft((d) => ({ ...d, ...restored }));\n toast({ title: \"Versie hersteld naar werkversie\", description: \"Publiceer om hem live te zetten.\", tone: \"success\" });\n }}\n />\n )}\n </div>\n ) : (\n <p className=\"xicms-ve__hint\">Klik op een blok om het te bewerken.</p>\n )}\n </aside>\n </div>\n\n {/* Inline afbeelding vervangen: opent de mediabibliotheek; alleen de URL van het veld wordt gezet. */}\n <MediaPickerDialog\n siteId={siteId}\n open={!!imgPicker}\n onOpenChange={(v) => { if (!v) setImgPicker(null); }}\n onSelect={(m) => { if (imgPicker) commitInline(imgPicker.key, imgPicker.field, m.url); setImgPicker(null); }}\n />\n </div>\n );\n}\n\n/**\n * Pagina-instellingen (titel + SEO) direct in de visuele editor — de klant hoeft niet meer naar\n * /beheer voor een titel- of meta-omschrijving-wijziging. Slug/pad blijven bewust in /beheer\n * (die raken URL-structuur en zijn niet iets om per ongeluk inline aan te passen).\n */\nfunction PageSettings({\n draft,\n onChange,\n}: {\n draft: DraftPage;\n onChange: (part: Partial<DraftPage>) => void;\n}) {\n const seo = draft.seo ?? {};\n return (\n <details className=\"xicms-ve__section\" open>\n <summary className=\"text-sm font-semibold\">Pagina-instellingen</summary>\n <div className=\"mt-2 grid gap-3\">\n <Field label=\"Paginatitel\">\n <Input value={draft.title ?? \"\"} onChange={(e) => onChange({ title: e.target.value })} />\n </Field>\n <Field label=\"SEO-titel\" hint=\"Leeg = paginatitel\">\n <Input value={seo.title ?? \"\"} onChange={(e) => onChange({ seo: { ...seo, title: e.target.value } })} />\n </Field>\n <Field label=\"SEO-omschrijving\" hint=\"Wordt getoond in zoekresultaten\">\n <Textarea rows={3} value={seo.description ?? \"\"} onChange={(e) => onChange({ seo: { ...seo, description: e.target.value } })} />\n </Field>\n </div>\n </details>\n );\n}\n\n/** Versiegeschiedenis in de visuele editor: herstellen zet een snapshot terug naar de WERKVERSIE. */\nfunction VersionsPanel({\n siteId,\n pageId,\n onRestore,\n}: {\n siteId: string;\n pageId: string;\n onRestore: (restored: Partial<DraftPage>) => void;\n}) {\n const [versions, setVersions] = useState<{ id: string; createdAt?: unknown; kind?: string; label?: string }[]>([]);\n const { toast } = useToast();\n const confirm = useConfirm();\n useEffect(() => subscribeVersions(siteId, pageId, setVersions), [siteId, pageId]);\n if (versions.length === 0) return null;\n\n const restore = async (versionId: string) => {\n const ok = await confirm({\n title: \"Versie herstellen?\",\n description: \"De snapshot vervangt je huidige werkversie (ongedaan te maken met Ctrl+Z). De live site verandert pas bij Publiceren.\",\n confirmLabel: \"Herstellen\",\n });\n if (!ok) return;\n try {\n const restored = await restoreVersion(siteId, pageId, versionId);\n onRestore(restored);\n } catch (e) {\n toast({ title: \"Herstellen mislukt\", description: (e as Error).message, tone: \"error\" });\n }\n };\n\n return (\n <details className=\"xicms-ve__section\">\n <summary className=\"text-sm font-semibold\">Versiegeschiedenis</summary>\n <ul className=\"mt-2 grid gap-1 text-sm\">\n {versions.slice(0, 10).map((v) => (\n <li key={v.id} className=\"flex items-center justify-between gap-2\">\n <span>\n {v.label ?? (v.kind === \"publish\" ? \"Gepubliceerd\" : 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 restore(v.id)}>\n Herstellen\n </Button>\n </li>\n ))}\n </ul>\n </details>\n );\n}\n\nfunction formatVersionDate(createdAt: unknown): string {\n const date =\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 return date && !Number.isNaN(date.getTime()) ? date.toLocaleString(\"nl-NL\", { dateStyle: \"short\", timeStyle: \"short\" }) : \"\";\n}\n\nfunction SelectedEditor({\n block,\n siteId,\n registry,\n onChange,\n}: {\n block: Block;\n siteId: string;\n registry: ReturnType<typeof useRegistry>;\n onChange: (next: Record<string, unknown>) => void;\n}) {\n const def = registry.get(block._type);\n const Editor: ComponentType<BlockEditorProps> =\n (def?.Editor as ComponentType<BlockEditorProps> | undefined) ?? builtinBlockEditors[block._type] ?? GenericEditor;\n return (\n <div className=\"grid gap-3\">\n <h3 className=\"text-sm font-semibold\">{def?.label ?? block._type}</h3>\n {/* Maatwerk-Editors kunnen falen → vang per-blok op zodat het paneel niet leeg wordt. */}\n <BlockErrorBoundary mode=\"preview\" type={block._type} blockKey={block._key}>\n <Editor block={block} siteId={siteId} onChange={onChange} />\n </BlockErrorBoundary>\n <StyleEditor\n value={block._style as BlockStyle | undefined}\n onChange={(style) => onChange({ ...block, _style: style })}\n />\n </div>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/visual/VisualEditor.tsx"],"sourcesContent":["\"use client\";\n\n// De admin-stijlen (Tailwind, scoped onder .xicms-admin) horen bij dit lazy-geladen chunk, zodat\n// publieke bezoekers ze nooit downloaden — alleen wanneer een editor de pagina-editor opent.\nimport \"@xiwebdesign/xicms-core/admin.css\";\nimport { useCallback, useEffect, useMemo, useRef, useState, type ComponentType } from \"react\";\nimport { useSiteConfig } from \"../../config\";\nimport { useRegistry } from \"../../blocks/context\";\nimport { BlockErrorBoundary } from \"../../blocks/BlockErrorBoundary\";\nimport { InlineImageEditProvider } from \"../../media/ImageContext\";\nimport { MediaPickerDialog } from \"../media/MediaPicker\";\nimport { EmptyState } from \"@astryxdesign/core/EmptyState\";\nimport { buildBlockAddOptions } from \"../../blocks/registry\";\nimport { getDraftByPath, saveDraft, publishPage, subscribeDraft, subscribeVersions, restoreVersion, type DraftPage } from \"../lib/pagesClient\";\nimport { isDemoActive } from \"../../demo/demoStore\";\nimport { builtinBlockEditors, GenericEditor, type BlockEditorProps } from \"../blockEditors\";\nimport { StyleEditor } from \"../blockEditors/StyleEditor\";\nimport { reorderBlocks } from \"./reorder\";\nimport { PatternInserter } from \"../patterns/PatternInserter\";\nimport type { BlockStyle } from \"../../schema/blocks\";\nimport { Button, Field, Input, Textarea } from \"../ui/primitives\";\nimport { Select } from \"../ui/select\";\nimport { ToastProvider, useToast } from \"../ui/toast\";\nimport { ConfirmHost, useConfirm } from \"../ui/ConfirmDialog\";\n\ntype Block = Record<string, unknown> & { _key: string; _type: string };\ntype Status = \"loading\" | \"none\" | \"idle\" | \"saving\" | \"saved\" | \"error\";\n\n/**\n * Welke tekstvelden per bloktype **inline** (direct in het canvas) bewerkbaar zijn, gemapt op hun\n * stabiele DOM-klasse — zo hoeft de blok-Render niet aangepast te worden. `html: true` synct innerHTML\n * (rich text), anders platte tekst. Elk blok kan ook zelf velden markeren met `data-xicms-edit=\"veld\"`.\n */\nconst INLINE_FIELDS: Record<string, { selector: string; field: string; html?: boolean }[]> = {\n hero: [\n { selector: \".xicms-hero__title\", field: \"title\" },\n { selector: \".xicms-hero__sub\", field: \"subtitle\" },\n ],\n};\n\n/** Selectors die in ELK blok inline bewerkbaar zijn: rich-text-inhoud (`.xicms-richtext`, gebruikt door\n * richText én feature) en velden die een blok zelf markeert met `data-xicms-edit=\"veld\"`. */\nconst GLOBAL_INLINE = [\n { selector: \".xicms-richtext\", field: \"html\", html: true as const },\n { selector: \"[data-xicms-edit]\", field: \"\", html: false as const, fromData: true as const },\n];\n\n/* Lees/schrijf een waarde op een gepunt pad (bv. \"items.0.title\") — zodat ook lijst-items inline\n bewerkt kunnen worden. setPath kloont onderweg (immutable) zodat React de wijziging detecteert. */\nfunction getPath(obj: unknown, path: string): unknown {\n return path.split(\".\").reduce<unknown>((o, k) => (o == null ? undefined : (o as Record<string, unknown>)[k]), obj);\n}\nfunction setPath<T>(obj: T, path: string, value: unknown): T {\n const keys = path.split(\".\");\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const clone = (v: unknown): any => (Array.isArray(v) ? [...v] : v && typeof v === \"object\" ? { ...(v as object) } : v);\n const root = clone(obj);\n let cur = root;\n for (let i = 0; i < keys.length - 1; i++) {\n cur[keys[i]] = clone(cur[keys[i]] ?? {});\n cur = cur[keys[i]];\n }\n cur[keys[keys.length - 1]] = value;\n return root as T;\n}\n\nfunction newKey() {\n return (globalThis.crypto?.randomUUID?.() ?? `b${Date.now()}${Math.random()}`).slice(0, 12);\n}\n\n/** JSON.stringify met gesorteerde sleutels — voor betrouwbare inhoudsvergelijking (conflictdetectie):\n * Firestore kan velden in een andere volgorde teruggeven dan wij ze lokaal opbouwden. */\nfunction stableStringify(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(stableStringify).join(\",\")}]`;\n if (value && typeof value === \"object\") {\n const entries = Object.entries(value as Record<string, unknown>)\n .filter(([, v]) => v !== undefined)\n .sort(([a], [b]) => (a < b ? -1 : 1));\n return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`).join(\",\")}}`;\n }\n return JSON.stringify(value) ?? \"null\";\n}\n\n/**\n * Visuele/inline pagina-editor: opent als overlay over de live pagina, laadt de werkversie van de\n * huidige route, en laat de editor blokken op een live canvas selecteren, herordenen, dupliceren,\n * verwijderen en toevoegen — met autosave en publiceren. De zware code laadt pas bij openen\n * (lazy, via EditorProvider), dus bezoekers downloaden hem nooit.\n */\nexport default function VisualEditor({ onClose }: { onClose: () => void }) {\n return (\n <div className=\"xicms-admin xicms-ve\">\n <ToastProvider>\n <ConfirmHost>\n <VisualEditorInner onClose={onClose} />\n </ConfirmHost>\n </ToastProvider>\n </div>\n );\n}\n\nfunction VisualEditorInner({ onClose }: { onClose: () => void }) {\n const { siteId, name } = useSiteConfig();\n const registry = useRegistry();\n const { toast } = useToast();\n const confirm = useConfirm();\n\n const path = useMemo(() => (typeof window !== \"undefined\" ? window.location.pathname || \"/\" : \"/\"), []);\n const [draft, setDraft] = useState<DraftPage | null>(null);\n const [status, setStatus] = useState<Status>(\"loading\");\n const [selectedKey, setSelectedKey] = useState<string | null>(null);\n const [publishing, setPublishing] = useState(false);\n const [imgPicker, setImgPicker] = useState<{ key: string; field: string } | null>(null);\n const firstSave = useRef(true);\n const canvasRef = useRef<HTMLDivElement>(null);\n const [conflict, setConflict] = useState(false);\n\n // Drag-and-drop herordenen: dragIndex = het gesleepte blok, dropIndex = invoegpunt (0..length).\n // draggingRef pauzeert de inline-bind MutationObserver tijdens het slepen (re-bind-ruis).\n const [dragIndex, setDragIndex] = useState<number | null>(null);\n const [dropIndex, setDropIndex] = useState<number | null>(null);\n const draggingRef = useRef(false);\n const endDrag = useCallback(() => {\n draggingRef.current = false;\n setDragIndex(null);\n setDropIndex(null);\n }, []);\n\n // Altijd de laatste draft + \"zijn er niet-opgeslagen wijzigingen\" bij de hand houden, zodat we\n // bij sluiten/wegnavigeren nog kunnen flushen (de debounce zou de laatste 700ms anders weggooien).\n const draftRef = useRef<DraftPage | null>(null);\n const dirtyRef = useRef(false);\n // Inhoud (stable-stringified) van de laatst succesvol opgeslagen payload — voor conflictdetectie.\n const lastSavedRef = useRef<string | null>(null);\n useEffect(() => {\n draftRef.current = draft;\n }, [draft]);\n\n // ── Undo/redo ──────────────────────────────────────────────────────────────\n // History over draft-snapshots. Tekstedits (per toetsaanslag) worden ge-coalesced: binnen één\n // typ-burst (<800ms) staat het pre-burst-snapshot al op de stack, dus niet opnieuw pushen —\n // Ctrl+Z springt dan per burst terug i.p.v. per letter. `histVersion` forceert re-render van\n // de knop-states (de stacks leven in een ref).\n const historyRef = useRef<{ past: DraftPage[]; future: DraftPage[]; lastPush: number }>({ past: [], future: [], lastPush: 0 });\n const [, setHistVersion] = useState(0);\n\n const pushHistory = useCallback((prev: DraftPage, coalesce: boolean) => {\n const h = historyRef.current;\n const now = Date.now();\n if (!(coalesce && now - h.lastPush < 800 && h.past.length > 0)) {\n h.past.push(prev);\n if (h.past.length > 100) h.past.shift();\n }\n h.lastPush = now;\n h.future = [];\n setHistVersion((v) => v + 1);\n }, []);\n\n /** Enige route waarlangs de draft muteert: bewaart eerst een undo-snapshot. */\n const mutateDraft = useCallback(\n (updater: (d: DraftPage) => DraftPage, opts?: { coalesce?: boolean }) => {\n const d = draftRef.current;\n if (!d) return;\n const next = updater(d);\n if (next === d) return;\n pushHistory(d, opts?.coalesce ?? false);\n draftRef.current = next;\n setDraft(next);\n },\n [pushHistory],\n );\n\n const undo = useCallback(() => {\n const h = historyRef.current;\n const d = draftRef.current;\n const prev = h.past.pop();\n if (!prev || !d) return;\n h.future.push(d);\n h.lastPush = 0;\n draftRef.current = prev;\n setDraft(prev);\n setHistVersion((v) => v + 1);\n }, []);\n\n const redo = useCallback(() => {\n const h = historyRef.current;\n const d = draftRef.current;\n const next = h.future.pop();\n if (!next || !d) return;\n h.past.push(d);\n h.lastPush = 0;\n draftRef.current = next;\n setDraft(next);\n setHistVersion((v) => v + 1);\n }, []);\n\n const draftPayload = (d: DraftPage) => ({\n blocks: d.blocks,\n title: d.title,\n slug: d.slug,\n path: d.path,\n seo: d.seo,\n });\n\n // Schrijf eventuele pending wijziging direct weg (gebruikt bij Sluiten, tab-wissel en retry).\n const flushSave = useCallback(async () => {\n const d = draftRef.current;\n if (!d || !dirtyRef.current) return;\n const payload = draftPayload(d);\n await saveDraft(siteId, d.id, payload);\n lastSavedRef.current = stableStringify(payload);\n // Alleen \"schoon\" markeren als er intussen niets nieuws is getypt (anders zou een trage\n // write nieuwere wijzigingen als opgeslagen bestempelen).\n if (draftRef.current === d) {\n dirtyRef.current = false;\n setStatus(\"saved\");\n }\n }, [siteId]);\n\n const handleClose = useCallback(() => {\n void flushSave().finally(() => onClose());\n }, [flushSave, onClose]);\n\n useEffect(() => {\n let active = true;\n getDraftByPath(siteId, path)\n .then((d) => {\n if (!active) return;\n if (d) {\n setDraft(d);\n lastSavedRef.current = stableStringify({ blocks: d.blocks, title: d.title, slug: d.slug, path: d.path, seo: d.seo });\n setStatus(\"idle\");\n } else {\n setStatus(\"none\");\n }\n })\n .catch(() => active && setStatus(\"none\"));\n return () => {\n active = false;\n };\n }, [siteId, path]);\n\n // ── Conflictdetectie ───────────────────────────────────────────────────────\n // Luister naar de draft in Firestore. Een server-snapshot die afwijkt van zowel onze laatst\n // opgeslagen payload als onze huidige lokale staat, betekent: iemand anders (tab/collega)\n // heeft deze pagina gewijzigd. Waarschuw i.p.v. elkaars werk stil te overschrijven.\n const draftId = draft?.id;\n useEffect(() => {\n if (!draftId) return;\n return subscribeDraft(siteId, draftId, (remote, meta) => {\n if (!meta.fromServer || !remote) return;\n const remotePayload = stableStringify({ blocks: remote.blocks, title: remote.title, slug: remote.slug, path: remote.path, seo: remote.seo });\n const local = draftRef.current;\n const localPayload = local ? stableStringify(draftPayload(local)) : null;\n if (remotePayload !== lastSavedRef.current && remotePayload !== localPayload) setConflict(true);\n });\n }, [siteId, draftId]);\n\n const blocks = (draft?.blocks ?? []) as Block[];\n\n // Schrijf een inline (in-canvas) tekstwijziging terug naar het juiste blokveld.\n const commitInline = useCallback(\n (key: string, field: string, value: string) => {\n mutateDraft(\n (d) => {\n const bl = d.blocks as Block[];\n const idx = bl.findIndex((x) => x._key === key);\n if (idx < 0 || getPath(bl[idx], field) === value) return d;\n const next = [...bl];\n next[idx] = setPath(next[idx], field, value);\n return { ...d, blocks: next };\n },\n { coalesce: true },\n );\n },\n [mutateDraft],\n );\n\n // Maak de tekstvelden van het GESELECTEERDE blok ter plekke bewerkbaar (contentEditable) en synct\n // op blur terug — zo bewerk je tekst inline op het canvas i.p.v. via de zijbalk. We binden via een\n // MutationObserver én her-binden zodra React de DOM-node vervangt (re-render), met een dataset-flag\n // zodat we nooit dubbele listeners zetten.\n useEffect(() => {\n if (!selectedKey) return;\n const host = canvasRef.current?.querySelector<HTMLElement>(`[data-ve-content=\"${selectedKey}\"]`);\n if (!host) return;\n const type = blocks.find((b) => b._key === selectedKey)?._type ?? \"\";\n const specs = [...(INLINE_FIELDS[type] ?? []), ...GLOBAL_INLINE];\n\n const bound = new Set<HTMLElement>();\n const bind = (el: HTMLElement, field: string, html: boolean) => {\n if (el.dataset.veInline === \"1\") return;\n el.dataset.veInline = \"1\";\n el.setAttribute(\"contenteditable\", \"true\");\n el.setAttribute(\"spellcheck\", \"true\");\n el.classList.add(\"xicms-ve-inline\");\n el.addEventListener(\"blur\", () => commitInline(selectedKey, field, html ? el.innerHTML : (el.textContent ?? \"\")));\n el.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Escape\") el.blur();\n else if (!html && e.key === \"Enter\") { e.preventDefault(); el.blur(); }\n });\n bound.add(el);\n };\n const apply = () => {\n for (const m of specs) {\n host.querySelectorAll<HTMLElement>(m.selector).forEach((el) =>\n bind(el, \"fromData\" in m && m.fromData ? (el.dataset.xicmsEdit as string) : m.field, !!m.html),\n );\n }\n };\n\n apply();\n // Her-bind na elke re-render die de tekstnodes vervangt (niet tijdens slepen: dan muteert de\n // DOM continu door de drop-indicatoren en zou her-binden alleen maar ruis geven).\n const obs = new MutationObserver(() => {\n if (!draggingRef.current) apply();\n });\n obs.observe(host, { childList: true, subtree: true });\n return () => {\n obs.disconnect();\n bound.forEach((el) => {\n el.removeAttribute(\"contenteditable\");\n el.classList.remove(\"xicms-ve-inline\");\n delete el.dataset.veInline;\n });\n };\n }, [selectedKey, blocks, commitInline]);\n\n // Autosave naar de draft (publiceren blijft een aparte, bewuste stap).\n // Debounce 700ms ná de laatste wijziging, met een MAX-WAIT van 3s: de debounce herstart bij elke\n // toetsaanslag, dus zonder max-wait zou iemand die blijft doortypen nooit tussentijds opslaan.\n const dirtySinceRef = useRef<number | null>(null);\n useEffect(() => {\n if (!draft) return;\n if (firstSave.current) {\n firstSave.current = false;\n return;\n }\n dirtyRef.current = true;\n dirtySinceRef.current ??= Date.now();\n setStatus(\"saving\");\n const waited = Date.now() - dirtySinceRef.current;\n const delay = Math.max(0, Math.min(700, 3000 - waited));\n const t = setTimeout(() => {\n dirtySinceRef.current = null;\n const payload = draftPayload(draft);\n void saveDraft(siteId, draft.id, payload)\n .then(() => {\n lastSavedRef.current = stableStringify(payload);\n // Alleen \"schoon\"/\"saved\" als dit nog de nieuwste versie is (zie flushSave).\n if (draftRef.current === draft) {\n dirtyRef.current = false;\n setStatus(\"saved\");\n }\n })\n .catch(() => setStatus(\"error\"));\n }, delay);\n return () => clearTimeout(t);\n }, [draft, siteId]);\n\n // Waarschuw bij het verlaten van de pagina met niet-opgeslagen wijzigingen; flush bij tab-wissel/\n // verbergen (visibilitychange) en bij unmount. Escape sluit de editor (na flush).\n useEffect(() => {\n const beforeUnload = (e: BeforeUnloadEvent) => {\n if (dirtyRef.current) {\n e.preventDefault();\n e.returnValue = \"\";\n }\n };\n const onVisibility = () => {\n // Tab-wissel/minimaliseren: beste moment om pending werk veilig te stellen.\n if (document.visibilityState === \"hidden\") void flushSave().catch(() => {});\n };\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") {\n // Escape sluit alléén de editor als er niets \"boven\" de editor actief is: een open\n // dialog (mediakiezer, bevestiging — Radix rendert [role=dialog] buiten deze shell) of\n // een inline bewerking/invoerveld vangt zijn eigen Escape. Anders verdween bij het\n // sluiten van de mediakiezer meteen de hele editor.\n const t = e.target as HTMLElement | null;\n const inField = !!t && (t.isContentEditable || t.tagName === \"INPUT\" || t.tagName === \"TEXTAREA\" || t.tagName === \"SELECT\");\n const dialogOpen = document.querySelector('[role=\"dialog\"][data-state=\"open\"], [role=\"alertdialog\"][data-state=\"open\"]');\n if (!inField && !dialogOpen) handleClose();\n return;\n }\n // Undo/redo op canvas-niveau. In invoervelden/contentEditable laten we de native\n // tekst-undo van de browser zijn werk doen (anders vecht Ctrl+Z met het veld zelf).\n const t = e.target as HTMLElement | null;\n const inField = !!t && (t.isContentEditable || t.tagName === \"INPUT\" || t.tagName === \"TEXTAREA\" || t.tagName === \"SELECT\");\n if ((e.metaKey || e.ctrlKey) && !inField && (e.key.toLowerCase() === \"z\" || e.key.toLowerCase() === \"y\")) {\n e.preventDefault();\n if (e.key.toLowerCase() === \"y\" || e.shiftKey) redo();\n else undo();\n }\n };\n window.addEventListener(\"beforeunload\", beforeUnload);\n document.addEventListener(\"visibilitychange\", onVisibility);\n window.addEventListener(\"keydown\", onKeyDown);\n return () => {\n window.removeEventListener(\"beforeunload\", beforeUnload);\n document.removeEventListener(\"visibilitychange\", onVisibility);\n window.removeEventListener(\"keydown\", onKeyDown);\n const d = draftRef.current;\n if (d && dirtyRef.current) {\n // Laatste redmiddel bij unmount; log een mislukking i.p.v. stil verliezen.\n void saveDraft(siteId, d.id, draftPayload(d)).catch((err) =>\n console.warn(\"[xicms] autosave bij sluiten mislukt:\", err),\n );\n }\n };\n }, [siteId, handleClose, flushSave, undo, redo]);\n\n function setBlocks(next: Block[], opts?: { coalesce?: boolean }) {\n mutateDraft((d) => ({ ...d, blocks: next }), opts);\n }\n function updateByKey(key: string, next: Record<string, unknown>) {\n // Zijpaneel-edits komen per toetsaanslag binnen → coalesce voor zinnige undo-stappen.\n setBlocks(blocks.map((b) => (b._key === key ? ({ ...(next as Block), _key: b._key, _type: b._type }) : b)), { coalesce: true });\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 function duplicate(i: number) {\n const copy = [...blocks];\n copy.splice(i + 1, 0, { ...blocks[i], _key: newKey() });\n setBlocks(copy);\n }\n async function remove(i: number) {\n const ok = await confirm({ title: \"Blok verwijderen?\", confirmLabel: \"Verwijderen\", tone: \"danger\" });\n if (!ok) return;\n const removed = blocks[i];\n setBlocks(blocks.filter((_, idx) => idx !== i));\n if (selectedKey === removed._key) setSelectedKey(null);\n }\n function addBlock(type: string) {\n const def = registry.get(type);\n const base = def?.defaults?.() ?? {};\n const block = { _key: newKey(), _type: type, ...base } as Block;\n insertAfterSelected([block]);\n setSelectedKey(block._key);\n }\n function insertAfterSelected(newBlocks: Array<Record<string, unknown>>) {\n const sel = blocks.findIndex((b) => b._key === selectedKey);\n const copy = [...blocks];\n copy.splice(sel >= 0 ? sel + 1 : blocks.length, 0, ...(newBlocks as Block[]));\n setBlocks(copy);\n }\n\n async function onPublish() {\n if (!draft) return;\n const ok = await confirm({ title: \"Pagina publiceren?\", description: `De live site wordt bijgewerkt (${draft.path}).`, confirmLabel: \"Publiceren\" });\n if (!ok) return;\n setPublishing(true);\n try {\n await saveDraft(siteId, draft.id, { blocks: draft.blocks, title: draft.title, slug: draft.slug, path: draft.path, seo: draft.seo });\n await publishPage(siteId, draft.id);\n toast({ title: \"Gepubliceerd\", description: \"Herlaad de pagina om het live resultaat te zien.\", tone: \"success\" });\n } catch (e) {\n toast({ title: \"Publiceren mislukt\", description: (e as Error).message, tone: \"error\" });\n } finally {\n setPublishing(false);\n }\n }\n\n const addOptions = useMemo(() => buildBlockAddOptions(registry.values()), [registry]);\n\n const selectedIndex = blocks.findIndex((b) => b._key === selectedKey);\n const selected = selectedIndex >= 0 ? blocks[selectedIndex] : null;\n const canUndo = historyRef.current.past.length > 0;\n const canRedo = historyRef.current.future.length > 0;\n\n /** Laad de nieuwste (remote) versie in de editor — bij een gedetecteerd conflict. */\n const reloadRemote = useCallback(async () => {\n const remote = await getDraftByPath(siteId, path).catch(() => null);\n if (remote) {\n mutateDraft(() => remote);\n lastSavedRef.current = stableStringify(draftPayload(remote));\n dirtyRef.current = false;\n setStatus(\"idle\");\n }\n setConflict(false);\n }, [siteId, path, mutateDraft]);\n\n return (\n <div className=\"xicms-ve__shell\" role=\"dialog\" aria-modal=\"true\" aria-label={`Pagina bewerken: ${path}`}>\n <header className=\"xicms-ve__bar\">\n <strong className=\"xicms-ve__title\">Bewerken · {name}</strong>\n <span className=\"xicms-ve__path\">{path}</span>\n <button type=\"button\" className=\"xicms-ve__histbtn\" disabled={!canUndo} onClick={undo} title=\"Ongedaan maken (Ctrl+Z)\" aria-label=\"Ongedaan maken (Ctrl+Z)\">↶</button>\n <button type=\"button\" className=\"xicms-ve__histbtn\" disabled={!canRedo} onClick={redo} title=\"Opnieuw (Ctrl+Shift+Z)\" aria-label=\"Opnieuw (Ctrl+Shift+Z)\">↷</button>\n <span className=\"xicms-ve__status\" aria-live=\"polite\">\n {status === \"saving\" ? \"Opslaan…\" : status === \"saved\" ? \"✓ Opgeslagen\" : status === \"error\" ? \"⚠ Niet opgeslagen\" : \"\"}\n </span>\n {status === \"error\" && (\n <Button size=\"sm\" variant=\"secondary\" onClick={() => void flushSave().catch(() => setStatus(\"error\"))}>\n Opnieuw opslaan\n </Button>\n )}\n <div className=\"xicms-ve__bar-actions\">\n <Button size=\"sm\" disabled={publishing || !draft} onClick={onPublish}>\n {publishing ? \"Bezig…\" : \"Publiceren\"}\n </Button>\n <Button size=\"sm\" variant=\"secondary\" onClick={handleClose}>Sluiten</Button>\n </div>\n </header>\n\n {conflict && (\n <div className=\"xicms-ve__conflict\" role=\"alert\">\n <span>⚠ Deze pagina is intussen ergens anders gewijzigd (andere tab of collega). Doorwerken kan die wijzigingen overschrijven.</span>\n <Button size=\"sm\" onClick={() => void reloadRemote()}>Nieuwste versie laden</Button>\n <Button size=\"sm\" variant=\"secondary\" onClick={() => setConflict(false)}>Mijn versie behouden</Button>\n </div>\n )}\n\n <div className=\"xicms-ve__body\">\n <main className=\"xicms-ve__canvas\">\n {status === \"loading\" && <p className=\"xicms-ve__hint\">Laden…</p>}\n {status === \"none\" && (\n <EmptyState\n icon=\"📄\"\n title=\"Geen werkversie gevonden\"\n description={`Voor deze pagina (${path}) bestaat nog geen werkversie. Maak de pagina eerst aan in /beheer → Pagina's.`}\n />\n )}\n {draft && (\n <div className=\"container\" ref={canvasRef}>\n {blocks.length === 0 && (\n <EmptyState icon=\"🧱\" title=\"Nog geen blokken\" description=\"Voeg hieronder je eerste blok toe.\" />\n )}\n {blocks.map((b, i) => {\n const def = registry.get(b._type);\n const Render = def?.Render;\n // Coerce via het schema zodat defaults zijn toegepast (zoals de live pagina via\n // validateBlocks doet). Ruwe drafts missen anders defaults (bv. hero.buttons) en\n // zouden de Render laten throwen. Lukt het parsen niet, val terug op het ruwe blok\n // (de error-boundary hieronder vangt een eventuele fout dan netjes op).\n const safe = (() => {\n const parsed = def?.schema?.safeParse(b);\n return parsed?.success ? (parsed.data as Block) : b;\n })();\n const dropClass = dropIndex === i ? \" is-drop-before\" : dropIndex === i + 1 ? \" is-drop-after\" : \"\";\n return (\n <div\n key={b._key}\n className={`xicms-ve-block${b._key === selectedKey ? \" is-selected\" : \"\"}${dragIndex === i ? \" is-dragging\" : \"\"}${dropClass}`}\n onDragOver={(e) => {\n if (dragIndex === null) return;\n e.preventDefault();\n e.dataTransfer.dropEffect = \"move\";\n const rect = e.currentTarget.getBoundingClientRect();\n setDropIndex(e.clientY < rect.top + rect.height / 2 ? i : i + 1);\n }}\n onDrop={(e) => {\n if (dragIndex === null || dropIndex === null) return;\n e.preventDefault();\n setBlocks(reorderBlocks(blocks, dragIndex, dropIndex));\n endDrag();\n }}\n >\n <div className=\"xicms-ve-block__toolbar\">\n {/* Sleep-greep: apart element (niet het hele blok) zodat slepen nooit botst met\n het inline contentEditable-bewerken; ↑/↓ blijven het toetsenbord-alternatief. */}\n <button\n type=\"button\"\n className=\"xicms-ve-block__drag\"\n draggable\n onDragStart={(e) => {\n draggingRef.current = true;\n setDragIndex(i);\n e.dataTransfer.effectAllowed = \"move\";\n e.dataTransfer.setData(\"text/plain\", String(i));\n const blockEl = e.currentTarget.closest(\".xicms-ve-block\");\n if (blockEl) e.dataTransfer.setDragImage(blockEl, 24, 24);\n }}\n onDragEnd={endDrag}\n aria-label={`Versleep ${def?.label ?? b._type}`}\n title=\"Slepen om te verplaatsen\"\n >\n ⋮⋮\n </button>\n <span className=\"xicms-ve-block__label\">{def?.label ?? b._type}</span>\n <button type=\"button\" onClick={() => setSelectedKey(b._key)} aria-label={`Bewerk ${def?.label ?? b._type}`} title=\"Bewerken\">✏️</button>\n <button type=\"button\" disabled={i === 0} onClick={() => move(i, -1)} aria-label=\"Omhoog\">↑</button>\n <button type=\"button\" disabled={i === blocks.length - 1} onClick={() => move(i, 1)} aria-label=\"Omlaag\">↓</button>\n <button type=\"button\" onClick={() => duplicate(i)} aria-label=\"Dupliceren\">⎘</button>\n <button type=\"button\" onClick={() => void remove(i)} aria-label=\"Verwijderen\">🗑</button>\n </div>\n {/* Altijd renderen (geen conditie) → geen remount van de inhoud bij selecteren,\n anders verliest het inline-bewerken zijn contentEditable. Bij selectie zet de\n CSS deze overlay op pointer-events:none zodat je de tekst kunt bewerken. */}\n <button type=\"button\" className=\"xicms-ve-block__select\" aria-label={`Selecteer ${def?.label ?? b._type}`} onClick={() => setSelectedKey(b._key)} />\n <div className=\"xicms-ve-block__content\" data-ve-content={b._key}>\n {/* Net als BlockRenderer: elk blok in een per-blok error-boundary zodat één\n falend (maatwerk-)blok de hele editor niet blank maakt. mode=\"preview\". */}\n {Render ? (\n <BlockErrorBoundary mode=\"preview\" type={b._type} blockKey={b._key}>\n {/* Alleen het geselecteerde blok krijgt de inline beeld-vervangen-overlay. */}\n <InlineImageEditProvider onReplace={b._key === selectedKey ? (field) => setImgPicker({ key: b._key, field }) : null}>\n <Render block={safe} siteId={siteId} mode=\"preview\" />\n </InlineImageEditProvider>\n </BlockErrorBoundary>\n ) : (\n <p className=\"xicms-ve__hint\">Onbekend blok: {b._type}</p>\n )}\n </div>\n </div>\n );\n })}\n <div className=\"xicms-ve__inserter\">\n <span>Toevoegen{selected ? \" (na selectie)\" : \"\"}:</span>\n <div className=\"xicms-ve__inserter-select\">\n <Select value=\"\" placeholder=\"Kies een blok…\" onValueChange={(v) => v && addBlock(v)} options={addOptions} />\n </div>\n <PatternInserter onInsert={insertAfterSelected} />\n </div>\n </div>\n )}\n </main>\n\n <aside className=\"xicms-ve__panel\">\n {selected ? (\n <SelectedEditor key={selected._key} block={selected} siteId={siteId} registry={registry} onChange={(next) => updateByKey(selected._key, next)} />\n ) : draft ? (\n <div className=\"grid gap-4\">\n <p className=\"xicms-ve__hint\">Klik op een blok om het te bewerken.</p>\n <PageSettings draft={draft} onChange={(part) => mutateDraft((d) => ({ ...d, ...part }), { coalesce: true })} />\n {!isDemoActive() && (\n <VersionsPanel\n siteId={siteId}\n pageId={draft.id}\n onRestore={(restored) => {\n mutateDraft((d) => ({ ...d, ...restored }));\n toast({ title: \"Versie hersteld naar werkversie\", description: \"Publiceer om hem live te zetten.\", tone: \"success\" });\n }}\n />\n )}\n </div>\n ) : (\n <p className=\"xicms-ve__hint\">Klik op een blok om het te bewerken.</p>\n )}\n </aside>\n </div>\n\n {/* Inline afbeelding vervangen: opent de mediabibliotheek; alleen de URL van het veld wordt gezet. */}\n <MediaPickerDialog\n siteId={siteId}\n open={!!imgPicker}\n onOpenChange={(v) => { if (!v) setImgPicker(null); }}\n onSelect={(m) => { if (imgPicker) commitInline(imgPicker.key, imgPicker.field, m.url); setImgPicker(null); }}\n />\n </div>\n );\n}\n\n/**\n * Pagina-instellingen (titel + SEO) direct in de visuele editor — de klant hoeft niet meer naar\n * /beheer voor een titel- of meta-omschrijving-wijziging. Slug/pad blijven bewust in /beheer\n * (die raken URL-structuur en zijn niet iets om per ongeluk inline aan te passen).\n */\nfunction PageSettings({\n draft,\n onChange,\n}: {\n draft: DraftPage;\n onChange: (part: Partial<DraftPage>) => void;\n}) {\n const seo = draft.seo ?? {};\n return (\n <details className=\"xicms-ve__section\" open>\n <summary className=\"text-sm font-semibold\">Pagina-instellingen</summary>\n <div className=\"mt-2 grid gap-3\">\n <Field label=\"Paginatitel\">\n <Input value={draft.title ?? \"\"} onChange={(e) => onChange({ title: e.target.value })} />\n </Field>\n <Field label=\"SEO-titel\" hint=\"Leeg = paginatitel\">\n <Input value={seo.title ?? \"\"} onChange={(e) => onChange({ seo: { ...seo, title: e.target.value } })} />\n </Field>\n <Field label=\"SEO-omschrijving\" hint=\"Wordt getoond in zoekresultaten\">\n <Textarea rows={3} value={seo.description ?? \"\"} onChange={(e) => onChange({ seo: { ...seo, description: e.target.value } })} />\n </Field>\n </div>\n </details>\n );\n}\n\n/** Versiegeschiedenis in de visuele editor: herstellen zet een snapshot terug naar de WERKVERSIE. */\nfunction VersionsPanel({\n siteId,\n pageId,\n onRestore,\n}: {\n siteId: string;\n pageId: string;\n onRestore: (restored: Partial<DraftPage>) => void;\n}) {\n const [versions, setVersions] = useState<{ id: string; createdAt?: unknown; kind?: string; label?: string }[]>([]);\n const { toast } = useToast();\n const confirm = useConfirm();\n useEffect(() => subscribeVersions(siteId, pageId, setVersions), [siteId, pageId]);\n if (versions.length === 0) return null;\n\n const restore = async (versionId: string) => {\n const ok = await confirm({\n title: \"Versie herstellen?\",\n description: \"De snapshot vervangt je huidige werkversie (ongedaan te maken met Ctrl+Z). De live site verandert pas bij Publiceren.\",\n confirmLabel: \"Herstellen\",\n });\n if (!ok) return;\n try {\n const restored = await restoreVersion(siteId, pageId, versionId);\n onRestore(restored);\n } catch (e) {\n toast({ title: \"Herstellen mislukt\", description: (e as Error).message, tone: \"error\" });\n }\n };\n\n return (\n <details className=\"xicms-ve__section\">\n <summary className=\"text-sm font-semibold\">Versiegeschiedenis</summary>\n <ul className=\"mt-2 grid gap-1 text-sm\">\n {versions.slice(0, 10).map((v) => (\n <li key={v.id} className=\"flex items-center justify-between gap-2\">\n <span>\n {v.label ?? (v.kind === \"publish\" ? \"Gepubliceerd\" : 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 restore(v.id)}>\n Herstellen\n </Button>\n </li>\n ))}\n </ul>\n </details>\n );\n}\n\nfunction formatVersionDate(createdAt: unknown): string {\n const date =\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 return date && !Number.isNaN(date.getTime()) ? date.toLocaleString(\"nl-NL\", { dateStyle: \"short\", timeStyle: \"short\" }) : \"\";\n}\n\nfunction SelectedEditor({\n block,\n siteId,\n registry,\n onChange,\n}: {\n block: Block;\n siteId: string;\n registry: ReturnType<typeof useRegistry>;\n onChange: (next: Record<string, unknown>) => void;\n}) {\n const def = registry.get(block._type);\n const Editor: ComponentType<BlockEditorProps> =\n (def?.Editor as ComponentType<BlockEditorProps> | undefined) ?? builtinBlockEditors[block._type] ?? GenericEditor;\n return (\n <div className=\"grid gap-3\">\n <h3 className=\"text-sm font-semibold\">{def?.label ?? block._type}</h3>\n {/* Maatwerk-Editors kunnen falen → vang per-blok op zodat het paneel niet leeg wordt. */}\n <BlockErrorBoundary mode=\"preview\" type={block._type} blockKey={block._key}>\n <Editor block={block} siteId={siteId} onChange={onChange} />\n </BlockErrorBoundary>\n <StyleEditor\n value={block._style as BlockStyle | undefined}\n onChange={(style) => onChange({ ...block, _style: style })}\n />\n </div>\n );\n}\n"],"mappings":"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","names":["t","inField"]}
@@ -8,6 +8,12 @@ export interface AgendaEvent {
8
8
  description?: string;
9
9
  url?: string;
10
10
  }
11
+ /**
12
+ * Parseert een event-datum als LOKALE tijd. Cruciaal voor datum-zonder-tijd: `new Date("2026-08-01")`
13
+ * is per spec UTC-middernacht, waardoor west-van-UTC-bezoekers "31 juli" te zien kregen (en de
14
+ * server/client bij hydration konden verschillen). "2026-08-01" is hier gewoon 1 augustus, overal.
15
+ */
16
+ export declare function parseEventDate(value: string): Date;
11
17
  /** Toont een datum leesbaar (nl-NL). Valt terug op de ruwe string bij ongeldige invoer. */
12
18
  export declare function formatEventDate(value: string): string;
13
19
  /**
@@ -3,9 +3,14 @@ import { AgendaBlockSchema } from "../../schema/blocks";
3
3
  import { defineBlock } from "../registry";
4
4
  import { Heading } from "../ui";
5
5
  import { icsDataUrl } from "./lib/ics";
6
+ function parseEventDate(value) {
7
+ const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value.trim());
8
+ if (m) return new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
9
+ return new Date(value);
10
+ }
6
11
  function formatEventDate(value) {
7
12
  if (!value) return "";
8
- const d = new Date(value);
13
+ const d = parseEventDate(value);
9
14
  if (Number.isNaN(d.getTime())) return value;
10
15
  const hasTime = value.includes("T");
11
16
  return d.toLocaleDateString("nl-NL", {
@@ -22,13 +27,13 @@ function selectAgendaEvents(events, opts = {}) {
22
27
  const today = /* @__PURE__ */ new Date();
23
28
  today.setHours(0, 0, 0, 0);
24
29
  list = list.filter((e) => {
25
- const d = new Date(e.end || e.start);
30
+ const d = parseEventDate(e.end || e.start);
26
31
  return Number.isNaN(d.getTime()) || d >= today;
27
32
  });
28
33
  }
29
34
  list = [...list].sort((a, b) => {
30
- const ta = new Date(a.start).getTime();
31
- const tb = new Date(b.start).getTime();
35
+ const ta = parseEventDate(a.start).getTime();
36
+ const tb = parseEventDate(b.start).getTime();
32
37
  if (Number.isNaN(ta) && Number.isNaN(tb)) return 0;
33
38
  if (Number.isNaN(ta)) return 1;
34
39
  if (Number.isNaN(tb)) return -1;
@@ -40,7 +45,9 @@ function eventJsonLd(events) {
40
45
  return {
41
46
  "@context": "https://schema.org",
42
47
  "@type": "ItemList",
43
- itemListElement: events.filter((e) => e.title && e.start).map((e, i) => ({
48
+ // Google vereist `location` voor Event-rich-results: events zonder locatie renderen wel op
49
+ // de pagina, maar blijven uit de JSON-LD (voorkomt gegarandeerde Search-Console-errors).
50
+ itemListElement: events.filter((e) => e.title && e.start && e.location).map((e, i) => ({
44
51
  "@type": "ListItem",
45
52
  position: i + 1,
46
53
  item: {
@@ -48,7 +55,7 @@ function eventJsonLd(events) {
48
55
  name: e.title,
49
56
  startDate: e.start,
50
57
  ...e.end ? { endDate: e.end } : {},
51
- ...e.location ? { location: { "@type": "Place", name: e.location } } : {},
58
+ location: { "@type": "Place", name: e.location },
52
59
  ...e.description ? { description: e.description } : {},
53
60
  ...e.url ? { url: e.url } : {}
54
61
  }
@@ -97,6 +104,7 @@ const agendaBlock = defineBlock({
97
104
  export {
98
105
  agendaBlock,
99
106
  formatEventDate,
107
+ parseEventDate,
100
108
  selectAgendaEvents
101
109
  };
102
110
  //# sourceMappingURL=Agenda.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/blocks/builtins/Agenda.tsx"],"sourcesContent":["import type { z } from \"zod\";\nimport { AgendaBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { Heading } from \"../ui\";\nimport { icsDataUrl } from \"./lib/ics\";\n\ntype Agenda = z.infer<typeof AgendaBlockSchema>;\n\n/** Eén event zoals zowel het Agenda-blok als de Agenda-widget het kennen. */\nexport interface AgendaEvent {\n title: string;\n start: string;\n end?: string;\n location?: string;\n description?: string;\n url?: string;\n}\n\n/** Toont een datum leesbaar (nl-NL). Valt terug op de ruwe string bij ongeldige invoer. */\nexport function formatEventDate(value: string): string {\n if (!value) return \"\";\n const d = new Date(value);\n if (Number.isNaN(d.getTime())) return value;\n const hasTime = value.includes(\"T\");\n return d.toLocaleDateString(\"nl-NL\", {\n weekday: \"short\",\n day: \"numeric\",\n month: \"long\",\n year: \"numeric\",\n ...(hasTime ? { hour: \"2-digit\", minute: \"2-digit\" } : {}),\n });\n}\n\n/**\n * Selecteert de te tonen events: filtert lege titels, optioneel verlopen events, sorteert\n * chronologisch (events zonder geldige datum achteraan, in invoervolgorde) en limiteert. Gedeeld\n * door het volwaardige Agenda-blok en de compacte Agenda-widget — zo \"haalt\" de widget alles\n * uit dezelfde logica als de volledige versie.\n */\nexport function selectAgendaEvents<T extends AgendaEvent>(\n events: T[] | undefined,\n opts: { hidePast?: boolean; limit?: number } = {},\n): T[] {\n let list = (events ?? []).filter((e) => e.title);\n if (opts.hidePast) {\n const today = new Date();\n today.setHours(0, 0, 0, 0);\n list = list.filter((e) => {\n const d = new Date(e.end || e.start);\n return Number.isNaN(d.getTime()) || d >= today;\n });\n }\n list = [...list].sort((a, b) => {\n const ta = new Date(a.start).getTime();\n const tb = new Date(b.start).getTime();\n if (Number.isNaN(ta) && Number.isNaN(tb)) return 0;\n if (Number.isNaN(ta)) return 1;\n if (Number.isNaN(tb)) return -1;\n return ta - tb;\n });\n return typeof opts.limit === \"number\" ? list.slice(0, opts.limit) : list;\n}\n\nfunction eventJsonLd(events: Agenda[\"events\"]): Record<string, unknown> {\n return {\n \"@context\": \"https://schema.org\",\n \"@type\": \"ItemList\",\n itemListElement: events\n .filter((e) => e.title && e.start)\n .map((e, i) => ({\n \"@type\": \"ListItem\",\n position: i + 1,\n item: {\n \"@type\": \"Event\",\n name: e.title,\n startDate: e.start,\n ...(e.end ? { endDate: e.end } : {}),\n ...(e.location ? { location: { \"@type\": \"Place\", name: e.location } } : {}),\n ...(e.description ? { description: e.description } : {}),\n ...(e.url ? { url: e.url } : {}),\n },\n })),\n };\n}\n\n/** Agenda/evenementen. Server-safe (geen client-state); `hidePast` filtert verlopen events. */\nfunction AgendaRender({ block }: BlockRenderProps<Agenda>) {\n const events = selectAgendaEvents(block.events, { hidePast: block.hidePast });\n if (events.length === 0) return null;\n\n return (\n <section className=\"xicms-agenda\">\n {/* Alleen de kop is inline bewerkbaar: de items worden gefilterd/gesorteerd, dus hun\n render-index wijst niet betrouwbaar naar block.events[i]. */}\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n <ul className=\"xicms-agenda__list\">\n {events.map((e, i) => (\n <li key={i} className=\"xicms-agenda__item\">\n <time className=\"xicms-agenda__date\" dateTime={e.start}>\n {formatEventDate(e.start)}\n </time>\n <div className=\"xicms-agenda__body\">\n <strong className=\"xicms-agenda__title\">\n {e.url ? <a href={e.url}>{e.title}</a> : e.title}\n </strong>\n {e.location && <span className=\"xicms-agenda__location\">{e.location}</span>}\n {e.description && <p className=\"xicms-agenda__desc\">{e.description}</p>}\n {(() => {\n const ics = icsDataUrl(e);\n return ics ? (\n <a className=\"xicms-agenda__ics\" href={ics} download={`${(e.title || \"evenement\").toLowerCase().replace(/\\s+/g, \"-\").slice(0, 40)}.ics`}>\n 📅 Zet in agenda\n </a>\n ) : null;\n })()}\n </div>\n </li>\n ))}\n </ul>\n {block.jsonLd && (\n <script\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{ __html: JSON.stringify(eventJsonLd(events)) }}\n />\n )}\n </section>\n );\n}\n\nexport const agendaBlock = defineBlock({\n type: \"agenda\",\n schema: AgendaBlockSchema,\n Render: AgendaRender,\n label: \"Agenda\",\n icon: \"📅\",\n defaults: () => ({\n heading: \"Agenda\",\n events: [{ title: \"Evenement\", start: \"\" }],\n hidePast: true,\n jsonLd: true,\n }),\n});\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/blocks/builtins/Agenda.tsx"],"sourcesContent":["import type { z } from \"zod\";\nimport { AgendaBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { Heading } from \"../ui\";\nimport { icsDataUrl } from \"./lib/ics\";\n\ntype Agenda = z.infer<typeof AgendaBlockSchema>;\n\n/** Eén event zoals zowel het Agenda-blok als de Agenda-widget het kennen. */\nexport interface AgendaEvent {\n title: string;\n start: string;\n end?: string;\n location?: string;\n description?: string;\n url?: string;\n}\n\n/**\n * Parseert een event-datum als LOKALE tijd. Cruciaal voor datum-zonder-tijd: `new Date(\"2026-08-01\")`\n * is per spec UTC-middernacht, waardoor west-van-UTC-bezoekers \"31 juli\" te zien kregen (en de\n * server/client bij hydration konden verschillen). \"2026-08-01\" is hier gewoon 1 augustus, overal.\n */\nexport function parseEventDate(value: string): Date {\n const m = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(value.trim());\n if (m) return new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]));\n return new Date(value);\n}\n\n/** Toont een datum leesbaar (nl-NL). Valt terug op de ruwe string bij ongeldige invoer. */\nexport function formatEventDate(value: string): string {\n if (!value) return \"\";\n const d = parseEventDate(value);\n if (Number.isNaN(d.getTime())) return value;\n const hasTime = value.includes(\"T\");\n return d.toLocaleDateString(\"nl-NL\", {\n weekday: \"short\",\n day: \"numeric\",\n month: \"long\",\n year: \"numeric\",\n ...(hasTime ? { hour: \"2-digit\", minute: \"2-digit\" } : {}),\n });\n}\n\n/**\n * Selecteert de te tonen events: filtert lege titels, optioneel verlopen events, sorteert\n * chronologisch (events zonder geldige datum achteraan, in invoervolgorde) en limiteert. Gedeeld\n * door het volwaardige Agenda-blok en de compacte Agenda-widget — zo \"haalt\" de widget alles\n * uit dezelfde logica als de volledige versie.\n */\nexport function selectAgendaEvents<T extends AgendaEvent>(\n events: T[] | undefined,\n opts: { hidePast?: boolean; limit?: number } = {},\n): T[] {\n let list = (events ?? []).filter((e) => e.title);\n if (opts.hidePast) {\n const today = new Date();\n today.setHours(0, 0, 0, 0);\n list = list.filter((e) => {\n const d = parseEventDate(e.end || e.start);\n return Number.isNaN(d.getTime()) || d >= today;\n });\n }\n list = [...list].sort((a, b) => {\n const ta = parseEventDate(a.start).getTime();\n const tb = parseEventDate(b.start).getTime();\n if (Number.isNaN(ta) && Number.isNaN(tb)) return 0;\n if (Number.isNaN(ta)) return 1;\n if (Number.isNaN(tb)) return -1;\n return ta - tb;\n });\n return typeof opts.limit === \"number\" ? list.slice(0, opts.limit) : list;\n}\n\nfunction eventJsonLd(events: Agenda[\"events\"]): Record<string, unknown> {\n return {\n \"@context\": \"https://schema.org\",\n \"@type\": \"ItemList\",\n // Google vereist `location` voor Event-rich-results: events zonder locatie renderen wel op\n // de pagina, maar blijven uit de JSON-LD (voorkomt gegarandeerde Search-Console-errors).\n itemListElement: events\n .filter((e) => e.title && e.start && e.location)\n .map((e, i) => ({\n \"@type\": \"ListItem\",\n position: i + 1,\n item: {\n \"@type\": \"Event\",\n name: e.title,\n startDate: e.start,\n ...(e.end ? { endDate: e.end } : {}),\n location: { \"@type\": \"Place\", name: e.location },\n ...(e.description ? { description: e.description } : {}),\n ...(e.url ? { url: e.url } : {}),\n },\n })),\n };\n}\n\n/** Agenda/evenementen. Server-safe (geen client-state); `hidePast` filtert verlopen events. */\nfunction AgendaRender({ block }: BlockRenderProps<Agenda>) {\n const events = selectAgendaEvents(block.events, { hidePast: block.hidePast });\n if (events.length === 0) return null;\n\n return (\n <section className=\"xicms-agenda\">\n {/* Alleen de kop is inline bewerkbaar: de items worden gefilterd/gesorteerd, dus hun\n render-index wijst niet betrouwbaar naar block.events[i]. */}\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n <ul className=\"xicms-agenda__list\">\n {events.map((e, i) => (\n <li key={i} className=\"xicms-agenda__item\">\n <time className=\"xicms-agenda__date\" dateTime={e.start}>\n {formatEventDate(e.start)}\n </time>\n <div className=\"xicms-agenda__body\">\n <strong className=\"xicms-agenda__title\">\n {e.url ? <a href={e.url}>{e.title}</a> : e.title}\n </strong>\n {e.location && <span className=\"xicms-agenda__location\">{e.location}</span>}\n {e.description && <p className=\"xicms-agenda__desc\">{e.description}</p>}\n {(() => {\n const ics = icsDataUrl(e);\n return ics ? (\n <a className=\"xicms-agenda__ics\" href={ics} download={`${(e.title || \"evenement\").toLowerCase().replace(/\\s+/g, \"-\").slice(0, 40)}.ics`}>\n 📅 Zet in agenda\n </a>\n ) : null;\n })()}\n </div>\n </li>\n ))}\n </ul>\n {block.jsonLd && (\n <script\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{ __html: JSON.stringify(eventJsonLd(events)) }}\n />\n )}\n </section>\n );\n}\n\nexport const agendaBlock = defineBlock({\n type: \"agenda\",\n schema: AgendaBlockSchema,\n Render: AgendaRender,\n label: \"Agenda\",\n icon: \"📅\",\n defaults: () => ({\n heading: \"Agenda\",\n events: [{ title: \"Evenement\", start: \"\" }],\n hidePast: true,\n jsonLd: true,\n }),\n});\n"],"mappings":"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","names":[]}
@@ -52,7 +52,11 @@ function BreadcrumbsRender({ block, mode }) {
52
52
  "script",
53
53
  {
54
54
  type: "application/ld+json",
55
- dangerouslySetInnerHTML: { __html: JSON.stringify(breadcrumbsJsonLd(crumbs)) }
55
+ dangerouslySetInnerHTML: {
56
+ __html: JSON.stringify(
57
+ breadcrumbsJsonLd(crumbs.map((c) => ({ ...c, href: `${window.location.origin}${c.href}` })))
58
+ )
59
+ }
56
60
  }
57
61
  )
58
62
  ] });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/blocks/builtins/Breadcrumbs.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport type { z } from \"zod\";\nimport { BreadcrumbsBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { useLinkComponent } from \"@astryxdesign/core/Link\";\n\ntype Breadcrumbs = z.infer<typeof BreadcrumbsBlockSchema>;\n\nexport interface Crumb {\n label: string;\n href: string;\n}\n\n/** Kruimels uit een pad: \"/diensten/tuinaanleg\" → Home / Diensten / Tuinaanleg. Puur en getest. */\nexport function crumbsFromPath(path: string, homeLabel = \"Home\"): Crumb[] {\n const segments = path.split(\"/\").filter(Boolean);\n const crumbs: Crumb[] = [{ label: homeLabel, href: \"/\" }];\n let href = \"\";\n for (const seg of segments) {\n href += `/${seg}`;\n // decodeURIComponent gooit op misvormde percent-encoding (\"%E0%A4%A\") — dan het rauwe segment.\n let decoded = seg;\n try {\n decoded = decodeURIComponent(seg);\n } catch {\n /* rauw segment als label */\n }\n const label = decoded.replace(/[-_]+/g, \" \").replace(/^./, (c) => c.toUpperCase());\n crumbs.push({ label, href });\n }\n return crumbs;\n}\n\n/** BreadcrumbList-JSON-LD. Puur en getest. */\nexport function breadcrumbsJsonLd(crumbs: Crumb[]): Record<string, unknown> {\n return {\n \"@context\": \"https://schema.org\",\n \"@type\": \"BreadcrumbList\",\n itemListElement: crumbs.map((c, i) => ({\n \"@type\": \"ListItem\",\n position: i + 1,\n name: c.label,\n item: c.href,\n })),\n };\n}\n\n/**\n * Kruimelpad, afgeleid van de URL (client, ná mount — hydration-veilig). Op de homepage\n * (alleen \"Home\") wordt niets getoond.\n */\nfunction BreadcrumbsRender({ block, mode }: BlockRenderProps<Breadcrumbs>) {\n const LinkC = useLinkComponent();\n const [path, setPath] = useState<string | null>(null);\n useEffect(() => {\n setPath(window.location.pathname);\n }, []);\n\n if (mode === \"preview\" && (path === null || path === \"/\")) {\n return <nav className=\"xicms-breadcrumbs\" aria-label=\"Kruimelpad\">{block.homeLabel} / Voorbeeldpagina</nav>;\n }\n if (path === null || path === \"/\") return null;\n const crumbs = crumbsFromPath(path, block.homeLabel);\n\n return (\n <nav className=\"xicms-breadcrumbs\" aria-label=\"Kruimelpad\">\n <ol className=\"xicms-breadcrumbs__list\">\n {crumbs.map((c, i) => (\n <li key={c.href} className=\"xicms-breadcrumbs__item\">\n {i < crumbs.length - 1 ? (\n <LinkC href={c.href}>{c.label}</LinkC>\n ) : (\n <span aria-current=\"page\">{c.label}</span>\n )}\n </li>\n ))}\n </ol>\n {block.jsonLd && (\n <script\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbsJsonLd(crumbs)) }}\n />\n )}\n </nav>\n );\n}\n\nexport const breadcrumbsBlock = defineBlock({\n type: \"breadcrumbs\",\n schema: BreadcrumbsBlockSchema,\n Render: BreadcrumbsRender,\n label: \"Kruimelpad\",\n icon: \"🧭\",\n defaults: () => ({ homeLabel: \"Home\", jsonLd: true }),\n});\n"],"mappings":";AA6DW,SAWG,KAXH;AA3DX,SAAS,WAAW,gBAAgB;AAEpC,SAAS,8BAA8B;AACvC,SAAS,mBAA0C;AACnD,SAAS,wBAAwB;AAU1B,SAAS,eAAe,MAAc,YAAY,QAAiB;AACxE,QAAM,WAAW,KAAK,MAAM,GAAG,EAAE,OAAO,OAAO;AAC/C,QAAM,SAAkB,CAAC,EAAE,OAAO,WAAW,MAAM,IAAI,CAAC;AACxD,MAAI,OAAO;AACX,aAAW,OAAO,UAAU;AAC1B,YAAQ,IAAI,GAAG;AAEf,QAAI,UAAU;AACd,QAAI;AACF,gBAAU,mBAAmB,GAAG;AAAA,IAClC,QAAQ;AAAA,IAER;AACA,UAAM,QAAQ,QAAQ,QAAQ,UAAU,GAAG,EAAE,QAAQ,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC;AACjF,WAAO,KAAK,EAAE,OAAO,KAAK,CAAC;AAAA,EAC7B;AACA,SAAO;AACT;AAGO,SAAS,kBAAkB,QAA0C;AAC1E,SAAO;AAAA,IACL,YAAY;AAAA,IACZ,SAAS;AAAA,IACT,iBAAiB,OAAO,IAAI,CAAC,GAAG,OAAO;AAAA,MACrC,SAAS;AAAA,MACT,UAAU,IAAI;AAAA,MACd,MAAM,EAAE;AAAA,MACR,MAAM,EAAE;AAAA,IACV,EAAE;AAAA,EACJ;AACF;AAMA,SAAS,kBAAkB,EAAE,OAAO,KAAK,GAAkC;AACzE,QAAM,QAAQ,iBAAiB;AAC/B,QAAM,CAAC,MAAM,OAAO,IAAI,SAAwB,IAAI;AACpD,YAAU,MAAM;AACd,YAAQ,OAAO,SAAS,QAAQ;AAAA,EAClC,GAAG,CAAC,CAAC;AAEL,MAAI,SAAS,cAAc,SAAS,QAAQ,SAAS,MAAM;AACzD,WAAO,qBAAC,SAAI,WAAU,qBAAoB,cAAW,cAAc;AAAA,YAAM;AAAA,MAAU;AAAA,OAAkB;AAAA,EACvG;AACA,MAAI,SAAS,QAAQ,SAAS,IAAK,QAAO;AAC1C,QAAM,SAAS,eAAe,MAAM,MAAM,SAAS;AAEnD,SACE,qBAAC,SAAI,WAAU,qBAAoB,cAAW,cAC5C;AAAA,wBAAC,QAAG,WAAU,2BACX,iBAAO,IAAI,CAAC,GAAG,MACd,oBAAC,QAAgB,WAAU,2BACxB,cAAI,OAAO,SAAS,IACnB,oBAAC,SAAM,MAAM,EAAE,MAAO,YAAE,OAAM,IAE9B,oBAAC,UAAK,gBAAa,QAAQ,YAAE,OAAM,KAJ9B,EAAE,IAMX,CACD,GACH;AAAA,IACC,MAAM,UACL;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QAEL,yBAAyB,EAAE,QAAQ,KAAK,UAAU,kBAAkB,MAAM,CAAC,EAAE;AAAA;AAAA,IAC/E;AAAA,KAEJ;AAEJ;AAEO,MAAM,mBAAmB,YAAY;AAAA,EAC1C,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,OAAO,EAAE,WAAW,QAAQ,QAAQ,KAAK;AACrD,CAAC;","names":[]}
1
+ {"version":3,"sources":["../../../src/blocks/builtins/Breadcrumbs.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport type { z } from \"zod\";\nimport { BreadcrumbsBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { useLinkComponent } from \"@astryxdesign/core/Link\";\n\ntype Breadcrumbs = z.infer<typeof BreadcrumbsBlockSchema>;\n\nexport interface Crumb {\n label: string;\n href: string;\n}\n\n/** Kruimels uit een pad: \"/diensten/tuinaanleg\" → Home / Diensten / Tuinaanleg. Puur en getest. */\nexport function crumbsFromPath(path: string, homeLabel = \"Home\"): Crumb[] {\n const segments = path.split(\"/\").filter(Boolean);\n const crumbs: Crumb[] = [{ label: homeLabel, href: \"/\" }];\n let href = \"\";\n for (const seg of segments) {\n href += `/${seg}`;\n // decodeURIComponent gooit op misvormde percent-encoding (\"%E0%A4%A\") — dan het rauwe segment.\n let decoded = seg;\n try {\n decoded = decodeURIComponent(seg);\n } catch {\n /* rauw segment als label */\n }\n const label = decoded.replace(/[-_]+/g, \" \").replace(/^./, (c) => c.toUpperCase());\n crumbs.push({ label, href });\n }\n return crumbs;\n}\n\n/** BreadcrumbList-JSON-LD. Puur en getest. */\nexport function breadcrumbsJsonLd(crumbs: Crumb[]): Record<string, unknown> {\n return {\n \"@context\": \"https://schema.org\",\n \"@type\": \"BreadcrumbList\",\n itemListElement: crumbs.map((c, i) => ({\n \"@type\": \"ListItem\",\n position: i + 1,\n name: c.label,\n item: c.href,\n })),\n };\n}\n\n/**\n * Kruimelpad, afgeleid van de URL (client, ná mount — hydration-veilig). Op de homepage\n * (alleen \"Home\") wordt niets getoond.\n */\nfunction BreadcrumbsRender({ block, mode }: BlockRenderProps<Breadcrumbs>) {\n const LinkC = useLinkComponent();\n const [path, setPath] = useState<string | null>(null);\n useEffect(() => {\n setPath(window.location.pathname);\n }, []);\n\n if (mode === \"preview\" && (path === null || path === \"/\")) {\n return <nav className=\"xicms-breadcrumbs\" aria-label=\"Kruimelpad\">{block.homeLabel} / Voorbeeldpagina</nav>;\n }\n if (path === null || path === \"/\") return null;\n const crumbs = crumbsFromPath(path, block.homeLabel);\n\n return (\n <nav className=\"xicms-breadcrumbs\" aria-label=\"Kruimelpad\">\n <ol className=\"xicms-breadcrumbs__list\">\n {crumbs.map((c, i) => (\n <li key={c.href} className=\"xicms-breadcrumbs__item\">\n {i < crumbs.length - 1 ? (\n <LinkC href={c.href}>{c.label}</LinkC>\n ) : (\n <span aria-current=\"page\">{c.label}</span>\n )}\n </li>\n ))}\n </ol>\n {block.jsonLd && (\n <script\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger\n // Absolute URL's: Google wil volledige item-URL's in BreadcrumbList (dit rendert\n // client-side ná mount, dus window.location.origin is beschikbaar).\n dangerouslySetInnerHTML={{\n __html: JSON.stringify(\n breadcrumbsJsonLd(crumbs.map((c) => ({ ...c, href: `${window.location.origin}${c.href}` }))),\n ),\n }}\n />\n )}\n </nav>\n );\n}\n\nexport const breadcrumbsBlock = defineBlock({\n type: \"breadcrumbs\",\n schema: BreadcrumbsBlockSchema,\n Render: BreadcrumbsRender,\n label: \"Kruimelpad\",\n icon: \"🧭\",\n defaults: () => ({ homeLabel: \"Home\", jsonLd: true }),\n});\n"],"mappings":";AA6DW,SAWG,KAXH;AA3DX,SAAS,WAAW,gBAAgB;AAEpC,SAAS,8BAA8B;AACvC,SAAS,mBAA0C;AACnD,SAAS,wBAAwB;AAU1B,SAAS,eAAe,MAAc,YAAY,QAAiB;AACxE,QAAM,WAAW,KAAK,MAAM,GAAG,EAAE,OAAO,OAAO;AAC/C,QAAM,SAAkB,CAAC,EAAE,OAAO,WAAW,MAAM,IAAI,CAAC;AACxD,MAAI,OAAO;AACX,aAAW,OAAO,UAAU;AAC1B,YAAQ,IAAI,GAAG;AAEf,QAAI,UAAU;AACd,QAAI;AACF,gBAAU,mBAAmB,GAAG;AAAA,IAClC,QAAQ;AAAA,IAER;AACA,UAAM,QAAQ,QAAQ,QAAQ,UAAU,GAAG,EAAE,QAAQ,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC;AACjF,WAAO,KAAK,EAAE,OAAO,KAAK,CAAC;AAAA,EAC7B;AACA,SAAO;AACT;AAGO,SAAS,kBAAkB,QAA0C;AAC1E,SAAO;AAAA,IACL,YAAY;AAAA,IACZ,SAAS;AAAA,IACT,iBAAiB,OAAO,IAAI,CAAC,GAAG,OAAO;AAAA,MACrC,SAAS;AAAA,MACT,UAAU,IAAI;AAAA,MACd,MAAM,EAAE;AAAA,MACR,MAAM,EAAE;AAAA,IACV,EAAE;AAAA,EACJ;AACF;AAMA,SAAS,kBAAkB,EAAE,OAAO,KAAK,GAAkC;AACzE,QAAM,QAAQ,iBAAiB;AAC/B,QAAM,CAAC,MAAM,OAAO,IAAI,SAAwB,IAAI;AACpD,YAAU,MAAM;AACd,YAAQ,OAAO,SAAS,QAAQ;AAAA,EAClC,GAAG,CAAC,CAAC;AAEL,MAAI,SAAS,cAAc,SAAS,QAAQ,SAAS,MAAM;AACzD,WAAO,qBAAC,SAAI,WAAU,qBAAoB,cAAW,cAAc;AAAA,YAAM;AAAA,MAAU;AAAA,OAAkB;AAAA,EACvG;AACA,MAAI,SAAS,QAAQ,SAAS,IAAK,QAAO;AAC1C,QAAM,SAAS,eAAe,MAAM,MAAM,SAAS;AAEnD,SACE,qBAAC,SAAI,WAAU,qBAAoB,cAAW,cAC5C;AAAA,wBAAC,QAAG,WAAU,2BACX,iBAAO,IAAI,CAAC,GAAG,MACd,oBAAC,QAAgB,WAAU,2BACxB,cAAI,OAAO,SAAS,IACnB,oBAAC,SAAM,MAAM,EAAE,MAAO,YAAE,OAAM,IAE9B,oBAAC,UAAK,gBAAa,QAAQ,YAAE,OAAM,KAJ9B,EAAE,IAMX,CACD,GACH;AAAA,IACC,MAAM,UACL;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QAIL,yBAAyB;AAAA,UACvB,QAAQ,KAAK;AAAA,YACX,kBAAkB,OAAO,IAAI,CAAC,OAAO,EAAE,GAAG,GAAG,MAAM,GAAG,OAAO,SAAS,MAAM,GAAG,EAAE,IAAI,GAAG,EAAE,CAAC;AAAA,UAC7F;AAAA,QACF;AAAA;AAAA,IACF;AAAA,KAEJ;AAEJ;AAEO,MAAM,mBAAmB,YAAY;AAAA,EAC1C,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,OAAO,EAAE,WAAW,QAAQ,QAAQ,KAAK;AACrD,CAAC;","names":[]}
@@ -7,14 +7,15 @@ import { useLinkComponent } from "@astryxdesign/core/Link";
7
7
  import { Img } from "../../media/ImageContext";
8
8
  function CardsRender({ block }) {
9
9
  const LinkC = useLinkComponent();
10
- const items = (block.items ?? []).filter((c) => c.title || c.text || c.imageUrl);
10
+ const items = (block.items ?? []).map((c, i) => ({ ...c, _i: i })).filter((c) => c.title || c.text || c.imageUrl);
11
11
  if (items.length === 0 && !block.heading) return null;
12
12
  return /* @__PURE__ */ jsxs(VStack, { gap: 4, as: "section", className: "xicms-cards", children: [
13
13
  (block.heading || block.intro) && /* @__PURE__ */ jsxs(VStack, { gap: 1, hAlign: "center", style: { textAlign: "center" }, children: [
14
14
  block.heading && /* @__PURE__ */ jsx(Heading, { level: 2, "data-xicms-edit": "heading", children: block.heading }),
15
15
  block.intro && /* @__PURE__ */ jsx(Text, { type: "large", color: "secondary", children: block.intro })
16
16
  ] }),
17
- /* @__PURE__ */ jsx(Grid, { columns: { minWidth: 240, max: block.columns }, gap: 4, children: items.map((c, i) => {
17
+ /* @__PURE__ */ jsx(Grid, { columns: { minWidth: 240, max: block.columns }, gap: 4, children: items.map((c) => {
18
+ const i = c._i;
18
19
  const cover = block.imageStyle === "cover" && Boolean(c.imageUrl);
19
20
  const body = /* @__PURE__ */ jsxs(VStack, { gap: 2, style: cover ? { padding: "1.1rem 1.25rem 1.25rem" } : void 0, children: [
20
21
  c.title && /* @__PURE__ */ jsx(Heading, { level: 3, className: "xicms-cards__title", "data-xicms-edit": `items.${i}.title`, children: c.title }),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/blocks/builtins/Cards.tsx"],"sourcesContent":["\"use client\";\n\nimport type { z } from \"zod\";\nimport { CardsBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { VStack, Grid, Card, Heading, Text } from \"../ui\";\nimport { useLinkComponent } from \"@astryxdesign/core/Link\";\nimport { Img } from \"../../media/ImageContext\";\n\ntype Cards = z.infer<typeof CardsBlockSchema>;\n\n/** Diensten/uitgelichte kaarten in een responsive grid (Astryx). */\nfunction CardsRender({ block }: BlockRenderProps<Cards>) {\n const LinkC = useLinkComponent();\n const items = (block.items ?? []).filter((c) => c.title || c.text || c.imageUrl);\n if (items.length === 0 && !block.heading) return null;\n return (\n <VStack gap={4} as=\"section\" className=\"xicms-cards\">\n {(block.heading || block.intro) && (\n <VStack gap={1} hAlign=\"center\" style={{ textAlign: \"center\" }}>\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n {block.intro && <Text type=\"large\" color=\"secondary\">{block.intro}</Text>}\n </VStack>\n )}\n <Grid columns={{ minWidth: 240, max: block.columns }} gap={4}>\n {items.map((c, i) => {\n const cover = block.imageStyle === \"cover\" && Boolean(c.imageUrl);\n const body = (\n <VStack gap={2} style={cover ? { padding: \"1.1rem 1.25rem 1.25rem\" } : undefined}>\n {c.title && <Heading level={3} className=\"xicms-cards__title\" data-xicms-edit={`items.${i}.title`}>{c.title}</Heading>}\n {c.text && <Text color=\"secondary\" className=\"xicms-cards__text\" data-xicms-edit={`items.${i}.text`}>{c.text}</Text>}\n {c.href && c.linkLabel && (\n <Text type=\"label\" style={{ color: \"var(--color-accent)\" }}>{c.linkLabel} →</Text>\n )}\n </VStack>\n );\n const inner = cover ? (\n <VStack gap={0} style={{ height: \"100%\" }}>\n {/* Beeld edge-to-edge bovenaan (postkaart-stijl); tekst hieronder gepad. */}\n <Img src={c.imageUrl as string} alt={c.title ?? \"\"} width={600} height={360}\n sizes=\"(max-width: 720px) 100vw, 33vw\" editField={`items.${i}.imageUrl`}\n style={{ width: \"100%\", aspectRatio: \"16 / 10\", objectFit: \"cover\", display: \"block\" }} />\n {body}\n </VStack>\n ) : (\n <VStack gap={2}>\n {c.imageUrl ? (\n <Img src={c.imageUrl} alt={c.title ?? \"\"} width={480} height={300}\n sizes=\"(max-width: 720px) 100vw, 33vw\" editField={`items.${i}.imageUrl`}\n style={{ width: \"100%\", height: 160, objectFit: \"cover\", borderRadius: \"var(--radius-element)\" }} />\n ) : c.icon ? (\n <div aria-hidden=\"true\" style={{ fontSize: \"2rem\", lineHeight: 1 }}>{c.icon}</div>\n ) : null}\n {c.title && <Heading level={3} className=\"xicms-cards__title\" data-xicms-edit={`items.${i}.title`}>{c.title}</Heading>}\n {c.text && <Text color=\"secondary\" className=\"xicms-cards__text\" data-xicms-edit={`items.${i}.text`}>{c.text}</Text>}\n {c.href && c.linkLabel && (\n <Text type=\"label\" style={{ color: \"var(--color-accent)\" }}>{c.linkLabel} →</Text>\n )}\n </VStack>\n );\n const card = (\n <Card\n padding={cover ? 0 : 4}\n height=\"100%\"\n className={`xicms-cards__card${c.featured ? \" xicms-cards__card--featured\" : \"\"}`}\n style={cover ? { overflow: \"hidden\" } : undefined}\n >\n {inner}\n </Card>\n );\n return c.href ? (\n <LinkC key={i} href={c.href} style={{ textDecoration: \"none\", color: \"inherit\", display: \"block\" }}>\n {card}\n </LinkC>\n ) : (\n <div key={i}>{card}</div>\n );\n })}\n </Grid>\n </VStack>\n );\n}\n\nexport const cardsBlock = defineBlock({\n type: \"cards\",\n schema: CardsBlockSchema,\n Render: CardsRender,\n label: \"Diensten / kaarten\",\n icon: \"🧩\",\n defaults: () => ({\n heading: \"Onze diensten\",\n columns: 3,\n items: [\n { icon: \"⭐\", title: \"Dienst één\", text: \"Korte omschrijving van wat je biedt.\" },\n { icon: \"⚡\", title: \"Dienst twee\", text: \"Korte omschrijving van wat je biedt.\" },\n { icon: \"💬\", title: \"Dienst drie\", text: \"Korte omschrijving van wat je biedt.\" },\n ],\n }),\n});\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/blocks/builtins/Cards.tsx"],"sourcesContent":["\"use client\";\n\nimport type { z } from \"zod\";\nimport { CardsBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { VStack, Grid, Card, Heading, Text } from \"../ui\";\nimport { useLinkComponent } from \"@astryxdesign/core/Link\";\nimport { Img } from \"../../media/ImageContext\";\n\ntype Cards = z.infer<typeof CardsBlockSchema>;\n\n/** Diensten/uitgelichte kaarten in een responsive grid (Astryx). */\nfunction CardsRender({ block }: BlockRenderProps<Cards>) {\n const LinkC = useLinkComponent();\n // _i = index in de OORSPRONKELIJKE array — inline-edit-paden moeten naar de ongefilterde\n // items wijzen, anders schrijft bewerken naar het verkeerde (weggefilterde) item.\n const items = (block.items ?? []).map((c, i) => ({ ...c, _i: i })).filter((c) => c.title || c.text || c.imageUrl);\n if (items.length === 0 && !block.heading) return null;\n return (\n <VStack gap={4} as=\"section\" className=\"xicms-cards\">\n {(block.heading || block.intro) && (\n <VStack gap={1} hAlign=\"center\" style={{ textAlign: \"center\" }}>\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n {block.intro && <Text type=\"large\" color=\"secondary\">{block.intro}</Text>}\n </VStack>\n )}\n <Grid columns={{ minWidth: 240, max: block.columns }} gap={4}>\n {items.map((c) => {\n const i = c._i;\n const cover = block.imageStyle === \"cover\" && Boolean(c.imageUrl);\n const body = (\n <VStack gap={2} style={cover ? { padding: \"1.1rem 1.25rem 1.25rem\" } : undefined}>\n {c.title && <Heading level={3} className=\"xicms-cards__title\" data-xicms-edit={`items.${i}.title`}>{c.title}</Heading>}\n {c.text && <Text color=\"secondary\" className=\"xicms-cards__text\" data-xicms-edit={`items.${i}.text`}>{c.text}</Text>}\n {c.href && c.linkLabel && (\n <Text type=\"label\" style={{ color: \"var(--color-accent)\" }}>{c.linkLabel} →</Text>\n )}\n </VStack>\n );\n const inner = cover ? (\n <VStack gap={0} style={{ height: \"100%\" }}>\n {/* Beeld edge-to-edge bovenaan (postkaart-stijl); tekst hieronder gepad. */}\n <Img src={c.imageUrl as string} alt={c.title ?? \"\"} width={600} height={360}\n sizes=\"(max-width: 720px) 100vw, 33vw\" editField={`items.${i}.imageUrl`}\n style={{ width: \"100%\", aspectRatio: \"16 / 10\", objectFit: \"cover\", display: \"block\" }} />\n {body}\n </VStack>\n ) : (\n <VStack gap={2}>\n {c.imageUrl ? (\n <Img src={c.imageUrl} alt={c.title ?? \"\"} width={480} height={300}\n sizes=\"(max-width: 720px) 100vw, 33vw\" editField={`items.${i}.imageUrl`}\n style={{ width: \"100%\", height: 160, objectFit: \"cover\", borderRadius: \"var(--radius-element)\" }} />\n ) : c.icon ? (\n <div aria-hidden=\"true\" style={{ fontSize: \"2rem\", lineHeight: 1 }}>{c.icon}</div>\n ) : null}\n {c.title && <Heading level={3} className=\"xicms-cards__title\" data-xicms-edit={`items.${i}.title`}>{c.title}</Heading>}\n {c.text && <Text color=\"secondary\" className=\"xicms-cards__text\" data-xicms-edit={`items.${i}.text`}>{c.text}</Text>}\n {c.href && c.linkLabel && (\n <Text type=\"label\" style={{ color: \"var(--color-accent)\" }}>{c.linkLabel} →</Text>\n )}\n </VStack>\n );\n const card = (\n <Card\n padding={cover ? 0 : 4}\n height=\"100%\"\n className={`xicms-cards__card${c.featured ? \" xicms-cards__card--featured\" : \"\"}`}\n style={cover ? { overflow: \"hidden\" } : undefined}\n >\n {inner}\n </Card>\n );\n return c.href ? (\n <LinkC key={i} href={c.href} style={{ textDecoration: \"none\", color: \"inherit\", display: \"block\" }}>\n {card}\n </LinkC>\n ) : (\n <div key={i}>{card}</div>\n );\n })}\n </Grid>\n </VStack>\n );\n}\n\nexport const cardsBlock = defineBlock({\n type: \"cards\",\n schema: CardsBlockSchema,\n Render: CardsRender,\n label: \"Diensten / kaarten\",\n icon: \"🧩\",\n defaults: () => ({\n heading: \"Onze diensten\",\n columns: 3,\n items: [\n { icon: \"⭐\", title: \"Dienst één\", text: \"Korte omschrijving van wat je biedt.\" },\n { icon: \"⚡\", title: \"Dienst twee\", text: \"Korte omschrijving van wat je biedt.\" },\n { icon: \"💬\", title: \"Dienst drie\", text: \"Korte omschrijving van wat je biedt.\" },\n ],\n }),\n});\n"],"mappings":"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","names":[]}
@@ -9,7 +9,8 @@ function contactJsonLd(b) {
9
9
  // Gedeeld relatief @id: contact-, openingstijden- en layout-JSON-LD beschrijven hetzelfde
10
10
  // bedrijf; zonder @id ziet Google drie conflicterende LocalBusiness-entiteiten per pagina.
11
11
  "@id": "#business",
12
- ...b.heading ? { name: b.heading } : {},
12
+ // Bewust géén `name: heading` — de blokkop is meestal "Contact" en zou als bedrijfsnaam in de
13
+ // samengevoegde #business-entiteit belanden; de naam komt uit de layout-brede businessJsonLd.
13
14
  ...b.address ? { address: b.address } : {},
14
15
  ...b.phone ? { telephone: b.phone } : {},
15
16
  ...b.email ? { email: b.email } : {},
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/blocks/builtins/Contact.tsx"],"sourcesContent":["import type { z } from \"zod\";\nimport { ContactBlockSchema, SocialBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { Heading } from \"../ui\";\n\n/* ── Contact ─────────────────────────────────────────────────────────────── */\ntype Contact = z.infer<typeof ContactBlockSchema>;\nexport function contactJsonLd(b: Contact): Record<string, unknown> {\n return {\n \"@context\": \"https://schema.org\",\n \"@type\": \"LocalBusiness\",\n // Gedeeld relatief @id: contact-, openingstijden- en layout-JSON-LD beschrijven hetzelfde\n // bedrijf; zonder @id ziet Google drie conflicterende LocalBusiness-entiteiten per pagina.\n \"@id\": \"#business\",\n ...(b.heading ? { name: b.heading } : {}),\n ...(b.address ? { address: b.address } : {}),\n ...(b.phone ? { telephone: b.phone } : {}),\n ...(b.email ? { email: b.email } : {}),\n ...(b.geo ? { geo: { \"@type\": \"GeoCoordinates\", latitude: b.geo.lat, longitude: b.geo.lng } } : {}),\n };\n}\nfunction ContactRender({ block }: BlockRenderProps<Contact>) {\n return (\n <section className=\"xicms-contact\">\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n <dl className=\"xicms-contact__list\">\n {block.address && (\n <div className=\"xicms-contact__row\">\n <dt>Adres</dt>\n <dd data-xicms-edit=\"address\">{block.address}</dd>\n </div>\n )}\n {block.phone && (\n <div className=\"xicms-contact__row\">\n <dt>Telefoon</dt>\n <dd><a href={`tel:${block.phone.replace(/\\s+/g, \"\")}`}>{block.phone}</a></dd>\n </div>\n )}\n {block.email && (\n <div className=\"xicms-contact__row\">\n <dt>E-mail</dt>\n <dd><a href={`mailto:${block.email}`}>{block.email}</a></dd>\n </div>\n )}\n {block.hours && (\n <div className=\"xicms-contact__row\">\n <dt>Openingstijden</dt>\n <dd data-xicms-edit=\"hours\">{block.hours}</dd>\n </div>\n )}\n </dl>\n {block.jsonLd && (\n <script\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{ __html: JSON.stringify(contactJsonLd(block)) }}\n />\n )}\n </section>\n );\n}\nexport const contactBlock = defineBlock({\n type: \"contact\",\n schema: ContactBlockSchema,\n Render: ContactRender,\n label: \"Contactgegevens\",\n icon: \"📇\",\n defaults: () => ({ heading: \"Contact\", jsonLd: true }),\n});\n\n/* ── Social ──────────────────────────────────────────────────────────────── */\ntype Social = z.infer<typeof SocialBlockSchema>;\n\n/**\n * Minimalistische, monochrome merk-iconen (currentColor) — geen icon-dependency nodig.\n * Onbekende platformen vallen terug op de tekstweergave, dus niets breekt.\n */\nfunction SocialIcon({ platform }: { platform: string }) {\n const p = platform.toLowerCase();\n const common = { width: 22, height: 22, viewBox: \"0 0 24 24\", \"aria-hidden\": true } as const;\n if (p.includes(\"instagram\")) {\n return (\n <svg {...common} fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\">\n <rect x=\"4\" y=\"4\" width=\"16\" height=\"16\" rx=\"4.5\" />\n <circle cx=\"12\" cy=\"12\" r=\"3.6\" />\n <circle cx=\"16.7\" cy=\"7.3\" r=\"1.1\" fill=\"currentColor\" stroke=\"none\" />\n </svg>\n );\n }\n if (p.includes(\"facebook\")) {\n return (\n <svg {...common} fill=\"currentColor\">\n <path d=\"M13.5 20v-6.2h2.1l.4-2.6h-2.5V9.6c0-.8.3-1.3 1.3-1.3h1.3V6c-.3 0-1-.1-1.9-.1-2 0-3.3 1.2-3.3 3.4v1.9H8.7v2.6h2.2V20h2.6z\" />\n </svg>\n );\n }\n if (p.includes(\"linkedin\")) {\n return (\n <svg {...common} fill=\"currentColor\">\n <circle cx=\"6\" cy=\"6.4\" r=\"1.8\" />\n <rect x=\"4.6\" y=\"9.6\" width=\"2.8\" height=\"9.9\" />\n <path d=\"M10.4 9.6h2.7v1.4c.5-.9 1.6-1.7 3.1-1.7 2.4 0 3.7 1.5 3.7 4.3v5.9h-2.8v-5.4c0-1.5-.6-2.3-1.7-2.3-1.2 0-2.2.8-2.2 2.4v5.3h-2.8V9.6z\" />\n </svg>\n );\n }\n if (p.includes(\"twitter\") || p === \"x\" || p.startsWith(\"x \") || p.includes(\"(x)\")) {\n return (\n <svg {...common} fill=\"currentColor\">\n <path d=\"M4.5 4.5h3.6l4.1 5.6 4.7-5.6h2.6l-6.1 7.2 6.4 8.4h-3.6l-4.4-6-5 6H4.2l6.5-7.7-6.2-7.9z\" />\n </svg>\n );\n }\n if (p.includes(\"youtube\")) {\n return (\n <svg {...common} fill=\"currentColor\">\n <rect x=\"3.2\" y=\"6.2\" width=\"17.6\" height=\"11.6\" rx=\"3.2\" />\n <path d=\"M10.4 9.4v5.2l4.8-2.6z\" fill=\"var(--xicms-surface, #fff)\" />\n </svg>\n );\n }\n if (p.includes(\"whatsapp\")) {\n return (\n <svg {...common} fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\">\n <path d=\"M12 4.2a7.8 7.8 0 0 0-6.7 11.8L4.4 19.6l3.7-.9A7.8 7.8 0 1 0 12 4.2z\" />\n <path d=\"M9.3 8.9c.7 2.4 2.6 4.4 5 5l1-1.2 2 1.2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n );\n }\n if (p.includes(\"tiktok\")) {\n return (\n <svg {...common} fill=\"currentColor\">\n <path d=\"M14.5 4h2.6c.2 1.7 1.3 3 3 3.3v2.6a6.6 6.6 0 0 1-3.2-1v5.7a5.4 5.4 0 1 1-5.4-5.4c.2 0 .5 0 .7.1v2.8a2.6 2.6 0 1 0 2.3 2.6V4z\" />\n </svg>\n );\n }\n return null;\n}\n\nfunction SocialRender({ block }: BlockRenderProps<Social>) {\n const items = (block.items ?? []).filter((i) => i.href && i.platform);\n if (items.length === 0) return null;\n return (\n <section className=\"xicms-social\">\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n <ul className=\"xicms-social__list\">\n {items.map((it, i) => {\n // Als functie aangeroepen (geen hooks) zodat `null` echt null is — een JSX-element\n // zou altijd truthy zijn en de tekst-fallback nooit tonen.\n const icon = SocialIcon({ platform: it.platform });\n return (\n <li key={i}>\n <a href={it.href} rel=\"noopener noreferrer\" target=\"_blank\" aria-label={it.platform} title={it.platform} className={icon ? \"xicms-social__iconlink\" : undefined}>\n {icon ?? it.platform}\n </a>\n </li>\n );\n })}\n </ul>\n </section>\n );\n}\nexport const socialBlock = defineBlock({\n type: \"social\",\n schema: SocialBlockSchema,\n Render: SocialRender,\n label: \"Social-links\",\n icon: \"🔗\",\n defaults: () => ({ items: [{ platform: \"Instagram\", href: \"\" }] }),\n});\n"],"mappings":"AAwBwB,cAGd,YAHc;AAvBxB,SAAS,oBAAoB,yBAAyB;AACtD,SAAS,mBAA0C;AACnD,SAAS,eAAe;AAIjB,SAAS,cAAc,GAAqC;AACjE,SAAO;AAAA,IACL,YAAY;AAAA,IACZ,SAAS;AAAA;AAAA;AAAA,IAGT,OAAO;AAAA,IACP,GAAI,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,IAAI,CAAC;AAAA,IACvC,GAAI,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC1C,GAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,IAAI,CAAC;AAAA,IACxC,GAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAAA,IACpC,GAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,kBAAkB,UAAU,EAAE,IAAI,KAAK,WAAW,EAAE,IAAI,IAAI,EAAE,IAAI,CAAC;AAAA,EACnG;AACF;AACA,SAAS,cAAc,EAAE,MAAM,GAA8B;AAC3D,SACE,qBAAC,aAAQ,WAAU,iBAChB;AAAA,UAAM,WAAW,oBAAC,WAAQ,OAAO,GAAG,mBAAgB,WAAW,gBAAM,SAAQ;AAAA,IAC9E,qBAAC,QAAG,WAAU,uBACX;AAAA,YAAM,WACL,qBAAC,SAAI,WAAU,sBACb;AAAA,4BAAC,QAAG,mBAAK;AAAA,QACT,oBAAC,QAAG,mBAAgB,WAAW,gBAAM,SAAQ;AAAA,SAC/C;AAAA,MAED,MAAM,SACL,qBAAC,SAAI,WAAU,sBACb;AAAA,4BAAC,QAAG,sBAAQ;AAAA,QACZ,oBAAC,QAAG,8BAAC,OAAE,MAAM,OAAO,MAAM,MAAM,QAAQ,QAAQ,EAAE,CAAC,IAAK,gBAAM,OAAM,GAAI;AAAA,SAC1E;AAAA,MAED,MAAM,SACL,qBAAC,SAAI,WAAU,sBACb;AAAA,4BAAC,QAAG,oBAAM;AAAA,QACV,oBAAC,QAAG,8BAAC,OAAE,MAAM,UAAU,MAAM,KAAK,IAAK,gBAAM,OAAM,GAAI;AAAA,SACzD;AAAA,MAED,MAAM,SACL,qBAAC,SAAI,WAAU,sBACb;AAAA,4BAAC,QAAG,4BAAc;AAAA,QAClB,oBAAC,QAAG,mBAAgB,SAAS,gBAAM,OAAM;AAAA,SAC3C;AAAA,OAEJ;AAAA,IACC,MAAM,UACL;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QAEL,yBAAyB,EAAE,QAAQ,KAAK,UAAU,cAAc,KAAK,CAAC,EAAE;AAAA;AAAA,IAC1E;AAAA,KAEJ;AAEJ;AACO,MAAM,eAAe,YAAY;AAAA,EACtC,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,OAAO,EAAE,SAAS,WAAW,QAAQ,KAAK;AACtD,CAAC;AASD,SAAS,WAAW,EAAE,SAAS,GAAyB;AACtD,QAAM,IAAI,SAAS,YAAY;AAC/B,QAAM,SAAS,EAAE,OAAO,IAAI,QAAQ,IAAI,SAAS,aAAa,eAAe,KAAK;AAClF,MAAI,EAAE,SAAS,WAAW,GAAG;AAC3B,WACE,qBAAC,SAAK,GAAG,QAAQ,MAAK,QAAO,QAAO,gBAAe,aAAY,OAC7D;AAAA,0BAAC,UAAK,GAAE,KAAI,GAAE,KAAI,OAAM,MAAK,QAAO,MAAK,IAAG,OAAM;AAAA,MAClD,oBAAC,YAAO,IAAG,MAAK,IAAG,MAAK,GAAE,OAAM;AAAA,MAChC,oBAAC,YAAO,IAAG,QAAO,IAAG,OAAM,GAAE,OAAM,MAAK,gBAAe,QAAO,QAAO;AAAA,OACvE;AAAA,EAEJ;AACA,MAAI,EAAE,SAAS,UAAU,GAAG;AAC1B,WACE,oBAAC,SAAK,GAAG,QAAQ,MAAK,gBACpB,8BAAC,UAAK,GAAE,4HAA2H,GACrI;AAAA,EAEJ;AACA,MAAI,EAAE,SAAS,UAAU,GAAG;AAC1B,WACE,qBAAC,SAAK,GAAG,QAAQ,MAAK,gBACpB;AAAA,0BAAC,YAAO,IAAG,KAAI,IAAG,OAAM,GAAE,OAAM;AAAA,MAChC,oBAAC,UAAK,GAAE,OAAM,GAAE,OAAM,OAAM,OAAM,QAAO,OAAM;AAAA,MAC/C,oBAAC,UAAK,GAAE,sIAAqI;AAAA,OAC/I;AAAA,EAEJ;AACA,MAAI,EAAE,SAAS,SAAS,KAAK,MAAM,OAAO,EAAE,WAAW,IAAI,KAAK,EAAE,SAAS,KAAK,GAAG;AACjF,WACE,oBAAC,SAAK,GAAG,QAAQ,MAAK,gBACpB,8BAAC,UAAK,GAAE,0FAAyF,GACnG;AAAA,EAEJ;AACA,MAAI,EAAE,SAAS,SAAS,GAAG;AACzB,WACE,qBAAC,SAAK,GAAG,QAAQ,MAAK,gBACpB;AAAA,0BAAC,UAAK,GAAE,OAAM,GAAE,OAAM,OAAM,QAAO,QAAO,QAAO,IAAG,OAAM;AAAA,MAC1D,oBAAC,UAAK,GAAE,0BAAyB,MAAK,8BAA6B;AAAA,OACrE;AAAA,EAEJ;AACA,MAAI,EAAE,SAAS,UAAU,GAAG;AAC1B,WACE,qBAAC,SAAK,GAAG,QAAQ,MAAK,QAAO,QAAO,gBAAe,aAAY,OAC7D;AAAA,0BAAC,UAAK,GAAE,wEAAuE;AAAA,MAC/E,oBAAC,UAAK,GAAE,2CAA0C,eAAc,SAAQ,gBAAe,SAAQ;AAAA,OACjG;AAAA,EAEJ;AACA,MAAI,EAAE,SAAS,QAAQ,GAAG;AACxB,WACE,oBAAC,SAAK,GAAG,QAAQ,MAAK,gBACpB,8BAAC,UAAK,GAAE,gIAA+H,GACzI;AAAA,EAEJ;AACA,SAAO;AACT;AAEA,SAAS,aAAa,EAAE,MAAM,GAA6B;AACzD,QAAM,SAAS,MAAM,SAAS,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ;AACpE,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,SACE,qBAAC,aAAQ,WAAU,gBAChB;AAAA,UAAM,WAAW,oBAAC,WAAQ,OAAO,GAAG,mBAAgB,WAAW,gBAAM,SAAQ;AAAA,IAC9E,oBAAC,QAAG,WAAU,sBACX,gBAAM,IAAI,CAAC,IAAI,MAAM;AAGpB,YAAM,OAAO,WAAW,EAAE,UAAU,GAAG,SAAS,CAAC;AACjD,aACE,oBAAC,QACC,8BAAC,OAAE,MAAM,GAAG,MAAM,KAAI,uBAAsB,QAAO,UAAS,cAAY,GAAG,UAAU,OAAO,GAAG,UAAU,WAAW,OAAO,2BAA2B,QACnJ,kBAAQ,GAAG,UACd,KAHO,CAIT;AAAA,IAEJ,CAAC,GACH;AAAA,KACF;AAEJ;AACO,MAAM,cAAc,YAAY;AAAA,EACrC,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,OAAO,EAAE,OAAO,CAAC,EAAE,UAAU,aAAa,MAAM,GAAG,CAAC,EAAE;AAClE,CAAC;","names":[]}
1
+ {"version":3,"sources":["../../../src/blocks/builtins/Contact.tsx"],"sourcesContent":["import type { z } from \"zod\";\nimport { ContactBlockSchema, SocialBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { Heading } from \"../ui\";\n\n/* ── Contact ─────────────────────────────────────────────────────────────── */\ntype Contact = z.infer<typeof ContactBlockSchema>;\nexport function contactJsonLd(b: Contact): Record<string, unknown> {\n return {\n \"@context\": \"https://schema.org\",\n \"@type\": \"LocalBusiness\",\n // Gedeeld relatief @id: contact-, openingstijden- en layout-JSON-LD beschrijven hetzelfde\n // bedrijf; zonder @id ziet Google drie conflicterende LocalBusiness-entiteiten per pagina.\n \"@id\": \"#business\",\n // Bewust géén `name: heading` — de blokkop is meestal \"Contact\" en zou als bedrijfsnaam in de\n // samengevoegde #business-entiteit belanden; de naam komt uit de layout-brede businessJsonLd.\n ...(b.address ? { address: b.address } : {}),\n ...(b.phone ? { telephone: b.phone } : {}),\n ...(b.email ? { email: b.email } : {}),\n ...(b.geo ? { geo: { \"@type\": \"GeoCoordinates\", latitude: b.geo.lat, longitude: b.geo.lng } } : {}),\n };\n}\nfunction ContactRender({ block }: BlockRenderProps<Contact>) {\n return (\n <section className=\"xicms-contact\">\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n <dl className=\"xicms-contact__list\">\n {block.address && (\n <div className=\"xicms-contact__row\">\n <dt>Adres</dt>\n <dd data-xicms-edit=\"address\">{block.address}</dd>\n </div>\n )}\n {block.phone && (\n <div className=\"xicms-contact__row\">\n <dt>Telefoon</dt>\n <dd><a href={`tel:${block.phone.replace(/\\s+/g, \"\")}`}>{block.phone}</a></dd>\n </div>\n )}\n {block.email && (\n <div className=\"xicms-contact__row\">\n <dt>E-mail</dt>\n <dd><a href={`mailto:${block.email}`}>{block.email}</a></dd>\n </div>\n )}\n {block.hours && (\n <div className=\"xicms-contact__row\">\n <dt>Openingstijden</dt>\n <dd data-xicms-edit=\"hours\">{block.hours}</dd>\n </div>\n )}\n </dl>\n {block.jsonLd && (\n <script\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{ __html: JSON.stringify(contactJsonLd(block)) }}\n />\n )}\n </section>\n );\n}\nexport const contactBlock = defineBlock({\n type: \"contact\",\n schema: ContactBlockSchema,\n Render: ContactRender,\n label: \"Contactgegevens\",\n icon: \"📇\",\n defaults: () => ({ heading: \"Contact\", jsonLd: true }),\n});\n\n/* ── Social ──────────────────────────────────────────────────────────────── */\ntype Social = z.infer<typeof SocialBlockSchema>;\n\n/**\n * Minimalistische, monochrome merk-iconen (currentColor) — geen icon-dependency nodig.\n * Onbekende platformen vallen terug op de tekstweergave, dus niets breekt.\n */\nfunction SocialIcon({ platform }: { platform: string }) {\n const p = platform.toLowerCase();\n const common = { width: 22, height: 22, viewBox: \"0 0 24 24\", \"aria-hidden\": true } as const;\n if (p.includes(\"instagram\")) {\n return (\n <svg {...common} fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\">\n <rect x=\"4\" y=\"4\" width=\"16\" height=\"16\" rx=\"4.5\" />\n <circle cx=\"12\" cy=\"12\" r=\"3.6\" />\n <circle cx=\"16.7\" cy=\"7.3\" r=\"1.1\" fill=\"currentColor\" stroke=\"none\" />\n </svg>\n );\n }\n if (p.includes(\"facebook\")) {\n return (\n <svg {...common} fill=\"currentColor\">\n <path d=\"M13.5 20v-6.2h2.1l.4-2.6h-2.5V9.6c0-.8.3-1.3 1.3-1.3h1.3V6c-.3 0-1-.1-1.9-.1-2 0-3.3 1.2-3.3 3.4v1.9H8.7v2.6h2.2V20h2.6z\" />\n </svg>\n );\n }\n if (p.includes(\"linkedin\")) {\n return (\n <svg {...common} fill=\"currentColor\">\n <circle cx=\"6\" cy=\"6.4\" r=\"1.8\" />\n <rect x=\"4.6\" y=\"9.6\" width=\"2.8\" height=\"9.9\" />\n <path d=\"M10.4 9.6h2.7v1.4c.5-.9 1.6-1.7 3.1-1.7 2.4 0 3.7 1.5 3.7 4.3v5.9h-2.8v-5.4c0-1.5-.6-2.3-1.7-2.3-1.2 0-2.2.8-2.2 2.4v5.3h-2.8V9.6z\" />\n </svg>\n );\n }\n if (p.includes(\"twitter\") || p === \"x\" || p.startsWith(\"x \") || p.includes(\"(x)\")) {\n return (\n <svg {...common} fill=\"currentColor\">\n <path d=\"M4.5 4.5h3.6l4.1 5.6 4.7-5.6h2.6l-6.1 7.2 6.4 8.4h-3.6l-4.4-6-5 6H4.2l6.5-7.7-6.2-7.9z\" />\n </svg>\n );\n }\n if (p.includes(\"youtube\")) {\n return (\n <svg {...common} fill=\"currentColor\">\n <rect x=\"3.2\" y=\"6.2\" width=\"17.6\" height=\"11.6\" rx=\"3.2\" />\n <path d=\"M10.4 9.4v5.2l4.8-2.6z\" fill=\"var(--xicms-surface, #fff)\" />\n </svg>\n );\n }\n if (p.includes(\"whatsapp\")) {\n return (\n <svg {...common} fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\">\n <path d=\"M12 4.2a7.8 7.8 0 0 0-6.7 11.8L4.4 19.6l3.7-.9A7.8 7.8 0 1 0 12 4.2z\" />\n <path d=\"M9.3 8.9c.7 2.4 2.6 4.4 5 5l1-1.2 2 1.2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n );\n }\n if (p.includes(\"tiktok\")) {\n return (\n <svg {...common} fill=\"currentColor\">\n <path d=\"M14.5 4h2.6c.2 1.7 1.3 3 3 3.3v2.6a6.6 6.6 0 0 1-3.2-1v5.7a5.4 5.4 0 1 1-5.4-5.4c.2 0 .5 0 .7.1v2.8a2.6 2.6 0 1 0 2.3 2.6V4z\" />\n </svg>\n );\n }\n return null;\n}\n\nfunction SocialRender({ block }: BlockRenderProps<Social>) {\n const items = (block.items ?? []).filter((i) => i.href && i.platform);\n if (items.length === 0) return null;\n return (\n <section className=\"xicms-social\">\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n <ul className=\"xicms-social__list\">\n {items.map((it, i) => {\n // Als functie aangeroepen (geen hooks) zodat `null` echt null is — een JSX-element\n // zou altijd truthy zijn en de tekst-fallback nooit tonen.\n const icon = SocialIcon({ platform: it.platform });\n return (\n <li key={i}>\n <a href={it.href} rel=\"noopener noreferrer\" target=\"_blank\" aria-label={it.platform} title={it.platform} className={icon ? \"xicms-social__iconlink\" : undefined}>\n {icon ?? it.platform}\n </a>\n </li>\n );\n })}\n </ul>\n </section>\n );\n}\nexport const socialBlock = defineBlock({\n type: \"social\",\n schema: SocialBlockSchema,\n Render: SocialRender,\n label: \"Social-links\",\n icon: \"🔗\",\n defaults: () => ({ items: [{ platform: \"Instagram\", href: \"\" }] }),\n});\n"],"mappings":"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","names":[]}