@xiwebdesign/xicms-core 7.9.0 → 7.9.1

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.
@@ -389,12 +389,16 @@ function VisualEditorInner({ onClose }) {
389
389
  key,
390
390
  type,
391
391
  top: Math.max(r.top + 8, veilig + 8),
392
- left: Math.max(r.right - 62, 8),
392
+ // Vanaf de RECHTERkant verankerd: het palet klapt uit en moet die ruimte naar LINKS
393
+ // pakken — een full-width blok eindigt op de viewportrand, rechts is daar per definitie
394
+ // geen plek. Met een left-anker schoof het uitgeklapte deel het scherm af en leek de
395
+ // hele uitbreiding gewoon niet te bestaan.
396
+ right: Math.max(window.innerWidth - r.right + 8, 8),
393
397
  label: registryRef.current.get(type)?.label ?? type
394
398
  });
395
399
  }
396
400
  setBlokknoppen((vorige) => {
397
- if (vorige.length === gevonden.length && vorige.every((v, i) => v.key === gevonden[i].key && v.top === gevonden[i].top && v.left === gevonden[i].left)) {
401
+ if (vorige.length === gevonden.length && vorige.every((v, i) => v.key === gevonden[i].key && v.top === gevonden[i].top && v.right === gevonden[i].right)) {
398
402
  return vorige;
399
403
  }
400
404
  return gevonden;
@@ -427,9 +431,9 @@ function VisualEditorInner({ onClose }) {
427
431
  const id = doel.dataset.xicmsProduct;
428
432
  if (!id) return;
429
433
  const r = doel.getBoundingClientRect();
430
- setProductKnop(
431
- (v) => v && v.id === id && v.top === Math.max(r.top + 8, 44) && v.left === Math.max(r.right - 62, 8) ? v : { id, top: Math.max(r.top + 8, 44), left: Math.max(r.right - 62, 8) }
432
- );
434
+ const top = Math.max(r.top + 8, 44);
435
+ const right = Math.max(window.innerWidth - r.right + 8, 8);
436
+ setProductKnop((v) => v && v.id === id && v.top === top && v.right === right ? v : { id, top, right });
433
437
  };
434
438
  document.addEventListener("mouseover", onOver, true);
435
439
  return () => document.removeEventListener("mouseover", onOver, true);
@@ -793,15 +797,21 @@ function VisualEditorInner({ onClose }) {
793
797
  toast({ title: "Los eerst het conflict op", description: "Kies 'Nieuwste versie laden' of 'Mijn versie behouden' in de gele balk.", tone: "error" });
794
798
  return;
795
799
  }
796
- const ok = await confirm({ title: "Pagina publiceren?", description: `De live site wordt bijgewerkt (${d.path}).`, confirmLabel: "Publiceren" });
800
+ const demo = isDemoActive();
801
+ const ok = await confirm({
802
+ title: "Pagina publiceren?",
803
+ description: demo ? "In deze demo blijft alles in je eigen browser \u2014 er gaat niets echt online." : `De live site wordt bijgewerkt (${d.path}).`,
804
+ confirmLabel: "Publiceren"
805
+ });
797
806
  if (!ok) return;
798
807
  setPublishing(true);
799
808
  try {
800
809
  await saveDraft(siteId, d.id, draftPayload(d));
801
810
  await publishPage(siteId, d.id);
811
+ setLiveBlocks(structuredClone(d.blocks));
802
812
  toast({
803
813
  title: "Gepubliceerd",
804
- description: "Zelf zie je de nieuwe tekst meteen. Voor overige bezoekers duurt het nog ongeveer 15 minuten.",
814
+ description: demo ? "In deze demo blijft de wijziging in je browser staan." : "Zelf zie je de nieuwe tekst meteen. Voor overige bezoekers duurt het nog ongeveer 15 minuten.",
805
815
  tone: "success"
806
816
  });
807
817
  } catch (e) {
@@ -834,9 +844,11 @@ function VisualEditorInner({ onClose }) {
834
844
  actief: "bewerken",
835
845
  voorSwitch: (m) => {
836
846
  if (m === "live" && nietLiveRef.current) {
847
+ const n = aantalWijzigingen(wijzigingen);
837
848
  toast({
838
849
  title: "Nog niet gepubliceerd",
839
- description: `${aantalWijzigingen(wijzigingen)} blijft bewaard als werkversie. Je ziet ze weer zodra je terug naar Bewerken gaat.`
850
+ // Enkelvoud/meervoud netjes: "1 wijziging blijft", "14 wijzigingen blijven".
851
+ description: `${n} ${n.startsWith("1 ") ? "blijft" : "blijven"} bewaard als werkversie. Je ziet ze terug zodra je weer naar Bewerken gaat.`
840
852
  });
841
853
  }
842
854
  }
@@ -990,7 +1002,7 @@ function VisualEditorInner({ onClose }) {
990
1002
  blokknoppen.map((b) => {
991
1003
  const i = blocks.findIndex((blok) => blok._key === b.key);
992
1004
  const isSymbool = b.type === "symbolRef";
993
- return /* @__PURE__ */ jsxs("div", { className: "xicms-ve-blokknoppen", style: { top: b.top, left: b.left }, children: [
1005
+ return /* @__PURE__ */ jsxs("div", { className: `xicms-ve-blokknoppen${b.key === selectedKey ? " is-open" : ""}`, style: { top: b.top, right: b.right }, children: [
994
1006
  /* @__PURE__ */ jsx(
995
1007
  "button",
996
1008
  {
@@ -1029,7 +1041,7 @@ function VisualEditorInner({ onClose }) {
1029
1041
  ] })
1030
1042
  }
1031
1043
  ),
1032
- i >= 0 && /* @__PURE__ */ jsxs("span", { className: "xicms-ve-blokknoppen__meer", children: [
1044
+ i >= 0 && /* @__PURE__ */ jsx("span", { className: "xicms-ve-blokknoppen__meer", children: /* @__PURE__ */ jsxs("span", { className: "xicms-ve-blokknoppen__meer-in", children: [
1033
1045
  /* @__PURE__ */ jsx(
1034
1046
  "button",
1035
1047
  {
@@ -1098,7 +1110,7 @@ function VisualEditorInner({ onClose }) {
1098
1110
  children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", width: "13", height: "13", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M3 6h18M8 6V4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2M6 6l1 14a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2l1-14" }) })
1099
1111
  }
1100
1112
  )
1101
- ] })
1113
+ ] }) })
1102
1114
  ] }, b.key);
1103
1115
  }),
1104
1116
  spots.map((s, i) => /* @__PURE__ */ jsx(
@@ -1124,7 +1136,7 @@ function VisualEditorInner({ onClose }) {
1124
1136
  "div",
1125
1137
  {
1126
1138
  className: "xicms-ve-blokknoppen is-product",
1127
- style: { top: productKnop.top, left: productKnop.left },
1139
+ style: { top: productKnop.top, right: productKnop.right },
1128
1140
  children: [
1129
1141
  /* @__PURE__ */ jsx(
1130
1142
  "button",
@@ -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 { introspectToFields } from \"../lib/zodFields\";\nimport { BlockErrorBoundary } from \"../../blocks/BlockErrorBoundary\";\nimport { InlineImageEditProvider } from \"../../media/ImageContext\";\nimport { MediaPickerDialog } from \"../media/MediaPicker\";\nimport { EmptyState } from \"@astryxdesign/core/EmptyState\";\nimport { useFocusTrap, useScrollLock } from \"@astryxdesign/core/hooks\";\nimport { useNestedDialogOpen } from \"../lib/useNestedDialog\";\nimport { useThemeVars } from \"../../XiCMSProvider\";\nimport { buildBlockAddOptions } from \"../../blocks/registry\";\nimport { safeParse } from \"zod/v4/core\";\nimport DOMPurify from \"dompurify\";\nimport { createPage, getDraftByPath, getPublishedPage, saveDraft, publishPage, subscribeDraft, subscribeVersions, restoreVersion, type DraftPage } from \"../lib/pagesClient\";\nimport { isDemoActive } from \"../../demo/demoStore\";\nimport { setProductField } from \"../shop/productClient\";\nimport { ProductPaneel } from \"../shop/ProductPaneel\";\nimport { builtinBlockEditors, GenericEditor, type BlockEditorProps } from \"../blockEditors\";\nimport { StyleEditor } from \"../blockEditors/StyleEditor\";\nimport { siteLocales, parseLocaleSlugs, localizeBlocks } from \"../../i18n/overlay\";\nimport { useEditor } from \"../../editor/EditorProvider\";\nimport { AdminModes } from \"../../components/EditBar\";\nimport { useBalkOffset } from \"../../components/barOffset\";\nimport { aantalWijzigingen, bepaalWijzigingen, GEEN_WIJZIGINGEN, type Wijzigingen } from \"./wijzigingen\";\nimport { TranslationPanel } from \"./TranslationPanel\";\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 * Inline bewerken is sinds 5.0 volledig generiek: élk blok markeert zijn tekstvelden met\n * `data-xicms-edit=\"veldpad\"` (de builtin-Renders doen dat), plus de rich-text-haak\n * `.xicms-richtext`. De oude hardgecodeerde per-type selector-map (`INLINE_FIELDS`) is vervallen.\n * In development waarschuwt `warnInlineCoverage` wanneer een tekstveld uit het schema géén\n * DOM-haak heeft — zo blijven Renders eerlijk t.o.v. hun schema.\n */\nconst GLOBAL_INLINE = [\n // `field: \"html\"` is de AANNAME, geen wet: een element mag met `data-xicms-edit` zeggen welk veld\n // het werkelijk toont, en dat wint (zie de bind hieronder). Zonder die voorrang schreef élk\n // `.xicms-richtext`-element naar een veld dat letterlijk \"html\" heet — waar de FAQ zijn antwoord\n // in `items[].aHtml` bewaart en het accordeon in `items[].bodyHtml`. Inline bewerken van een\n // FAQ-antwoord landde daardoor op een niet-bestaand veld: de wijziging verdween stil en het blok\n // hield een zwerfveld over.\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/**\n * Dev-vangnet (5.0): meld tekstvelden zonder inline-haak — één keer per bloktype. Vooral voor\n * MAATWERKblokken in een klantrepo; de ingebouwde blokken worden statisch en volledig gedekt door\n * `npm run check:inline`.\n *\n * Een veld telt alleen als het zijn waarde ook echt als tekst op het scherm zet. Zonder die eis\n * riep dit elk blok na over lege velden (een niet-ingevulde `eyebrow` rendert niet, dus valt er\n * niets aan te klikken) en over configuratie die toevallig een tekstveld is (`overlayColor`).\n * Zulke loze meldingen leren je de waarschuwing wegkijken, en dan mist hij zijn doel.\n */\nconst inlineCoverageWarned = new Set<string>();\nfunction warnInlineCoverage(\n type: string,\n def: { schema: Parameters<typeof introspectToFields>[0]; fields?: Parameters<typeof introspectToFields>[1] } | undefined,\n host: HTMLElement,\n values: Record<string, unknown>,\n) {\n if (process.env.NODE_ENV === \"production\" || !def || inlineCoverageWarned.has(type)) return;\n inlineCoverageWarned.add(type);\n const zichtbaar = host.textContent ?? \"\";\n const missing = introspectToFields(def.schema, def.fields)\n .filter((f) => f.type === \"text\" || f.type === \"textarea\")\n .filter((f) => {\n const waarde = getPath(values, f.key);\n return typeof waarde === \"string\" && waarde.trim().length > 1 && zichtbaar.includes(waarde.trim());\n })\n .map((f) => f.key)\n .filter((key) => !host.querySelector(`[data-xicms-edit=\"${key}\"], [data-xicms-edit$=\".${key}\"]`));\n if (missing.length > 0) {\n console.warn(\n `[xicms] blok \"${type}\": tekstveld(en) zonder inline-haak: ${missing.join(\", \")} — voeg data-xicms-edit=\"<veld>\" toe aan de Render zodat ze direct op de pagina bewerkbaar zijn.`,\n );\n }\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 config = useSiteConfig();\n const { siteId, name } = config;\n const registry = useRegistry();\n const { toast } = useToast();\n const confirm = useConfirm();\n\n // Meertalig (5.0): een taalprefix in de URL (/de/over-ons) hoort niet bij het canonieke\n // pagina-pad — de editor opent dan de pagina zélf, met de betreffende taal actief.\n const localeCfg = useMemo(() => siteLocales(config), [config]);\n const { path, urlLocale } = useMemo(() => {\n const raw = typeof window !== \"undefined\" ? window.location.pathname || \"/\" : \"/\";\n const { locale, slugs } = parseLocaleSlugs(raw.split(\"/\").filter(Boolean), localeCfg);\n const p = \"/\" + slugs.join(\"/\");\n return { path: p === \"/\" ? \"/\" : p.replace(/\\/$/, \"\"), urlLocale: locale };\n }, [localeCfg]);\n const [locale, setLocale] = useState(urlLocale);\n const isTranslating = locale !== localeCfg.defaultLocale;\n const [draft, setDraft] = useState<DraftPage | null>(null);\n // Het zijpaneel stond altijd open en nam 360px van het canvas. Sinds élk tekstveld inline\n // bewerkbaar is, is dat paneel niet meer de hoofdweg maar het uitzonderingsgeval: de instellingen\n // die je niet op de pagina ziet (een link achter een knop, een kleur, een limiet). Het opent\n // daarom op verzoek — via het tandwiel bij het blok — en het canvas krijgt de volle breedte.\n const [panelOpen, setPanelOpen] = useState(false);\n const [status, setStatus] = useState<Status>(\"loading\");\n const [selectedKey, setSelectedKey] = useState<string | null>(null);\n /** Staat het zijpaneel op een PRODUCT in plaats van op een blok? Dan wint het product. */\n const [selectedProduct, setSelectedProduct] = 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 * De blokken zoals ze NU LIVE staan — de maatstaf voor \"wat heb ik veranderd\".\n *\n * `undefined` = nog niet gelezen (dan zeggen we niets, want een lege pagina rood kleuren omdat\n * de lezing nog loopt is erger dan even niets tonen). `null` = gelezen, maar deze pagina is nog\n * nooit gepubliceerd.\n */\n const [liveBlocks, setLiveBlocks] = useState<Block[] | null | undefined>(undefined);\n useBalkOffset(true);\n\n // ── Modale discipline (WAI-ARIA dialog pattern) ───────────────────────────\n // De shell draagt `role=\"dialog\" aria-modal=\"true\"` en belooft daarmee aan hulpsoftware dat\n // alles erbuiten onbereikbaar is. Zonder trap klopte die belofte niet: met Tab liep je zó de\n // onderliggende pagina in, terwijl de schermlezer meldde dat die er niet was.\n //\n // Geen `onEscape` meegeven: Escape wordt hieronder al afgehandeld met logica die de trap niet\n // kent (niet sluiten terwijl je in een invoerveld staat of er een dialoog openstaat).\n // Het canvas is een VOORBEELD van de live pagina en moet dus dezelfde merk-tokens dragen.\n // De editor rendert buiten `.xicms-root`, dus die erven we hier expliciet via context.\n const { vars: themeVars, darkAuto } = useThemeVars();\n const nestedDialogOpen = useNestedDialogOpen();\n const { containerRef, focusFirst } = useFocusTrap<HTMLDivElement>({ isActive: !nestedDialogOpen });\n useScrollLock(true);\n\n // Focus de eerste bediening bij openen, en geef hem bij sluiten terug aan het element dat de\n // editor opende (\"Pagina bewerken\"). Zonder dat teruggeven valt de focus terug naar <body> en\n // begint een toetsenbordgebruiker weer bovenaan de pagina.\n useEffect(() => {\n const opener = document.activeElement as HTMLElement | null;\n focusFirst();\n return () => opener?.focus?.();\n }, [focusFirst]);\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 // Dirty HIER markeren (synchroon met de mutatie), niet pas in het debounced autosave-effect:\n // een inline edit (blur) direct gevolgd door \"Sluiten\" in dezelfde gesture zou anders\n // flushSave() zien met dirtyRef=false → laatste wijziging verloren.\n dirtyRef.current = true;\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 dirtyRef.current = true; // undo is óók een wijziging die opgeslagen moet worden\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 dirtyRef.current = true; // redo is óók een wijziging die opgeslagen moet worden\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 // Alleen meesturen wanneer aanwezig: `undefined` weigert de Firestore-client.\n ...(d.translations ? { translations: d.translations } : {}),\n });\n\n // Schrijf eventuele pending wijziging direct weg (gebruikt bij Sluiten, tab-wissel en retry).\n const flushSave = useCallback(async () => {\n // Een veld dat pas op blur committeert (bv. het JSON-redmiddel in GenericEditor) moet die\n // commit eerst nog uitvoeren. Op desktop vuurt blur al vóór de click van deze knop, maar op\n // mobiel Safari is die volgorde niet gegarandeerd (het toetsenbord-sluiten kan blur vertragen\n // tot ná de klik) — expliciet blur() forceert de commit synchroon, ongeacht platform.\n (document.activeElement as HTMLElement | null)?.blur();\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(draftPayload(d));\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 /**\n * Lees erbij wat er live staat, zodat de markering en de publiceerknop ergens op slaan.\n *\n * Eén keer per pagina, niet als abonnement: de gepubliceerde versie verandert alleen doordat\n * JIJ publiceert, en dat moment wordt hieronder zelf afgehandeld. Een tweede live-listener zou\n * bij elke autosave-echo opnieuw willen vergelijken zonder dat er iets veranderd is.\n */\n useEffect(() => {\n const id = draft?.id;\n if (!id) {\n setLiveBlocks(undefined);\n return;\n }\n let active = true;\n getPublishedPage(siteId, id)\n .then((pagina) => {\n if (active) setLiveBlocks((pagina?.blocks as Block[] | undefined) ?? null);\n })\n // Mislukte lezing: dan liever géén markering dan een verkeerde. `undefined` houdt de\n // publiceerknop bereikbaar maar rustig, en niets kleurt rood.\n .catch(() => active && setLiveBlocks(undefined));\n return () => {\n active = false;\n };\n // Alleen op de identiteit van de pagina: de INHOUD van de werkversie verandert continu en mag\n // deze lezing niet opnieuw aanjagen.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [siteId, draft?.id]);\n\n /** Bootstrap vanuit de \"Geen werkversie\"-toestand: maak de pagina hier direct aan (geen omweg\n * via /beheer → Pagina's — dat was een doodloop voor niet-technische klanten). */\n const handleCreatePage = useCallback(async () => {\n setStatus(\"loading\");\n try {\n const segment = path.split(\"/\").filter(Boolean).pop() ?? \"\";\n const title = segment ? segment.replace(/[-_]+/g, \" \").replace(/^\\w/, (c) => c.toUpperCase()) : \"Home\";\n await createPage(siteId, { title, slug: segment || \"home\", path });\n const d = await getDraftByPath(siteId, path);\n if (d) {\n setDraft(d);\n lastSavedRef.current = stableStringify(draftPayload(d));\n setStatus(\"idle\");\n toast({ title: \"Pagina aangemaakt\", description: \"Voeg je eerste blok toe en publiceer wanneer je klaar bent.\", tone: \"success\" });\n } else {\n setStatus(\"none\");\n }\n } catch (e) {\n setStatus(\"none\");\n toast({ title: \"Pagina aanmaken mislukt\", description: (e as Error)?.message, tone: \"error\" });\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [siteId, path, toast]);\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(draftPayload(remote));\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 // Via een ref, zodat het bind-effect hieronder NIET op elke draft-wijziging opnieuw hoeft te\n // draaien. Deed het dat wel, dan brak een autosave van de vorige bewerking de bewerking af die\n // je net was begonnen: de cleanup haalde contentEditable weg en je toetsaanslagen verdwenen.\n const blocksRef = useRef(blocks);\n blocksRef.current = blocks;\n // Canvas in een niet-default taal: toon de vertaal-overlay over de canonieke blokken.\n const overlay = draft?.translations?.[locale];\n const displayBlocks = useMemo(\n () => (isTranslating ? (localizeBlocks(blocks, overlay) as Block[]) : blocks),\n [blocks, isTranslating, overlay],\n );\n\n /**\n * Wat staat er nog niet live? Hierop leunen de rode markering én de knop rechtsonder.\n *\n * Bewust op `blocks` en niet op `displayBlocks`: in vertaalmodus is `displayBlocks` de\n * samengevoegde weergave (standaardtaal + overlay), en die zou élk vertaald veld als wijziging\n * aanwijzen. Publiceren gaat over de bron, dus vergelijken we de bron.\n */\n /** Voor de waarschuwing bij wegnavigeren: die luistert buiten React om en heeft een ref nodig. */\n const nietLiveRef = useRef(false);\n const wijzigingen: Wijzigingen = useMemo(\n () => (liveBlocks === undefined ? GEEN_WIJZIGINGEN : bepaalWijzigingen(blocks, liveBlocks)),\n [blocks, liveBlocks],\n );\n nietLiveRef.current = wijzigingen.iets;\n\n /**\n * Duw de werkversie naar de PAGINA. Dit is de kern van het nieuwe bewerken: de site rendert\n * zichzelf — eigen layout, eigen huisstijl, eigen componenten — maar met deze blokken in plaats\n * van de gepubliceerde. Daardoor is wat je ziet precies wat je bewerkt, zonder dat de editor de\n * pagina hoeft na te bouwen.\n *\n * `displayBlocks` en niet `blocks`: bewerk je een vertaling, dan hoort de pagina die vertaling te\n * tonen en niet het origineel.\n */\n const { publishDraft, zetFotoVervanger } = useEditor();\n\n /**\n * De 📷-knopjes op de pagina aansluiten op de mediakiezer van deze editor.\n *\n * Dit herstelt een stille regressie: `InlineImageEditProvider` werd sinds het verdwijnen van het\n * canvas nergens meer gerenderd, dus geen enkel beeld toonde nog \"Foto wijzigen\" — de import\n * stond er nog, de dialoog stond er nog, alleen de draad ertussen was weg. Een klant kon\n * daardoor geen foto vervangen zonder naar /beheer te gaan.\n */\n useEffect(() => {\n zetFotoVervanger((field, blockKey) => {\n if (blockKey) setImgPicker({ key: blockKey, field });\n });\n return () => zetFotoVervanger(null);\n }, [zetFotoVervanger]);\n const laatstGepubliceerd = useRef<string>(\"\");\n useEffect(() => {\n if (!draft) return;\n // ALLEEN bij een echte inhoudelijke wijziging. Dit effect duwt state naar een context waar de\n // hele pagina op meeleest — en dus ook deze editor. Duw je bij elke render, dan veroorzaakt de\n // render een update en de update een render: \"Maximum update depth exceeded\". Een vergelijking\n // op reference is niet genoeg, want `displayBlocks` kan een nieuwe array zijn met dezelfde\n // inhoud; vandaar een stabiele vergelijking op de inhoud zelf.\n const vinger = stableStringify(displayBlocks);\n if (vinger === laatstGepubliceerd.current) return;\n laatstGepubliceerd.current = vinger;\n publishDraft(path, displayBlocks);\n }, [draft, displayBlocks, path, publishDraft]);\n\n // Schrijf een inline (in-canvas) tekstwijziging terug naar het juiste blokveld (canoniek).\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 // Schrijf een vertaling naar de taal-overlay; leeg = sleutel weg (terugvallen op origineel).\n const setTranslation = useCallback(\n (tkey: string, value: string) => {\n mutateDraft(\n (d) => {\n const overlay = { ...(d.translations?.[locale] ?? {}) };\n if (value.trim() === \"\" || value === undefined) delete overlay[tkey];\n else overlay[tkey] = value;\n return { ...d, translations: { ...(d.translations ?? {}), [locale]: overlay } };\n },\n { coalesce: true },\n );\n },\n [mutateDraft, locale],\n );\n\n // ── Inline opmaak-toolbar (WYSIWYG op het canvas) ─────────────────────────\n // Zwevende mini-toolbar boven een gefocust rich-text-veld: vet/schuin/onderstrepen/link +\n // groter-kleiner via kop-niveaus (H2/H3/normaal — semantisch i.p.v. losse font-sizes).\n // document.execCommand is deprecated-maar-universeel en precies goed voor contentEditable;\n // de output gaat bij commit door DOMPurify én server-side door de allowlist-sanitizer.\n const [fmtBar, setFmtBar] = useState<{ top: number; left: number } | null>(null);\n const fmtElRef = useRef<HTMLElement | null>(null);\n const positionFmtBar = useCallback((el: HTMLElement) => {\n const r = el.getBoundingClientRect();\n setFmtBar({\n top: Math.max(56, r.top - 44),\n left: Math.min(Math.max(8, r.left), Math.max(8, window.innerWidth - 300)),\n });\n }, []);\n useEffect(() => {\n if (!fmtBar) return;\n const onScroll = () => {\n const el = fmtElRef.current;\n if (el) positionFmtBar(el);\n };\n window.addEventListener(\"scroll\", onScroll, { capture: true, passive: true });\n return () => window.removeEventListener(\"scroll\", onScroll, { capture: true });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [fmtBar !== null, positionFmtBar]);\n\n // Inline canvas-edits: in de default-taal naar het blokveld, in een andere taal naar de overlay.\n const commitText = useCallback(\n (key: string, field: string, value: string) => {\n if (isTranslating) setTranslation(`${key}.${field}`, value);\n else commitInline(key, field, value);\n },\n [isTranslating, setTranslation, commitInline],\n );\n\n // ── Inline bewerken: eerst het potlood, dan pas de tekst ──────────────────\n // Tot 7.3 werd élk tekstveld van het geselecteerde blok meteen contentEditable. Dat leest als\n // \"alles staat open\": één misklik in een kop en je typt al in de pagina, met autosave eronder.\n // Nu markeert de editor de bewerkbare velden alleen (een subtiele stippellijn) en zet een\n // potloodje ernaast de bewerking BEWUST aan — pas daarna verschijnen caret en opmaak-toolbar.\n // Dat maakt bovendien zichtbaar wát inline kan; dat wist je voorheen pas als je erop klikte.\n /**\n * Tandwielen naast elk blok OP DE PAGINA.\n *\n * Dit zat vroeger in de werkbalkjes van het canvas. Nu er geen canvas meer is, moeten ze over de\n * echte blokken heen — dezelfde aanpak als de potloodjes: vast gepositioneerd in de editorlaag,\n * en opnieuw meten zodra er gescrold of geschaald wordt. De pagina zelf wordt niet aangeraakt,\n * want dat is de site van de klant.\n */\n // Via een ref: de registry is een context-waarde en mag de meting niet opnieuw aanjagen.\n // Via een ref: de registry is een context-waarde en mag de meting niet opnieuw aanjagen.\n const registryRef = useRef(registry);\n registryRef.current = registry;\n\n /**\n * Hoe ver van bovenaf is het veilig om iets neer te zetten?\n *\n * De knoppen van het bovenste blok landden onder de balk van de editor én onder de plakkende\n * menubalk van de site zelf. Ze wáren er wel — ze lagen alleen onder iets anders, en dus kon je\n * het hoofdblok als enige blok niet bewerken of instellen. Dat was geen randgeval: het eerste\n * blok van de homepagina is meestal juist het blok dat je wilt aanpassen.\n *\n * We meten daarom wat er werkelijk bovenaan vastgeplakt zit — de eigen balk plus elk plakkend of\n * vastgezet element van de site dat de bovenrand raakt — in plaats van een vast getal te kiezen\n * dat op de ene site klopt en op de andere niet.\n */\n function veiligeBovenkant(): number {\n let onderkant = 0;\n for (const el of Array.from(document.querySelectorAll<HTMLElement>(\"body *\"))) {\n const pos = getComputedStyle(el).position;\n if (pos !== \"fixed\" && pos !== \"sticky\") continue;\n const r = el.getBoundingClientRect();\n // Alleen wat écht bovenaan hangt telt mee; een plakkende knop onderin de pagina niet.\n if (r.height === 0 || r.top > 4) continue;\n onderkant = Math.max(onderkant, r.bottom);\n }\n return onderkant;\n }\n\n /**\n * Potlood én tandwiel bij ELK blok.\n *\n * Tot 7.8 stond er per blok alleen een tandwiel, en verschenen de potloden pas nádat je een blok\n * had geselecteerd — via datzelfde tandwiel. Inline bewerken, de hoofdweg van dit hele systeem,\n * zat dus achter de knop voor het uitzonderingsgeval. Nu staan beide er meteen: het potlood zet\n * de tekst van dat blok open, het tandwiel opent alles wat je niet kunt typen.\n */\n const [blokknoppen, setBlokknoppen] = useState<{ key: string; type: string; top: number; left: number; label: string }[]>([]);\n useEffect(() => {\n if (!draft) {\n // `setBlokknoppen([])` zou hier een NIEUWE lege array zetten bij elke render — en dus elke\n // render een update, en elke update een render: \"Maximum update depth exceeded\". De\n // functionele vorm geeft de bestaande array terug als er toch al niets stond.\n setBlokknoppen((v) => (v.length === 0 ? v : []));\n return;\n }\n /**\n * De veilige bovenkant is duur om te bepalen (hij loopt de hele DOM af op zoek naar plakkende\n * elementen) en verandert alleen bij een resize of een structurele wijziging — niet bij\n * scrollen. `meet()` draait wél bij elke scroll-tick; die twee horen dus niet in één adem.\n * Gemeten winst: van O(alle elementen) naar O(blokken) per scroll-frame.\n */\n let veilig = veiligeBovenkant();\n const herijk = () => {\n veilig = veiligeBovenkant();\n meet();\n };\n const meet = () => {\n const gevonden: { key: string; type: string; top: number; left: number; label: string }[] = [];\n for (const el of Array.from(document.querySelectorAll<HTMLElement>(\"[data-xicms-block]\"))) {\n const key = el.dataset.xicmsBlock;\n if (!key) continue;\n const r = el.getBoundingClientRect();\n // Blokken zonder afmeting (nog niet zichtbaar, of `display:contents`) leveren knoppen in de\n // linkerbovenhoek op — dat is geen aanwijzing maar ruis.\n if (r.width === 0 && r.height === 0) continue;\n // Volledig uit beeld gescrolde blokken hoeven geen knoppen: die zouden vastgeklemd worden\n // aan de boven- of onderrand en daar blijven zweven bij een blok dat je niet ziet.\n if (r.bottom < veilig || r.top > window.innerHeight) continue;\n const type = el.dataset.xicmsBlockType ?? \"\";\n gevonden.push({\n key,\n type,\n top: Math.max(r.top + 8, veilig + 8),\n left: Math.max(r.right - 62, 8),\n label: registryRef.current.get(type)?.label ?? type,\n });\n }\n setBlokknoppen((vorige) => {\n // Ongewijzigde posities niet terugschrijven: elke setState is een render, en een render\n // meet opnieuw. Zonder deze vergelijking draait dat rond.\n if (vorige.length === gevonden.length && vorige.every((v, i) =>\n v.key === gevonden[i].key && v.top === gevonden[i].top && v.left === gevonden[i].left)) {\n return vorige;\n }\n return gevonden;\n });\n };\n // rAF-bundeling: scroll- en resize-events komen in salvo's; meten hoeft hoogstens één keer\n // per frame. Zonder dit liep de meting tijdens het scrollen vaker dan het scherm ververst.\n let frame = 0;\n const meetGepland = () => {\n cancelAnimationFrame(frame);\n frame = requestAnimationFrame(meet);\n };\n meet();\n window.addEventListener(\"scroll\", meetGepland, { capture: true, passive: true });\n window.addEventListener(\"resize\", herijk);\n return () => {\n cancelAnimationFrame(frame);\n window.removeEventListener(\"scroll\", meetGepland, { capture: true });\n window.removeEventListener(\"resize\", herijk);\n };\n }, [draft, displayBlocks, registryRef]);\n\n /**\n * Producten krijgen hun eigen potlood en tandwiel, maar alleen waar je muis is.\n *\n * Een cataloguspagina toont er twaalf tegelijk. Twaalf vaste knoppenparen naast twaalf kaarten is\n * geen gereedschap meer maar een raster van knoppen over de site heen — dan kijk je niet meer naar\n * je winkel. Op aanwijzen verschijnt er precies één paar, bij het product waar je naar kijkt.\n */\n const [productKnop, setProductKnop] = useState<{ id: string; top: number; left: number } | null>(null);\n useEffect(() => {\n if (!draft) return;\n const onOver = (e: MouseEvent) => {\n const doel = (e.target as HTMLElement | null)?.closest?.(\"[data-xicms-product]\") as HTMLElement | null;\n if (!doel) {\n // Niet meteen weghalen zodra je de kaart verlaat: je verlaat hem juist om NAAR de knoppen te\n // gaan. De knoppen zelf zitten niet in de kaart, dus zonder deze uitzondering verdwijnen ze\n // op weg ernaartoe en is er niets aan te klikken.\n const naarKnop = (e.target as HTMLElement | null)?.closest?.(\".xicms-ve-blokknoppen\");\n if (!naarKnop) setProductKnop(null);\n return;\n }\n const id = doel.dataset.xicmsProduct;\n if (!id) return;\n const r = doel.getBoundingClientRect();\n setProductKnop((v) =>\n v && v.id === id && v.top === Math.max(r.top + 8, 44) && v.left === Math.max(r.right - 62, 8)\n ? v\n : { id, top: Math.max(r.top + 8, 44), left: Math.max(r.right - 62, 8) },\n );\n };\n document.addEventListener(\"mouseover\", onOver, true);\n return () => document.removeEventListener(\"mouseover\", onOver, true);\n }, [draft]);\n\n /**\n * Inline bewerken van een PRODUCT — een ander pad dan een blokveld, met opzet.\n *\n * Een blokveld gaat naar de werkversie van deze pagina en wordt zichtbaar bij publiceren. Een\n * product staat in de productencollectie en wordt door élke pagina gelezen die het toont; daar\n * bestaat geen werkversie voor. Zie `productClient.ts` voor waarom dat zo blijft.\n */\n const productBewerkRef = useRef<HTMLElement | null>(null);\n const startProductInline = useCallback(\n (productId: string) => {\n const host = document.querySelector<HTMLElement>(`[data-xicms-product=\"${CSS.escape(productId)}\"]`);\n const el = host?.querySelector<HTMLElement>(\"[data-xicms-product-edit]\");\n if (!el) {\n toast({\n title: \"Hier valt niets te typen\",\n description: \"Gebruik het tandwiel voor prijs, foto's, varianten en voorraad.\",\n });\n return;\n }\n const veld = el.dataset.xicmsProductEdit;\n if (!veld) return;\n const origineel = el.textContent ?? \"\";\n el.setAttribute(\"contenteditable\", \"true\");\n el.setAttribute(\"spellcheck\", \"true\");\n el.classList.add(\"is-editing\");\n productBewerkRef.current = el;\n\n const stop = () => {\n el.removeAttribute(\"contenteditable\");\n el.classList.remove(\"is-editing\");\n el.removeEventListener(\"blur\", opBlur);\n el.removeEventListener(\"keydown\", opToets);\n if (productBewerkRef.current === el) productBewerkRef.current = null;\n };\n const opBlur = () => {\n const waarde = el.textContent ?? \"\";\n stop();\n if (waarde === origineel) return;\n void setProductField(siteId, productId, veld, waarde)\n .then(() =>\n toast({\n title: \"Product bijgewerkt\",\n description: \"Producten hebben geen werkversie: dit staat meteen live, op elke pagina.\",\n tone: \"success\",\n }),\n )\n .catch((err: Error) => {\n // Terugdraaien op het scherm, anders blijft er tekst staan die nergens bewaard is.\n el.textContent = origineel;\n toast({ title: \"Opslaan mislukt\", description: err.message, tone: \"error\" });\n });\n };\n const opToets = (ev: KeyboardEvent) => {\n if (ev.key === \"Escape\") {\n ev.stopPropagation();\n el.textContent = origineel;\n el.blur();\n } else if (ev.key === \"Enter\") {\n ev.preventDefault();\n el.blur();\n }\n };\n el.addEventListener(\"blur\", opBlur);\n el.addEventListener(\"keydown\", opToets);\n el.focus();\n const selection = window.getSelection();\n const range = document.createRange();\n range.selectNodeContents(el);\n range.collapse(false);\n selection?.removeAllRanges();\n selection?.addRange(range);\n },\n [siteId, toast],\n );\n\n /**\n * Rood en onderstreept: dit staat nog niet op de live site.\n *\n * De vraag die dit beantwoordt is \"wat heb ik nou eigenlijk veranderd?\" — en die was tot 7.8\n * onbeantwoordbaar. Je zag je eigen tekst staan en kon niet zien of een bezoeker hem al had.\n * Na publiceren wordt de markering vanzelf schoon, want dan is de werkversie gelijk aan wat er\n * live staat en levert `bepaalWijzigingen` niets meer op. De kleur is dus geen opmaak maar een\n * toestand: rood = van jou, gewone kleur = van iedereen.\n *\n * De klassen gaan op de LIVE DOM, net als de potloden — de render van de klant blijft schoon en\n * de markering verdwijnt met het gereedschap mee.\n */\n useEffect(() => {\n if (!draft) return;\n const gemarkeerd: HTMLElement[] = [];\n const markeer = (el: HTMLElement | null, klasse: string) => {\n if (!el) return;\n el.classList.add(klasse);\n gemarkeerd.push(el);\n };\n\n for (const key of wijzigingen.blokken) {\n markeer(document.querySelector<HTMLElement>(`[data-xicms-block=\"${CSS.escape(key)}\"]`), \"xicms-ve-nieuw\");\n }\n for (const [key, velden] of wijzigingen.velden) {\n const host = document.querySelector<HTMLElement>(`[data-xicms-block=\"${CSS.escape(key)}\"]`);\n if (!host) continue;\n for (const veld of velden) {\n // Het veldpad is dezelfde puntnotatie die het blok in zijn DOM-haak zet, dus dit is een\n // rechtstreekse treffer. Een veld dat wel wijzigde maar niets op het scherm zet (een kleur,\n // een limiet) heeft geen haak en levert hier gewoon niets op — terecht: er valt niets te\n // markeren aan iets wat je niet ziet. Dát er iets veranderde blijkt uit de knop rechtsonder.\n host\n .querySelectorAll<HTMLElement>(`[data-xicms-edit=\"${CSS.escape(veld)}\"]`)\n .forEach((el) => markeer(el, \"xicms-ve-gewijzigd\"));\n }\n }\n\n return () => {\n for (const el of gemarkeerd) {\n el.classList.remove(\"xicms-ve-nieuw\", \"xicms-ve-gewijzigd\");\n }\n };\n // `displayBlocks` staat erbij omdat de DOM-nodes na een re-render vervangen kunnen zijn: dan\n // moet er opnieuw gemarkeerd worden, ook al is de verzameling wijzigingen dezelfde.\n }, [draft, wijzigingen, displayBlocks]);\n\n const [spots, setSpots] = useState<{ top: number; left: number; title: string }[]>([]);\n const targetsRef = useRef<{ el: HTMLElement; field: string; html: boolean }[]>([]);\n const editingRef = useRef<HTMLElement | null>(null);\n /** Gezet door het potlood bij een blok: na het binden meteen in het eerste veld staan. */\n const wilInlineRef = useRef(false);\n\n const stopInline = useCallback((el: HTMLElement) => {\n el.removeAttribute(\"contenteditable\");\n el.classList.remove(\"is-editing\");\n if (editingRef.current === el) editingRef.current = null;\n }, []);\n\n const startInline = useCallback((index: number) => {\n const target = targetsRef.current[index];\n if (!target) return;\n const { el } = target;\n if (editingRef.current && editingRef.current !== el) editingRef.current.blur();\n el.setAttribute(\"contenteditable\", \"true\");\n el.setAttribute(\"spellcheck\", \"true\");\n el.classList.add(\"is-editing\");\n editingRef.current = el;\n el.focus();\n // Caret aan het eind, geen selectie over de hele tekst: bewerken is meestal bijschaven, en een\n // volledige selectie betekent dat de eerste toetsaanslag alles wist.\n const selection = window.getSelection();\n const range = document.createRange();\n range.selectNodeContents(el);\n range.collapse(false);\n selection?.removeAllRanges();\n selection?.addRange(range);\n }, []);\n\n useEffect(() => {\n if (!selectedKey) {\n targetsRef.current = [];\n setSpots([]);\n return;\n }\n // Het blok staat op de LIVE pagina, niet in een canvas van de editor. `BlockRenderer` zet\n // `data-xicms-block` op elk blok — op de gepubliceerde pagina net zo goed als hier — dus dat is\n // de haak. Vandaar ook `document` en niet een ref: de pagina is niet van de editor.\n const host = document.querySelector<HTMLElement>(`[data-xicms-block=\"${selectedKey}\"]`);\n if (!host) return;\n const geselecteerd = blocksRef.current.find((b) => b._key === selectedKey);\n const type = geselecteerd?._type ?? \"\";\n const def = registry.get(String(type));\n warnInlineCoverage(String(type), def, host, geselecteerd ?? {});\n\n // Leesbare veldnamen voor de tooltip van het potlood (\"Bewerk: Ondertitel\"), uit dezelfde\n // schema-beschrijvingen die het zijpaneel gebruikt — zodat beide paden hetzelfde heten.\n const labels = new Map<string, string>();\n if (def) {\n try {\n for (const f of introspectToFields(def.schema, def.fields)) labels.set(f.key, f.label);\n } catch {\n /* een blok zonder introspecteerbaar schema krijgt gewoon de veldnaam */\n }\n }\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.classList.add(\"xicms-ve-inline\");\n el.addEventListener(\"blur\", () => {\n // Inline HTML wordt vóór commit gesanitized (DOMPurify) — zelfde belofte als het TipTap-pad;\n // de server sanitized nogmaals bij publiceren. Geplakte Word-/Docs-rommel blijft zo buiten.\n commitText(selectedKey, field, html ? DOMPurify.sanitize(el.innerHTML) : (el.textContent ?? \"\"));\n stopInline(el);\n });\n if (html) {\n // Opmaak-toolbar tonen zolang dit rich-text-veld focus heeft.\n el.addEventListener(\"focus\", () => {\n fmtElRef.current = el;\n positionFmtBar(el);\n });\n el.addEventListener(\"blur\", () => {\n fmtElRef.current = null;\n setFmtBar(null);\n });\n }\n el.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Escape\") {\n // NIET laten doorborrelen. De editor luistert zelf ook op Escape om te sluiten, en die\n // controle (\"sta ik in een invoerveld?\") kijkt naar het element dat focus HEEFT. Na\n // `blur()` is dat de body, dus concludeerde hij dat je niet aan het typen was en sloot\n // hij de hele bewerkmodus. Eén Escape hoorde het veld te sluiten, niet de werkplek.\n e.stopPropagation();\n el.blur();\n } else if (!html && e.key === \"Enter\") { e.preventDefault(); el.blur(); }\n });\n bound.add(el);\n };\n\n /** De potloodjes staan in de editor-laag, niet in het blok: de render van de klant blijft schoon. */\n const measure = () => {\n const next = targetsRef.current.map(({ el, field }) => {\n const r = el.getBoundingClientRect();\n // Naast het veld, op ooghoogte van de tekst: rechts ervan, verticaal gecentreerd. Niet\n // links (dan staat het potlood van de tweede knop bovenop de eerste) en niet ín de hoek\n // (dan ligt het over de tekst). Aanwijzen laat het veld oplichten, dus wélk veld het is\n // blijft ook bij twee elementen naast elkaar duidelijk.\n return {\n top: Math.min(window.innerHeight - 22, Math.max(4, r.top + r.height / 2 - 9)),\n left: Math.min(window.innerWidth - 22, Math.max(4, r.right + 6)),\n title: `Bewerk: ${labels.get(field) ?? field}`,\n };\n });\n setSpots((prev) =>\n prev.length === next.length && prev.every((p, i) => p.top === next[i].top && p.left === next[i].left && p.title === next[i].title)\n ? prev\n : next,\n );\n };\n\n const apply = () => {\n const found: { el: HTMLElement; field: string; html: boolean }[] = [];\n for (const m of GLOBAL_INLINE) {\n host.querySelectorAll<HTMLElement>(m.selector).forEach((el) => {\n // Een expliciete `data-xicms-edit` wint altijd; anders het veld van de matcher.\n const field = el.dataset.xicmsEdit ?? m.field;\n bind(el, field, !!m.html);\n found.push({ el, field, html: !!m.html });\n });\n }\n targetsRef.current = found;\n measure();\n };\n\n apply();\n if (wilInlineRef.current) {\n wilInlineRef.current = false;\n // Alleen als er iets te bewerken is. Een blok zonder inline-velden (een productraster, een\n // winkelwagen) mag geen caret ergens neerzetten; daar is het tandwiel de goede weg en dat\n // zegt het blok zelf door geen enkele haak aan te bieden.\n if (targetsRef.current.length > 0) startInline(0);\n // Stil niets doen is hier het slechtste antwoord: dan lijkt de knop kapot. Zeg waar het wél\n // kan — bij een productraster staat de tekst in de producten, niet in het blok.\n else {\n toast({\n title: \"Dit blok heeft geen eigen tekst\",\n description: \"Wijs een product aan voor zijn naam, of gebruik het tandwiel voor de instellingen van dit blok.\",\n });\n }\n }\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 // Het canvas scrollt onder de potloodjes door; ze staan fixed, dus hermeten bij elke beweging.\n let frame = 0;\n const onMove = () => {\n cancelAnimationFrame(frame);\n frame = requestAnimationFrame(measure);\n };\n window.addEventListener(\"scroll\", onMove, { capture: true, passive: true });\n window.addEventListener(\"resize\", onMove, { passive: true });\n return () => {\n obs.disconnect();\n cancelAnimationFrame(frame);\n window.removeEventListener(\"scroll\", onMove, { capture: true });\n window.removeEventListener(\"resize\", onMove);\n bound.forEach((el) => {\n stopInline(el);\n el.classList.remove(\"xicms-ve-inline\");\n delete el.dataset.veInline;\n });\n targetsRef.current = [];\n setSpots([]);\n };\n // Alleen `selectedKey`: dit effect bindt de velden van hét geselecteerde blok. `blocks` en\n // `registry` lezen we uit een ref/context — draaide dit effect op elke draft-wijziging, dan\n // sloopte een autosave de lopende bewerking. Nieuwe DOM na een re-render wordt opgevangen door\n // de MutationObserver hieronder, niet door een her-run van het effect.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [selectedKey, commitText, positionFmtBar, stopInline, startInline, toast]);\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 // Bij een openstaand conflict PAUZEREN we autosave: de banner wegklikken en doorwerken\n // overschreef anders stil de wijzigingen van de collega/andere tab (destructief-by-default).\n // De klant kiest eerst expliciet \"Nieuwste laden\" of \"Mijn versie behouden\".\n if (conflict) return;\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, conflict]);\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 // Twee redenen om te waarschuwen, en ze zijn niet hetzelfde:\n // · `dirtyRef` — de laatste toetsaanslagen staan nog niet in de werkversie (autosave loopt).\n // · `nietLiveRef` — er is wél opgeslagen, maar niets van dit alles staat op de live site.\n // Tot 7.8 waarschuwde alleen de eerste. Wie rustig had zitten typen kreeg dus géén melding,\n // want alles was netjes bewaard — terwijl er nog steeds niets gepubliceerd was. Precies de\n // situatie waarin je denkt dat je klaar bent.\n if (dirtyRef.current || nietLiveRef.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 // Zelfde herkenning als useNestedDialog: Astryx' dialoog is een natieve <dialog open>.\n const dialogOpen = document.querySelector('dialog[open], [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 /** Symbool loskoppelen (5.0): vervang de referentie door een kloon van de symboolblokken —\n * daarna beweegt dit exemplaar niet meer mee met het gedeelde symbool. */\n async function detachSymbol(i: number) {\n const blk = blocks[i] as { _type?: string; symbolId?: string; _key: string };\n if (blk._type !== \"symbolRef\" || !blk.symbolId) return;\n const ok = await confirm({\n title: \"Symbool loskoppelen?\",\n description: \"Dit blok wordt een losse kopie en beweegt niet meer mee met het gedeelde symbool.\",\n confirmLabel: \"Loskoppelen\",\n });\n if (!ok) return;\n const { getSymbolOnce } = await import(\"../lib/symbolsClient\");\n const sym = await getSymbolOnce(siteId, blk.symbolId);\n if (!sym) {\n toast({ title: \"Symbool niet gevonden\", tone: \"error\" });\n return;\n }\n const clones = sym.blocks.map((b) => ({ ...(b as Record<string, unknown>), _key: newKey() })) as Block[];\n const copy = [...blocks];\n copy.splice(i, 1, ...clones);\n setBlocks(copy);\n if (selectedKey === blk._key) setSelectedKey(null);\n toast({ title: \"Losgekoppeld\", description: \"Dit is nu een losse kopie.\", tone: \"success\" });\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 // Zie flushSave: een veld dat pas op blur committeert (JSON-redmiddel) moet die commit eerst\n // nog doen — anders publiceert dit de stand van vóór de laatste bewerking.\n (document.activeElement as HTMLElement | null)?.blur();\n const d = draftRef.current;\n if (!d) return;\n // Openstaand conflict eerst oplossen: publiceren zou anders een stale merge live zetten.\n if (conflict) {\n toast({ title: \"Los eerst het conflict op\", description: \"Kies 'Nieuwste versie laden' of 'Mijn versie behouden' in de gele balk.\", tone: \"error\" });\n return;\n }\n const ok = await confirm({ title: \"Pagina publiceren?\", description: `De live site wordt bijgewerkt (${d.path}).`, confirmLabel: \"Publiceren\" });\n if (!ok) return;\n setPublishing(true);\n try {\n await saveDraft(siteId, d.id, draftPayload(d));\n await publishPage(siteId, d.id);\n toast({\n title: \"Gepubliceerd\",\n description: \"Zelf zie je de nieuwe tekst meteen. Voor overige bezoekers duurt het nog ongeveer 15 minuten.\",\n tone: \"success\",\n });\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 ref={containerRef} className=\"xicms-ve__shell\" data-xicms-tooling=\"\" role=\"group\" aria-label={`Pagina bewerken: ${path}`}>\n {/* Dezelfde balk als op de live site (`EditBar`), met hetzelfde `AdminModes` erin — alleen\n staat \"Bewerken\" nu ingedrukt en hangt het gereedschap ernaast. Zo verspringt er niets\n op het moment dat je overschakelt: de knoppen blijven waar je ze net nog zag.\n Publiceren staat hier BEWUST niet meer; dat is de zwevende knop rechtsonder, bij je werk,\n en niet weggestopt tussen de navigatie. */}\n <header className=\"xicms-ve__bar\" role=\"toolbar\" aria-label=\"Beheer\">\n <span className=\"xicms-editbar__brand\">{isDemoActive() ? \"XiCMS · demo\" : \"XiCMS\"}</span>\n <AdminModes\n actief=\"bewerken\"\n voorSwitch={(m) => {\n if (m === \"live\" && nietLiveRef.current) {\n toast({\n title: \"Nog niet gepubliceerd\",\n description: `${aantalWijzigingen(wijzigingen)} blijft bewaard als werkversie. Je ziet ze weer zodra je terug naar Bewerken gaat.`,\n });\n }\n }}\n />\n <span className=\"xicms-ve__sep\" aria-hidden=\"true\" />\n <span className=\"xicms-ve__path\" title={`Je bewerkt ${name} — ${path}`}>{path}</span>\n {localeCfg.locales.length > 1 && (\n <span className=\"xicms-ve__locale\" title=\"Taal — in een andere taal dan de standaard bewerk je vertalingen\">\n 🌐\n <Select\n value={locale}\n onValueChange={(l) => { setLocale(l); if (l !== localeCfg.defaultLocale) setPanelOpen(true); }}\n options={localeCfg.locales.map((l) => ({ value: l, label: l === localeCfg.defaultLocale ? `${l} (standaard)` : l }))}\n />\n </span>\n )}\n <button\n type=\"button\"\n className={`xicms-ve__histbtn${panelOpen && !selectedKey ? \" is-on\" : \"\"}`}\n onClick={() => { if (panelOpen && !selectedKey) { setPanelOpen(false); } else { setSelectedKey(null); setPanelOpen(true); } }}\n title=\"Pagina-instellingen (titel, SEO, versies)\"\n aria-label=\"Pagina-instellingen\"\n aria-pressed={panelOpen && !selectedKey}\n >\n ⚙\n </button>\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 {status === \"none\" && (\n <span className=\"xicms-ve__balkmelding\">\n Nog geen werkversie\n <Button size=\"sm\" onClick={() => void handleCreatePage()}>+ Pagina aanmaken</Button>\n </span>\n )}\n {/* \"Opgeslagen\" gaat over de WERKVERSIE, niet over de live site. Dat onderscheid stond\n nergens en werd daardoor gelezen als \"het staat online\". Vandaar de toevoeging. */}\n <span className=\"xicms-ve__status\" aria-live=\"polite\">\n {status === \"loading\" ? \"Werkversie laden…\" : null}\n {status === \"saving\" ? \"Opslaan…\" : status === \"saved\" ? \"✓ Bewaard als werkversie\" : 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 </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${panelOpen ? \" is-panel-open\" : \"\"}`}>\n {/* GEEN CANVAS MEER. De pagina eronder is de echte pagina en toont de werkversie (zie\n EditorProvider). Wat de editor nog moet zeggen — laden, of nog geen werkversie — staat\n in de balk hierboven, niet als los zwevend kaartje: dat dekte de menubalk van de site af,\n en gereedschap hoort de site niet in de weg te zitten. */}\n\n {panelOpen && (\n <aside className=\"xicms-ve__panel\">\n {/* Blokken toevoegen zat in de werkbalkjes van het canvas. Dat canvas is er niet meer, dus\n staat het hier — zichtbaar wanneer je het paneel toch al open hebt om aan de opbouw\n van de pagina te werken. Zonder deze verhuizing zou het toevoegen van een blok\n stilzwijgend zijn verdwenen. */}\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 {/* Een aangeklikt PRODUCT wint van een aangeklikt blok: je wees het product aan, dus dat\n wil je zien. Blok en product uitsluiten elkaar hier bewust — twee formulieren onder\n elkaar in één smal paneel is niet te lezen. */}\n {selectedProduct ? (\n <div className=\"grid gap-3\">\n <div className=\"xicms-ve__panel-head\">\n <span className=\"text-sm text-slate-500\">Webwinkel</span>\n <Button size=\"sm\" variant=\"ghost\" onClick={() => { setSelectedProduct(null); setPanelOpen(false); }}>\n ✕ Sluiten\n </Button>\n </div>\n <ProductPaneel siteId={siteId} productId={selectedProduct} />\n </div>\n ) : selected && isTranslating ? (\n <div className=\"grid gap-3\">\n <h3 className=\"text-sm font-semibold\">{registry.get(selected._type)?.label ?? selected._type} — vertaling ({locale})</h3>\n <TranslationPanel\n key={`${selected._key}:${locale}`}\n block={selected}\n siteId={siteId}\n registry={registry}\n locale={locale}\n overlay={overlay ?? {}}\n onTranslate={setTranslation}\n />\n </div>\n ) : selected ? (\n <div className=\"grid gap-3\">\n <div className=\"xicms-ve__panel-head\">\n {/* Kleur volgt de autosave-status (zelfde bron als de statustekst in de topbalk):\n blauw = nog niet-opgeslagen wijziging (of een mislukte save die opnieuw moet),\n grijs = alles staat al veilig. Directe visuele bevestiging i.p.v. alleen tekst. */}\n <Button\n size=\"sm\"\n variant={status === \"saving\" || status === \"error\" ? \"primary\" : \"ghost\"}\n onClick={() => void flushSave().catch(() => setStatus(\"error\"))}\n >\n {status === \"saving\" ? \"Opslaan…\" : status === \"error\" ? \"⚠ Opnieuw opslaan\" : \"✓ Opgeslagen\"}\n </Button>\n <Button size=\"sm\" variant=\"ghost\" onClick={() => setPanelOpen(false)}>\n ✕ Sluiten\n </Button>\n </div>\n <SelectedEditor key={selected._key} block={selected} siteId={siteId} registry={registry} onChange={(next) => updateByKey(selected._key, next)} />\n </div>\n ) : draft && isTranslating ? (\n <div className=\"grid gap-4\">\n <p className=\"xicms-ve__hint\">\n Je bewerkt de vertaling <strong>{locale}</strong>. Klik op een blok om zijn teksten te\n vertalen; lege velden vallen terug op de standaardtaal. Blokken toevoegen/verplaatsen\n doe je in de standaardtaal.\n </p>\n <PageTranslationSettings draft={draft} locale={locale} onTranslate={setTranslation} />\n </div>\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 )}\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) => {\n if (imgPicker) {\n commitInline(imgPicker.key, imgPicker.field, m.url);\n // Alt-tekst van de gekozen media meenemen (a11y/SEO): conventie url→alt / imageUrl→alt\n // op hetzelfde niveau. Bestaat het veld niet in het schema, dan stript Zod het bij\n // publiceren — nooit schadelijk, meestal precies goed (Image/Gallery/Cards/Slider).\n const altField = imgPicker.field.replace(/(^|\\.)((image)?[uU]rl)$/, \"$1alt\");\n if (altField !== imgPicker.field) commitInline(imgPicker.key, altField, m.alt ?? \"\");\n }\n setImgPicker(null);\n }}\n />\n\n {/* Potlood én tandwiel per blok, plus — op aanwijzen — de rest van het gereedschap.\n De acties verplaatsen/dupliceren/verwijderen BESTONDEN al die tijd: ze zaten in de\n werkbalkjes van het oude canvas, en toen dat canvas verdween is de UI eromheen nooit\n teruggebouwd. De functies bleven staan, niets riep ze aan, en TypeScript klaagt niet over\n een ongebruikte functiedeclaratie — dus de enige weg om een blok te verplaatsen liep\n maandenlang via het formulierscherm in /beheer. Precies waar de klant weg moest blijven.\n\n Twee knoppen in rust, de rest klapt uit bij aanwijzen: zes knoppen die permanent naast\n élk blok staan zijn geen gereedschap meer maar een hek om de site heen. */}\n {blokknoppen.map((b) => {\n const i = blocks.findIndex((blok) => blok._key === b.key);\n const isSymbool = b.type === \"symbolRef\";\n return (\n <div key={b.key} className=\"xicms-ve-blokknoppen\" style={{ top: b.top, left: b.left }}>\n <button\n type=\"button\"\n className={`xicms-ve-blokknop${b.key === selectedKey ? \" is-on\" : \"\"}`}\n title={`Tekst van ${b.label} bewerken`}\n aria-label={`Tekst van ${b.label} bewerken`}\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => {\n // Selecteren zet de velden van dít blok open (zie het bind-effect hierboven); de vlag\n // zorgt dat de caret meteen in het eerste veld staat. Zonder die vlag moest je na het\n // potlood nóg een potlood aanklikken, en dan is de eerste klik betekenisloos.\n wilInlineRef.current = true;\n setSelectedProduct(null);\n setSelectedKey(b.key);\n }}\n >\n <svg viewBox=\"0 0 24 24\" width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M4 20h4L19.5 8.5a2.1 2.1 0 0 0-3-3L5 17v3z\" />\n <path d=\"M14.5 6.5l3 3\" />\n </svg>\n </button>\n <button\n type=\"button\"\n className={`xicms-ve-blokknop${b.key === selectedKey && panelOpen ? \" is-on\" : \"\"}`}\n title={`Instellingen van ${b.label}`}\n aria-label={`Instellingen van ${b.label}`}\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => { setSelectedProduct(null); setSelectedKey(b.key); setPanelOpen(true); }}\n >\n <svg viewBox=\"0 0 24 24\" width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"3\" />\n <path d=\"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.6a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z\" />\n </svg>\n </button>\n {i >= 0 && (\n <span className=\"xicms-ve-blokknoppen__meer\">\n <button type=\"button\" className=\"xicms-ve-blokknop\" title=\"Blok omhoog\" aria-label={`${b.label} omhoog`} disabled={i === 0}\n onMouseDown={(e) => e.preventDefault()} onClick={() => move(i, -1)}>\n <svg viewBox=\"0 0 24 24\" width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\"><path d=\"M12 19V5M5 12l7-7 7 7\" /></svg>\n </button>\n <button type=\"button\" className=\"xicms-ve-blokknop\" title=\"Blok omlaag\" aria-label={`${b.label} omlaag`} disabled={i === blocks.length - 1}\n onMouseDown={(e) => e.preventDefault()} onClick={() => move(i, 1)}>\n <svg viewBox=\"0 0 24 24\" width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\"><path d=\"M12 5v14M19 12l-7 7-7-7\" /></svg>\n </button>\n <button type=\"button\" className=\"xicms-ve-blokknop\" title=\"Blok dupliceren\" aria-label={`${b.label} dupliceren`}\n onMouseDown={(e) => e.preventDefault()} onClick={() => duplicate(i)}>\n <svg viewBox=\"0 0 24 24\" width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\"><rect x=\"9\" y=\"9\" width=\"11\" height=\"11\" rx=\"2\" /><path d=\"M5 15V5a2 2 0 0 1 2-2h10\" /></svg>\n </button>\n {isSymbool && (\n <button type=\"button\" className=\"xicms-ve-blokknop\" title=\"Symbool loskoppelen (wordt een losse kopie)\" aria-label={`${b.label} loskoppelen`}\n onMouseDown={(e) => e.preventDefault()} onClick={() => void detachSymbol(i)}>\n <svg viewBox=\"0 0 24 24\" width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\"><path d=\"M9 12h6M8 7a4 4 0 0 0 0 8M16 7a4 4 0 0 1 0 8\" /><path d=\"M3 3l18 18\" /></svg>\n </button>\n )}\n <button type=\"button\" className=\"xicms-ve-blokknop xicms-ve-blokknop--gevaar\" title=\"Blok verwijderen\" aria-label={`${b.label} verwijderen`}\n onMouseDown={(e) => e.preventDefault()} onClick={() => void remove(i)}>\n <svg viewBox=\"0 0 24 24\" width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\"><path d=\"M3 6h18M8 6V4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2M6 6l1 14a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2l1-14\" /></svg>\n </button>\n </span>\n )}\n </div>\n );\n })}\n\n {/* Potloodjes bij elk inline bewerkbaar veld van het geselecteerde blok. Ze staan fixed in de\n editor-laag (niet in de render van de klant) en zetten het bewerken bewust aan.\n onMouseDown-preventDefault: anders neemt de knop de focus en sluit het veld meteen weer. */}\n {spots.map((s, i) => (\n <button\n key={i}\n type=\"button\"\n className=\"xicms-ve-pencil\"\n style={{ top: s.top, left: s.left }}\n title={s.title}\n aria-label={s.title}\n onMouseDown={(e) => e.preventDefault()}\n onMouseEnter={() => targetsRef.current[i]?.el.classList.add(\"is-hover\")}\n onMouseLeave={() => targetsRef.current[i]?.el.classList.remove(\"is-hover\")}\n onClick={() => startInline(i)}\n >\n {/* Fijn getekend potlood i.p.v. een emoji: een emoji is per platform anders, kleurt niet\n mee en oogt op deze schaal als een vlek. Eén pad, currentColor, 1.4 lijndikte. */}\n <svg viewBox=\"0 0 24 24\" width=\"12\" height=\"12\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M4 20h4L19.5 8.5a2.1 2.1 0 0 0-3-3L5 17v3z\" />\n <path d=\"M14.5 6.5l3 3\" />\n </svg>\n </button>\n ))}\n\n {/* Hetzelfde knoppenpaar, maar dan voor een PRODUCT — alleen zichtbaar bij het product dat je\n aanwijst (zie hierboven waarom). Zelfde vorm en zelfde plek als bij een blok, want het is\n dezelfde handeling: potlood voor de tekst, tandwiel voor de rest. */}\n {productKnop && (\n <div\n className=\"xicms-ve-blokknoppen is-product\"\n style={{ top: productKnop.top, left: productKnop.left }}\n >\n <button\n type=\"button\"\n className=\"xicms-ve-blokknop\"\n title=\"Productnaam bewerken (staat meteen live)\"\n aria-label=\"Productnaam bewerken\"\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => startProductInline(productKnop.id)}\n >\n <svg viewBox=\"0 0 24 24\" width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M4 20h4L19.5 8.5a2.1 2.1 0 0 0-3-3L5 17v3z\" />\n <path d=\"M14.5 6.5l3 3\" />\n </svg>\n </button>\n <button\n type=\"button\"\n className={`xicms-ve-blokknop${selectedProduct === productKnop.id ? \" is-on\" : \"\"}`}\n title=\"Product bewerken: prijs, foto's, varianten, voorraad\"\n aria-label=\"Product bewerken\"\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => {\n setSelectedKey(null);\n setSelectedProduct(productKnop.id);\n setPanelOpen(true);\n }}\n >\n <svg viewBox=\"0 0 24 24\" width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"3\" />\n <path d=\"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.6a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z\" />\n </svg>\n </button>\n </div>\n )}\n\n {/*\n Publiceren staat rechtsonder, bij je werk — niet bovenin tussen de navigatie.\n Daar was het een knop die altijd even hard riep, ook als er niets te publiceren viel, en\n dus niets meer betekende. Nu zegt de knop zelf of er iets klaarstaat: grijs en uitgeschakeld\n zolang de pagina gelijk is aan wat er live staat, en pas gekleurd zodra dat niet meer zo is.\n Dat is dezelfde bron als de rode markering op de pagina, dus de knop en de pagina kunnen het\n nooit oneens zijn.\n */}\n {draft && (\n <button\n type=\"button\"\n className={`xicms-ve-publiceer${wijzigingen.iets ? \" is-klaar\" : \"\"}`}\n disabled={publishing || !wijzigingen.iets}\n onClick={() => void onPublish()}\n title={\n wijzigingen.iets\n ? \"Zet deze wijzigingen op de live site\"\n : \"Er is niets veranderd sinds de laatste publicatie\"\n }\n >\n <span className=\"xicms-ve-publiceer__label\">{publishing ? \"Bezig…\" : \"Publiceren\"}</span>\n <span className=\"xicms-ve-publiceer__sub\">\n {wijzigingen.iets ? aantalWijzigingen(wijzigingen) : \"niets gewijzigd\"}\n </span>\n </button>\n )}\n\n {/* Zwevende opmaak-toolbar boven het gefocuste inline rich-text-veld. onMouseDown-preventDefault\n houdt focus + selectie in het veld, zodat execCommand op de selectie werkt. */}\n {fmtBar && (\n <div className=\"xicms-ve-fmtbar\" style={{ top: fmtBar.top, left: fmtBar.left }} role=\"toolbar\" aria-label=\"Tekstopmaak\">\n <FmtBtn label={<b>B</b>} title=\"Vet (Ctrl/Cmd+B)\" onRun={() => document.execCommand(\"bold\")} />\n <FmtBtn label={<i>I</i>} title=\"Cursief (Ctrl/Cmd+I)\" onRun={() => document.execCommand(\"italic\")} />\n <FmtBtn label={<u>U</u>} title=\"Onderstrepen (Ctrl/Cmd+U)\" onRun={() => document.execCommand(\"underline\")} />\n <FmtBtn\n label=\"🔗\"\n title=\"Link (selecteer eerst tekst; leeg = link verwijderen)\"\n onRun={() => {\n const url = window.prompt(\"Link-URL (leeg = link verwijderen)\", \"https://\");\n if (url === null) return;\n if (url === \"\" || url === \"https://\") document.execCommand(\"unlink\");\n else document.execCommand(\"createLink\", false, url);\n }}\n />\n <span className=\"xicms-ve-fmtbar__sep\" aria-hidden />\n <FmtBtn label=\"H2\" title=\"Grote kop\" onRun={() => document.execCommand(\"formatBlock\", false, \"<h2>\")} />\n <FmtBtn label=\"H3\" title=\"Kleine kop\" onRun={() => document.execCommand(\"formatBlock\", false, \"<h3>\")} />\n <FmtBtn label=\"¶\" title=\"Normale tekst\" onRun={() => document.execCommand(\"formatBlock\", false, \"<p>\")} />\n <span className=\"xicms-ve-fmtbar__sep\" aria-hidden />\n <FmtBtn label=\"•\" title=\"Opsomming\" onRun={() => document.execCommand(\"insertUnorderedList\")} />\n </div>\n )}\n </div>\n );\n}\n\n/** Toolbar-knop die de selectie/focus in het contentEditable-veld intact laat. */\nfunction FmtBtn({ label, title, onRun }: { label: React.ReactNode; title: string; onRun: () => void }) {\n return (\n <button\n type=\"button\"\n title={title}\n aria-label={title}\n onMouseDown={(e) => e.preventDefault()}\n onClick={onRun}\n >\n {label}\n </button>\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/** Pagina-instellingen in vertaalmodus (5.0): titel + SEO als overlay-sleutels. */\nfunction PageTranslationSettings({\n draft,\n locale,\n onTranslate,\n}: {\n draft: DraftPage;\n locale: string;\n onTranslate: (key: string, value: string) => void;\n}) {\n const ov = draft.translations?.[locale] ?? {};\n return (\n <details className=\"xicms-ve__section\" open>\n <summary className=\"text-sm font-semibold\">Pagina-instellingen ({locale})</summary>\n <div className=\"mt-2 grid gap-3\">\n <Field label=\"Paginatitel\" hint={`Origineel: ${draft.title ?? \"\"}`}>\n <Input value={ov[\"title\"] ?? \"\"} placeholder={draft.title ?? \"\"} onChange={(e) => onTranslate(\"title\", e.target.value)} />\n </Field>\n <Field label=\"SEO-titel\">\n <Input value={ov[\"seo.title\"] ?? \"\"} placeholder={draft.seo?.title ?? \"\"} onChange={(e) => onTranslate(\"seo.title\", e.target.value)} />\n </Field>\n <Field label=\"SEO-omschrijving\">\n <Textarea rows={3} value={ov[\"seo.description\"] ?? \"\"} placeholder={draft.seo?.description ?? \"\"} onChange={(e) => onTranslate(\"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":["locale","overlay","t","inField"]}
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 { introspectToFields } from \"../lib/zodFields\";\nimport { BlockErrorBoundary } from \"../../blocks/BlockErrorBoundary\";\nimport { InlineImageEditProvider } from \"../../media/ImageContext\";\nimport { MediaPickerDialog } from \"../media/MediaPicker\";\nimport { EmptyState } from \"@astryxdesign/core/EmptyState\";\nimport { useFocusTrap, useScrollLock } from \"@astryxdesign/core/hooks\";\nimport { useNestedDialogOpen } from \"../lib/useNestedDialog\";\nimport { useThemeVars } from \"../../XiCMSProvider\";\nimport { buildBlockAddOptions } from \"../../blocks/registry\";\nimport { safeParse } from \"zod/v4/core\";\nimport DOMPurify from \"dompurify\";\nimport { createPage, getDraftByPath, getPublishedPage, saveDraft, publishPage, subscribeDraft, subscribeVersions, restoreVersion, type DraftPage } from \"../lib/pagesClient\";\nimport { isDemoActive } from \"../../demo/demoStore\";\nimport { setProductField } from \"../shop/productClient\";\nimport { ProductPaneel } from \"../shop/ProductPaneel\";\nimport { builtinBlockEditors, GenericEditor, type BlockEditorProps } from \"../blockEditors\";\nimport { StyleEditor } from \"../blockEditors/StyleEditor\";\nimport { siteLocales, parseLocaleSlugs, localizeBlocks } from \"../../i18n/overlay\";\nimport { useEditor } from \"../../editor/EditorProvider\";\nimport { AdminModes } from \"../../components/EditBar\";\nimport { useBalkOffset } from \"../../components/barOffset\";\nimport { aantalWijzigingen, bepaalWijzigingen, GEEN_WIJZIGINGEN, type Wijzigingen } from \"./wijzigingen\";\nimport { TranslationPanel } from \"./TranslationPanel\";\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 * Inline bewerken is sinds 5.0 volledig generiek: élk blok markeert zijn tekstvelden met\n * `data-xicms-edit=\"veldpad\"` (de builtin-Renders doen dat), plus de rich-text-haak\n * `.xicms-richtext`. De oude hardgecodeerde per-type selector-map (`INLINE_FIELDS`) is vervallen.\n * In development waarschuwt `warnInlineCoverage` wanneer een tekstveld uit het schema géén\n * DOM-haak heeft — zo blijven Renders eerlijk t.o.v. hun schema.\n */\nconst GLOBAL_INLINE = [\n // `field: \"html\"` is de AANNAME, geen wet: een element mag met `data-xicms-edit` zeggen welk veld\n // het werkelijk toont, en dat wint (zie de bind hieronder). Zonder die voorrang schreef élk\n // `.xicms-richtext`-element naar een veld dat letterlijk \"html\" heet — waar de FAQ zijn antwoord\n // in `items[].aHtml` bewaart en het accordeon in `items[].bodyHtml`. Inline bewerken van een\n // FAQ-antwoord landde daardoor op een niet-bestaand veld: de wijziging verdween stil en het blok\n // hield een zwerfveld over.\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/**\n * Dev-vangnet (5.0): meld tekstvelden zonder inline-haak — één keer per bloktype. Vooral voor\n * MAATWERKblokken in een klantrepo; de ingebouwde blokken worden statisch en volledig gedekt door\n * `npm run check:inline`.\n *\n * Een veld telt alleen als het zijn waarde ook echt als tekst op het scherm zet. Zonder die eis\n * riep dit elk blok na over lege velden (een niet-ingevulde `eyebrow` rendert niet, dus valt er\n * niets aan te klikken) en over configuratie die toevallig een tekstveld is (`overlayColor`).\n * Zulke loze meldingen leren je de waarschuwing wegkijken, en dan mist hij zijn doel.\n */\nconst inlineCoverageWarned = new Set<string>();\nfunction warnInlineCoverage(\n type: string,\n def: { schema: Parameters<typeof introspectToFields>[0]; fields?: Parameters<typeof introspectToFields>[1] } | undefined,\n host: HTMLElement,\n values: Record<string, unknown>,\n) {\n if (process.env.NODE_ENV === \"production\" || !def || inlineCoverageWarned.has(type)) return;\n inlineCoverageWarned.add(type);\n const zichtbaar = host.textContent ?? \"\";\n const missing = introspectToFields(def.schema, def.fields)\n .filter((f) => f.type === \"text\" || f.type === \"textarea\")\n .filter((f) => {\n const waarde = getPath(values, f.key);\n return typeof waarde === \"string\" && waarde.trim().length > 1 && zichtbaar.includes(waarde.trim());\n })\n .map((f) => f.key)\n .filter((key) => !host.querySelector(`[data-xicms-edit=\"${key}\"], [data-xicms-edit$=\".${key}\"]`));\n if (missing.length > 0) {\n console.warn(\n `[xicms] blok \"${type}\": tekstveld(en) zonder inline-haak: ${missing.join(\", \")} — voeg data-xicms-edit=\"<veld>\" toe aan de Render zodat ze direct op de pagina bewerkbaar zijn.`,\n );\n }\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 config = useSiteConfig();\n const { siteId, name } = config;\n const registry = useRegistry();\n const { toast } = useToast();\n const confirm = useConfirm();\n\n // Meertalig (5.0): een taalprefix in de URL (/de/over-ons) hoort niet bij het canonieke\n // pagina-pad — de editor opent dan de pagina zélf, met de betreffende taal actief.\n const localeCfg = useMemo(() => siteLocales(config), [config]);\n const { path, urlLocale } = useMemo(() => {\n const raw = typeof window !== \"undefined\" ? window.location.pathname || \"/\" : \"/\";\n const { locale, slugs } = parseLocaleSlugs(raw.split(\"/\").filter(Boolean), localeCfg);\n const p = \"/\" + slugs.join(\"/\");\n return { path: p === \"/\" ? \"/\" : p.replace(/\\/$/, \"\"), urlLocale: locale };\n }, [localeCfg]);\n const [locale, setLocale] = useState(urlLocale);\n const isTranslating = locale !== localeCfg.defaultLocale;\n const [draft, setDraft] = useState<DraftPage | null>(null);\n // Het zijpaneel stond altijd open en nam 360px van het canvas. Sinds élk tekstveld inline\n // bewerkbaar is, is dat paneel niet meer de hoofdweg maar het uitzonderingsgeval: de instellingen\n // die je niet op de pagina ziet (een link achter een knop, een kleur, een limiet). Het opent\n // daarom op verzoek — via het tandwiel bij het blok — en het canvas krijgt de volle breedte.\n const [panelOpen, setPanelOpen] = useState(false);\n const [status, setStatus] = useState<Status>(\"loading\");\n const [selectedKey, setSelectedKey] = useState<string | null>(null);\n /** Staat het zijpaneel op een PRODUCT in plaats van op een blok? Dan wint het product. */\n const [selectedProduct, setSelectedProduct] = 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 * De blokken zoals ze NU LIVE staan — de maatstaf voor \"wat heb ik veranderd\".\n *\n * `undefined` = nog niet gelezen (dan zeggen we niets, want een lege pagina rood kleuren omdat\n * de lezing nog loopt is erger dan even niets tonen). `null` = gelezen, maar deze pagina is nog\n * nooit gepubliceerd.\n */\n const [liveBlocks, setLiveBlocks] = useState<Block[] | null | undefined>(undefined);\n useBalkOffset(true);\n\n // ── Modale discipline (WAI-ARIA dialog pattern) ───────────────────────────\n // De shell draagt `role=\"dialog\" aria-modal=\"true\"` en belooft daarmee aan hulpsoftware dat\n // alles erbuiten onbereikbaar is. Zonder trap klopte die belofte niet: met Tab liep je zó de\n // onderliggende pagina in, terwijl de schermlezer meldde dat die er niet was.\n //\n // Geen `onEscape` meegeven: Escape wordt hieronder al afgehandeld met logica die de trap niet\n // kent (niet sluiten terwijl je in een invoerveld staat of er een dialoog openstaat).\n // Het canvas is een VOORBEELD van de live pagina en moet dus dezelfde merk-tokens dragen.\n // De editor rendert buiten `.xicms-root`, dus die erven we hier expliciet via context.\n const { vars: themeVars, darkAuto } = useThemeVars();\n const nestedDialogOpen = useNestedDialogOpen();\n const { containerRef, focusFirst } = useFocusTrap<HTMLDivElement>({ isActive: !nestedDialogOpen });\n useScrollLock(true);\n\n // Focus de eerste bediening bij openen, en geef hem bij sluiten terug aan het element dat de\n // editor opende (\"Pagina bewerken\"). Zonder dat teruggeven valt de focus terug naar <body> en\n // begint een toetsenbordgebruiker weer bovenaan de pagina.\n useEffect(() => {\n const opener = document.activeElement as HTMLElement | null;\n focusFirst();\n return () => opener?.focus?.();\n }, [focusFirst]);\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 // Dirty HIER markeren (synchroon met de mutatie), niet pas in het debounced autosave-effect:\n // een inline edit (blur) direct gevolgd door \"Sluiten\" in dezelfde gesture zou anders\n // flushSave() zien met dirtyRef=false → laatste wijziging verloren.\n dirtyRef.current = true;\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 dirtyRef.current = true; // undo is óók een wijziging die opgeslagen moet worden\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 dirtyRef.current = true; // redo is óók een wijziging die opgeslagen moet worden\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 // Alleen meesturen wanneer aanwezig: `undefined` weigert de Firestore-client.\n ...(d.translations ? { translations: d.translations } : {}),\n });\n\n // Schrijf eventuele pending wijziging direct weg (gebruikt bij Sluiten, tab-wissel en retry).\n const flushSave = useCallback(async () => {\n // Een veld dat pas op blur committeert (bv. het JSON-redmiddel in GenericEditor) moet die\n // commit eerst nog uitvoeren. Op desktop vuurt blur al vóór de click van deze knop, maar op\n // mobiel Safari is die volgorde niet gegarandeerd (het toetsenbord-sluiten kan blur vertragen\n // tot ná de klik) — expliciet blur() forceert de commit synchroon, ongeacht platform.\n (document.activeElement as HTMLElement | null)?.blur();\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(draftPayload(d));\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 /**\n * Lees erbij wat er live staat, zodat de markering en de publiceerknop ergens op slaan.\n *\n * Eén keer per pagina, niet als abonnement: de gepubliceerde versie verandert alleen doordat\n * JIJ publiceert, en dat moment wordt hieronder zelf afgehandeld. Een tweede live-listener zou\n * bij elke autosave-echo opnieuw willen vergelijken zonder dat er iets veranderd is.\n */\n useEffect(() => {\n const id = draft?.id;\n if (!id) {\n setLiveBlocks(undefined);\n return;\n }\n let active = true;\n getPublishedPage(siteId, id)\n .then((pagina) => {\n if (active) setLiveBlocks((pagina?.blocks as Block[] | undefined) ?? null);\n })\n // Mislukte lezing: dan liever géén markering dan een verkeerde. `undefined` houdt de\n // publiceerknop bereikbaar maar rustig, en niets kleurt rood.\n .catch(() => active && setLiveBlocks(undefined));\n return () => {\n active = false;\n };\n // Alleen op de identiteit van de pagina: de INHOUD van de werkversie verandert continu en mag\n // deze lezing niet opnieuw aanjagen.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [siteId, draft?.id]);\n\n /** Bootstrap vanuit de \"Geen werkversie\"-toestand: maak de pagina hier direct aan (geen omweg\n * via /beheer → Pagina's — dat was een doodloop voor niet-technische klanten). */\n const handleCreatePage = useCallback(async () => {\n setStatus(\"loading\");\n try {\n const segment = path.split(\"/\").filter(Boolean).pop() ?? \"\";\n const title = segment ? segment.replace(/[-_]+/g, \" \").replace(/^\\w/, (c) => c.toUpperCase()) : \"Home\";\n await createPage(siteId, { title, slug: segment || \"home\", path });\n const d = await getDraftByPath(siteId, path);\n if (d) {\n setDraft(d);\n lastSavedRef.current = stableStringify(draftPayload(d));\n setStatus(\"idle\");\n toast({ title: \"Pagina aangemaakt\", description: \"Voeg je eerste blok toe en publiceer wanneer je klaar bent.\", tone: \"success\" });\n } else {\n setStatus(\"none\");\n }\n } catch (e) {\n setStatus(\"none\");\n toast({ title: \"Pagina aanmaken mislukt\", description: (e as Error)?.message, tone: \"error\" });\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [siteId, path, toast]);\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(draftPayload(remote));\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 // Via een ref, zodat het bind-effect hieronder NIET op elke draft-wijziging opnieuw hoeft te\n // draaien. Deed het dat wel, dan brak een autosave van de vorige bewerking de bewerking af die\n // je net was begonnen: de cleanup haalde contentEditable weg en je toetsaanslagen verdwenen.\n const blocksRef = useRef(blocks);\n blocksRef.current = blocks;\n // Canvas in een niet-default taal: toon de vertaal-overlay over de canonieke blokken.\n const overlay = draft?.translations?.[locale];\n const displayBlocks = useMemo(\n () => (isTranslating ? (localizeBlocks(blocks, overlay) as Block[]) : blocks),\n [blocks, isTranslating, overlay],\n );\n\n /**\n * Wat staat er nog niet live? Hierop leunen de rode markering én de knop rechtsonder.\n *\n * Bewust op `blocks` en niet op `displayBlocks`: in vertaalmodus is `displayBlocks` de\n * samengevoegde weergave (standaardtaal + overlay), en die zou élk vertaald veld als wijziging\n * aanwijzen. Publiceren gaat over de bron, dus vergelijken we de bron.\n */\n /** Voor de waarschuwing bij wegnavigeren: die luistert buiten React om en heeft een ref nodig. */\n const nietLiveRef = useRef(false);\n const wijzigingen: Wijzigingen = useMemo(\n () => (liveBlocks === undefined ? GEEN_WIJZIGINGEN : bepaalWijzigingen(blocks, liveBlocks)),\n [blocks, liveBlocks],\n );\n nietLiveRef.current = wijzigingen.iets;\n\n /**\n * Duw de werkversie naar de PAGINA. Dit is de kern van het nieuwe bewerken: de site rendert\n * zichzelf — eigen layout, eigen huisstijl, eigen componenten — maar met deze blokken in plaats\n * van de gepubliceerde. Daardoor is wat je ziet precies wat je bewerkt, zonder dat de editor de\n * pagina hoeft na te bouwen.\n *\n * `displayBlocks` en niet `blocks`: bewerk je een vertaling, dan hoort de pagina die vertaling te\n * tonen en niet het origineel.\n */\n const { publishDraft, zetFotoVervanger } = useEditor();\n\n /**\n * De 📷-knopjes op de pagina aansluiten op de mediakiezer van deze editor.\n *\n * Dit herstelt een stille regressie: `InlineImageEditProvider` werd sinds het verdwijnen van het\n * canvas nergens meer gerenderd, dus geen enkel beeld toonde nog \"Foto wijzigen\" — de import\n * stond er nog, de dialoog stond er nog, alleen de draad ertussen was weg. Een klant kon\n * daardoor geen foto vervangen zonder naar /beheer te gaan.\n */\n useEffect(() => {\n zetFotoVervanger((field, blockKey) => {\n if (blockKey) setImgPicker({ key: blockKey, field });\n });\n return () => zetFotoVervanger(null);\n }, [zetFotoVervanger]);\n const laatstGepubliceerd = useRef<string>(\"\");\n useEffect(() => {\n if (!draft) return;\n // ALLEEN bij een echte inhoudelijke wijziging. Dit effect duwt state naar een context waar de\n // hele pagina op meeleest — en dus ook deze editor. Duw je bij elke render, dan veroorzaakt de\n // render een update en de update een render: \"Maximum update depth exceeded\". Een vergelijking\n // op reference is niet genoeg, want `displayBlocks` kan een nieuwe array zijn met dezelfde\n // inhoud; vandaar een stabiele vergelijking op de inhoud zelf.\n const vinger = stableStringify(displayBlocks);\n if (vinger === laatstGepubliceerd.current) return;\n laatstGepubliceerd.current = vinger;\n publishDraft(path, displayBlocks);\n }, [draft, displayBlocks, path, publishDraft]);\n\n // Schrijf een inline (in-canvas) tekstwijziging terug naar het juiste blokveld (canoniek).\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 // Schrijf een vertaling naar de taal-overlay; leeg = sleutel weg (terugvallen op origineel).\n const setTranslation = useCallback(\n (tkey: string, value: string) => {\n mutateDraft(\n (d) => {\n const overlay = { ...(d.translations?.[locale] ?? {}) };\n if (value.trim() === \"\" || value === undefined) delete overlay[tkey];\n else overlay[tkey] = value;\n return { ...d, translations: { ...(d.translations ?? {}), [locale]: overlay } };\n },\n { coalesce: true },\n );\n },\n [mutateDraft, locale],\n );\n\n // ── Inline opmaak-toolbar (WYSIWYG op het canvas) ─────────────────────────\n // Zwevende mini-toolbar boven een gefocust rich-text-veld: vet/schuin/onderstrepen/link +\n // groter-kleiner via kop-niveaus (H2/H3/normaal — semantisch i.p.v. losse font-sizes).\n // document.execCommand is deprecated-maar-universeel en precies goed voor contentEditable;\n // de output gaat bij commit door DOMPurify én server-side door de allowlist-sanitizer.\n const [fmtBar, setFmtBar] = useState<{ top: number; left: number } | null>(null);\n const fmtElRef = useRef<HTMLElement | null>(null);\n const positionFmtBar = useCallback((el: HTMLElement) => {\n const r = el.getBoundingClientRect();\n setFmtBar({\n top: Math.max(56, r.top - 44),\n left: Math.min(Math.max(8, r.left), Math.max(8, window.innerWidth - 300)),\n });\n }, []);\n useEffect(() => {\n if (!fmtBar) return;\n const onScroll = () => {\n const el = fmtElRef.current;\n if (el) positionFmtBar(el);\n };\n window.addEventListener(\"scroll\", onScroll, { capture: true, passive: true });\n return () => window.removeEventListener(\"scroll\", onScroll, { capture: true });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [fmtBar !== null, positionFmtBar]);\n\n // Inline canvas-edits: in de default-taal naar het blokveld, in een andere taal naar de overlay.\n const commitText = useCallback(\n (key: string, field: string, value: string) => {\n if (isTranslating) setTranslation(`${key}.${field}`, value);\n else commitInline(key, field, value);\n },\n [isTranslating, setTranslation, commitInline],\n );\n\n // ── Inline bewerken: eerst het potlood, dan pas de tekst ──────────────────\n // Tot 7.3 werd élk tekstveld van het geselecteerde blok meteen contentEditable. Dat leest als\n // \"alles staat open\": één misklik in een kop en je typt al in de pagina, met autosave eronder.\n // Nu markeert de editor de bewerkbare velden alleen (een subtiele stippellijn) en zet een\n // potloodje ernaast de bewerking BEWUST aan — pas daarna verschijnen caret en opmaak-toolbar.\n // Dat maakt bovendien zichtbaar wát inline kan; dat wist je voorheen pas als je erop klikte.\n /**\n * Tandwielen naast elk blok OP DE PAGINA.\n *\n * Dit zat vroeger in de werkbalkjes van het canvas. Nu er geen canvas meer is, moeten ze over de\n * echte blokken heen — dezelfde aanpak als de potloodjes: vast gepositioneerd in de editorlaag,\n * en opnieuw meten zodra er gescrold of geschaald wordt. De pagina zelf wordt niet aangeraakt,\n * want dat is de site van de klant.\n */\n // Via een ref: de registry is een context-waarde en mag de meting niet opnieuw aanjagen.\n // Via een ref: de registry is een context-waarde en mag de meting niet opnieuw aanjagen.\n const registryRef = useRef(registry);\n registryRef.current = registry;\n\n /**\n * Hoe ver van bovenaf is het veilig om iets neer te zetten?\n *\n * De knoppen van het bovenste blok landden onder de balk van de editor én onder de plakkende\n * menubalk van de site zelf. Ze wáren er wel — ze lagen alleen onder iets anders, en dus kon je\n * het hoofdblok als enige blok niet bewerken of instellen. Dat was geen randgeval: het eerste\n * blok van de homepagina is meestal juist het blok dat je wilt aanpassen.\n *\n * We meten daarom wat er werkelijk bovenaan vastgeplakt zit — de eigen balk plus elk plakkend of\n * vastgezet element van de site dat de bovenrand raakt — in plaats van een vast getal te kiezen\n * dat op de ene site klopt en op de andere niet.\n */\n function veiligeBovenkant(): number {\n let onderkant = 0;\n for (const el of Array.from(document.querySelectorAll<HTMLElement>(\"body *\"))) {\n const pos = getComputedStyle(el).position;\n if (pos !== \"fixed\" && pos !== \"sticky\") continue;\n const r = el.getBoundingClientRect();\n // Alleen wat écht bovenaan hangt telt mee; een plakkende knop onderin de pagina niet.\n if (r.height === 0 || r.top > 4) continue;\n onderkant = Math.max(onderkant, r.bottom);\n }\n return onderkant;\n }\n\n /**\n * Potlood én tandwiel bij ELK blok.\n *\n * Tot 7.8 stond er per blok alleen een tandwiel, en verschenen de potloden pas nádat je een blok\n * had geselecteerd — via datzelfde tandwiel. Inline bewerken, de hoofdweg van dit hele systeem,\n * zat dus achter de knop voor het uitzonderingsgeval. Nu staan beide er meteen: het potlood zet\n * de tekst van dat blok open, het tandwiel opent alles wat je niet kunt typen.\n */\n const [blokknoppen, setBlokknoppen] = useState<{ key: string; type: string; top: number; right: number; label: string }[]>([]);\n useEffect(() => {\n if (!draft) {\n // `setBlokknoppen([])` zou hier een NIEUWE lege array zetten bij elke render — en dus elke\n // render een update, en elke update een render: \"Maximum update depth exceeded\". De\n // functionele vorm geeft de bestaande array terug als er toch al niets stond.\n setBlokknoppen((v) => (v.length === 0 ? v : []));\n return;\n }\n /**\n * De veilige bovenkant is duur om te bepalen (hij loopt de hele DOM af op zoek naar plakkende\n * elementen) en verandert alleen bij een resize of een structurele wijziging — niet bij\n * scrollen. `meet()` draait wél bij elke scroll-tick; die twee horen dus niet in één adem.\n * Gemeten winst: van O(alle elementen) naar O(blokken) per scroll-frame.\n */\n let veilig = veiligeBovenkant();\n const herijk = () => {\n veilig = veiligeBovenkant();\n meet();\n };\n const meet = () => {\n const gevonden: { key: string; type: string; top: number; right: number; label: string }[] = [];\n for (const el of Array.from(document.querySelectorAll<HTMLElement>(\"[data-xicms-block]\"))) {\n const key = el.dataset.xicmsBlock;\n if (!key) continue;\n const r = el.getBoundingClientRect();\n // Blokken zonder afmeting (nog niet zichtbaar, of `display:contents`) leveren knoppen in de\n // linkerbovenhoek op — dat is geen aanwijzing maar ruis.\n if (r.width === 0 && r.height === 0) continue;\n // Volledig uit beeld gescrolde blokken hoeven geen knoppen: die zouden vastgeklemd worden\n // aan de boven- of onderrand en daar blijven zweven bij een blok dat je niet ziet.\n if (r.bottom < veilig || r.top > window.innerHeight) continue;\n const type = el.dataset.xicmsBlockType ?? \"\";\n gevonden.push({\n key,\n type,\n top: Math.max(r.top + 8, veilig + 8),\n // Vanaf de RECHTERkant verankerd: het palet klapt uit en moet die ruimte naar LINKS\n // pakken — een full-width blok eindigt op de viewportrand, rechts is daar per definitie\n // geen plek. Met een left-anker schoof het uitgeklapte deel het scherm af en leek de\n // hele uitbreiding gewoon niet te bestaan.\n right: Math.max(window.innerWidth - r.right + 8, 8),\n label: registryRef.current.get(type)?.label ?? type,\n });\n }\n setBlokknoppen((vorige) => {\n // Ongewijzigde posities niet terugschrijven: elke setState is een render, en een render\n // meet opnieuw. Zonder deze vergelijking draait dat rond.\n if (vorige.length === gevonden.length && vorige.every((v, i) =>\n v.key === gevonden[i].key && v.top === gevonden[i].top && v.right === gevonden[i].right)) {\n return vorige;\n }\n return gevonden;\n });\n };\n // rAF-bundeling: scroll- en resize-events komen in salvo's; meten hoeft hoogstens één keer\n // per frame. Zonder dit liep de meting tijdens het scrollen vaker dan het scherm ververst.\n let frame = 0;\n const meetGepland = () => {\n cancelAnimationFrame(frame);\n frame = requestAnimationFrame(meet);\n };\n meet();\n window.addEventListener(\"scroll\", meetGepland, { capture: true, passive: true });\n window.addEventListener(\"resize\", herijk);\n return () => {\n cancelAnimationFrame(frame);\n window.removeEventListener(\"scroll\", meetGepland, { capture: true });\n window.removeEventListener(\"resize\", herijk);\n };\n }, [draft, displayBlocks, registryRef]);\n\n /**\n * Producten krijgen hun eigen potlood en tandwiel, maar alleen waar je muis is.\n *\n * Een cataloguspagina toont er twaalf tegelijk. Twaalf vaste knoppenparen naast twaalf kaarten is\n * geen gereedschap meer maar een raster van knoppen over de site heen — dan kijk je niet meer naar\n * je winkel. Op aanwijzen verschijnt er precies één paar, bij het product waar je naar kijkt.\n */\n const [productKnop, setProductKnop] = useState<{ id: string; top: number; right: number } | null>(null);\n useEffect(() => {\n if (!draft) return;\n const onOver = (e: MouseEvent) => {\n const doel = (e.target as HTMLElement | null)?.closest?.(\"[data-xicms-product]\") as HTMLElement | null;\n if (!doel) {\n // Niet meteen weghalen zodra je de kaart verlaat: je verlaat hem juist om NAAR de knoppen te\n // gaan. De knoppen zelf zitten niet in de kaart, dus zonder deze uitzondering verdwijnen ze\n // op weg ernaartoe en is er niets aan te klikken.\n const naarKnop = (e.target as HTMLElement | null)?.closest?.(\".xicms-ve-blokknoppen\");\n if (!naarKnop) setProductKnop(null);\n return;\n }\n const id = doel.dataset.xicmsProduct;\n if (!id) return;\n const r = doel.getBoundingClientRect();\n const top = Math.max(r.top + 8, 44);\n const right = Math.max(window.innerWidth - r.right + 8, 8);\n setProductKnop((v) => (v && v.id === id && v.top === top && v.right === right ? v : { id, top, right }));\n };\n document.addEventListener(\"mouseover\", onOver, true);\n return () => document.removeEventListener(\"mouseover\", onOver, true);\n }, [draft]);\n\n /**\n * Inline bewerken van een PRODUCT — een ander pad dan een blokveld, met opzet.\n *\n * Een blokveld gaat naar de werkversie van deze pagina en wordt zichtbaar bij publiceren. Een\n * product staat in de productencollectie en wordt door élke pagina gelezen die het toont; daar\n * bestaat geen werkversie voor. Zie `productClient.ts` voor waarom dat zo blijft.\n */\n const productBewerkRef = useRef<HTMLElement | null>(null);\n const startProductInline = useCallback(\n (productId: string) => {\n const host = document.querySelector<HTMLElement>(`[data-xicms-product=\"${CSS.escape(productId)}\"]`);\n const el = host?.querySelector<HTMLElement>(\"[data-xicms-product-edit]\");\n if (!el) {\n toast({\n title: \"Hier valt niets te typen\",\n description: \"Gebruik het tandwiel voor prijs, foto's, varianten en voorraad.\",\n });\n return;\n }\n const veld = el.dataset.xicmsProductEdit;\n if (!veld) return;\n const origineel = el.textContent ?? \"\";\n el.setAttribute(\"contenteditable\", \"true\");\n el.setAttribute(\"spellcheck\", \"true\");\n el.classList.add(\"is-editing\");\n productBewerkRef.current = el;\n\n const stop = () => {\n el.removeAttribute(\"contenteditable\");\n el.classList.remove(\"is-editing\");\n el.removeEventListener(\"blur\", opBlur);\n el.removeEventListener(\"keydown\", opToets);\n if (productBewerkRef.current === el) productBewerkRef.current = null;\n };\n const opBlur = () => {\n const waarde = el.textContent ?? \"\";\n stop();\n if (waarde === origineel) return;\n void setProductField(siteId, productId, veld, waarde)\n .then(() =>\n toast({\n title: \"Product bijgewerkt\",\n description: \"Producten hebben geen werkversie: dit staat meteen live, op elke pagina.\",\n tone: \"success\",\n }),\n )\n .catch((err: Error) => {\n // Terugdraaien op het scherm, anders blijft er tekst staan die nergens bewaard is.\n el.textContent = origineel;\n toast({ title: \"Opslaan mislukt\", description: err.message, tone: \"error\" });\n });\n };\n const opToets = (ev: KeyboardEvent) => {\n if (ev.key === \"Escape\") {\n ev.stopPropagation();\n el.textContent = origineel;\n el.blur();\n } else if (ev.key === \"Enter\") {\n ev.preventDefault();\n el.blur();\n }\n };\n el.addEventListener(\"blur\", opBlur);\n el.addEventListener(\"keydown\", opToets);\n el.focus();\n const selection = window.getSelection();\n const range = document.createRange();\n range.selectNodeContents(el);\n range.collapse(false);\n selection?.removeAllRanges();\n selection?.addRange(range);\n },\n [siteId, toast],\n );\n\n /**\n * Rood en onderstreept: dit staat nog niet op de live site.\n *\n * De vraag die dit beantwoordt is \"wat heb ik nou eigenlijk veranderd?\" — en die was tot 7.8\n * onbeantwoordbaar. Je zag je eigen tekst staan en kon niet zien of een bezoeker hem al had.\n * Na publiceren wordt de markering vanzelf schoon, want dan is de werkversie gelijk aan wat er\n * live staat en levert `bepaalWijzigingen` niets meer op. De kleur is dus geen opmaak maar een\n * toestand: rood = van jou, gewone kleur = van iedereen.\n *\n * De klassen gaan op de LIVE DOM, net als de potloden — de render van de klant blijft schoon en\n * de markering verdwijnt met het gereedschap mee.\n */\n useEffect(() => {\n if (!draft) return;\n const gemarkeerd: HTMLElement[] = [];\n const markeer = (el: HTMLElement | null, klasse: string) => {\n if (!el) return;\n el.classList.add(klasse);\n gemarkeerd.push(el);\n };\n\n for (const key of wijzigingen.blokken) {\n markeer(document.querySelector<HTMLElement>(`[data-xicms-block=\"${CSS.escape(key)}\"]`), \"xicms-ve-nieuw\");\n }\n for (const [key, velden] of wijzigingen.velden) {\n const host = document.querySelector<HTMLElement>(`[data-xicms-block=\"${CSS.escape(key)}\"]`);\n if (!host) continue;\n for (const veld of velden) {\n // Het veldpad is dezelfde puntnotatie die het blok in zijn DOM-haak zet, dus dit is een\n // rechtstreekse treffer. Een veld dat wel wijzigde maar niets op het scherm zet (een kleur,\n // een limiet) heeft geen haak en levert hier gewoon niets op — terecht: er valt niets te\n // markeren aan iets wat je niet ziet. Dát er iets veranderde blijkt uit de knop rechtsonder.\n host\n .querySelectorAll<HTMLElement>(`[data-xicms-edit=\"${CSS.escape(veld)}\"]`)\n .forEach((el) => markeer(el, \"xicms-ve-gewijzigd\"));\n }\n }\n\n return () => {\n for (const el of gemarkeerd) {\n el.classList.remove(\"xicms-ve-nieuw\", \"xicms-ve-gewijzigd\");\n }\n };\n // `displayBlocks` staat erbij omdat de DOM-nodes na een re-render vervangen kunnen zijn: dan\n // moet er opnieuw gemarkeerd worden, ook al is de verzameling wijzigingen dezelfde.\n }, [draft, wijzigingen, displayBlocks]);\n\n const [spots, setSpots] = useState<{ top: number; left: number; title: string }[]>([]);\n const targetsRef = useRef<{ el: HTMLElement; field: string; html: boolean }[]>([]);\n const editingRef = useRef<HTMLElement | null>(null);\n /** Gezet door het potlood bij een blok: na het binden meteen in het eerste veld staan. */\n const wilInlineRef = useRef(false);\n\n const stopInline = useCallback((el: HTMLElement) => {\n el.removeAttribute(\"contenteditable\");\n el.classList.remove(\"is-editing\");\n if (editingRef.current === el) editingRef.current = null;\n }, []);\n\n const startInline = useCallback((index: number) => {\n const target = targetsRef.current[index];\n if (!target) return;\n const { el } = target;\n if (editingRef.current && editingRef.current !== el) editingRef.current.blur();\n el.setAttribute(\"contenteditable\", \"true\");\n el.setAttribute(\"spellcheck\", \"true\");\n el.classList.add(\"is-editing\");\n editingRef.current = el;\n el.focus();\n // Caret aan het eind, geen selectie over de hele tekst: bewerken is meestal bijschaven, en een\n // volledige selectie betekent dat de eerste toetsaanslag alles wist.\n const selection = window.getSelection();\n const range = document.createRange();\n range.selectNodeContents(el);\n range.collapse(false);\n selection?.removeAllRanges();\n selection?.addRange(range);\n }, []);\n\n useEffect(() => {\n if (!selectedKey) {\n targetsRef.current = [];\n setSpots([]);\n return;\n }\n // Het blok staat op de LIVE pagina, niet in een canvas van de editor. `BlockRenderer` zet\n // `data-xicms-block` op elk blok — op de gepubliceerde pagina net zo goed als hier — dus dat is\n // de haak. Vandaar ook `document` en niet een ref: de pagina is niet van de editor.\n const host = document.querySelector<HTMLElement>(`[data-xicms-block=\"${selectedKey}\"]`);\n if (!host) return;\n const geselecteerd = blocksRef.current.find((b) => b._key === selectedKey);\n const type = geselecteerd?._type ?? \"\";\n const def = registry.get(String(type));\n warnInlineCoverage(String(type), def, host, geselecteerd ?? {});\n\n // Leesbare veldnamen voor de tooltip van het potlood (\"Bewerk: Ondertitel\"), uit dezelfde\n // schema-beschrijvingen die het zijpaneel gebruikt — zodat beide paden hetzelfde heten.\n const labels = new Map<string, string>();\n if (def) {\n try {\n for (const f of introspectToFields(def.schema, def.fields)) labels.set(f.key, f.label);\n } catch {\n /* een blok zonder introspecteerbaar schema krijgt gewoon de veldnaam */\n }\n }\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.classList.add(\"xicms-ve-inline\");\n el.addEventListener(\"blur\", () => {\n // Inline HTML wordt vóór commit gesanitized (DOMPurify) — zelfde belofte als het TipTap-pad;\n // de server sanitized nogmaals bij publiceren. Geplakte Word-/Docs-rommel blijft zo buiten.\n commitText(selectedKey, field, html ? DOMPurify.sanitize(el.innerHTML) : (el.textContent ?? \"\"));\n stopInline(el);\n });\n if (html) {\n // Opmaak-toolbar tonen zolang dit rich-text-veld focus heeft.\n el.addEventListener(\"focus\", () => {\n fmtElRef.current = el;\n positionFmtBar(el);\n });\n el.addEventListener(\"blur\", () => {\n fmtElRef.current = null;\n setFmtBar(null);\n });\n }\n el.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Escape\") {\n // NIET laten doorborrelen. De editor luistert zelf ook op Escape om te sluiten, en die\n // controle (\"sta ik in een invoerveld?\") kijkt naar het element dat focus HEEFT. Na\n // `blur()` is dat de body, dus concludeerde hij dat je niet aan het typen was en sloot\n // hij de hele bewerkmodus. Eén Escape hoorde het veld te sluiten, niet de werkplek.\n e.stopPropagation();\n el.blur();\n } else if (!html && e.key === \"Enter\") { e.preventDefault(); el.blur(); }\n });\n bound.add(el);\n };\n\n /** De potloodjes staan in de editor-laag, niet in het blok: de render van de klant blijft schoon. */\n const measure = () => {\n const next = targetsRef.current.map(({ el, field }) => {\n const r = el.getBoundingClientRect();\n // Naast het veld, op ooghoogte van de tekst: rechts ervan, verticaal gecentreerd. Niet\n // links (dan staat het potlood van de tweede knop bovenop de eerste) en niet ín de hoek\n // (dan ligt het over de tekst). Aanwijzen laat het veld oplichten, dus wélk veld het is\n // blijft ook bij twee elementen naast elkaar duidelijk.\n return {\n top: Math.min(window.innerHeight - 22, Math.max(4, r.top + r.height / 2 - 9)),\n left: Math.min(window.innerWidth - 22, Math.max(4, r.right + 6)),\n title: `Bewerk: ${labels.get(field) ?? field}`,\n };\n });\n setSpots((prev) =>\n prev.length === next.length && prev.every((p, i) => p.top === next[i].top && p.left === next[i].left && p.title === next[i].title)\n ? prev\n : next,\n );\n };\n\n const apply = () => {\n const found: { el: HTMLElement; field: string; html: boolean }[] = [];\n for (const m of GLOBAL_INLINE) {\n host.querySelectorAll<HTMLElement>(m.selector).forEach((el) => {\n // Een expliciete `data-xicms-edit` wint altijd; anders het veld van de matcher.\n const field = el.dataset.xicmsEdit ?? m.field;\n bind(el, field, !!m.html);\n found.push({ el, field, html: !!m.html });\n });\n }\n targetsRef.current = found;\n measure();\n };\n\n apply();\n if (wilInlineRef.current) {\n wilInlineRef.current = false;\n // Alleen als er iets te bewerken is. Een blok zonder inline-velden (een productraster, een\n // winkelwagen) mag geen caret ergens neerzetten; daar is het tandwiel de goede weg en dat\n // zegt het blok zelf door geen enkele haak aan te bieden.\n if (targetsRef.current.length > 0) startInline(0);\n // Stil niets doen is hier het slechtste antwoord: dan lijkt de knop kapot. Zeg waar het wél\n // kan — bij een productraster staat de tekst in de producten, niet in het blok.\n else {\n toast({\n title: \"Dit blok heeft geen eigen tekst\",\n description: \"Wijs een product aan voor zijn naam, of gebruik het tandwiel voor de instellingen van dit blok.\",\n });\n }\n }\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 // Het canvas scrollt onder de potloodjes door; ze staan fixed, dus hermeten bij elke beweging.\n let frame = 0;\n const onMove = () => {\n cancelAnimationFrame(frame);\n frame = requestAnimationFrame(measure);\n };\n window.addEventListener(\"scroll\", onMove, { capture: true, passive: true });\n window.addEventListener(\"resize\", onMove, { passive: true });\n return () => {\n obs.disconnect();\n cancelAnimationFrame(frame);\n window.removeEventListener(\"scroll\", onMove, { capture: true });\n window.removeEventListener(\"resize\", onMove);\n bound.forEach((el) => {\n stopInline(el);\n el.classList.remove(\"xicms-ve-inline\");\n delete el.dataset.veInline;\n });\n targetsRef.current = [];\n setSpots([]);\n };\n // Alleen `selectedKey`: dit effect bindt de velden van hét geselecteerde blok. `blocks` en\n // `registry` lezen we uit een ref/context — draaide dit effect op elke draft-wijziging, dan\n // sloopte een autosave de lopende bewerking. Nieuwe DOM na een re-render wordt opgevangen door\n // de MutationObserver hieronder, niet door een her-run van het effect.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [selectedKey, commitText, positionFmtBar, stopInline, startInline, toast]);\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 // Bij een openstaand conflict PAUZEREN we autosave: de banner wegklikken en doorwerken\n // overschreef anders stil de wijzigingen van de collega/andere tab (destructief-by-default).\n // De klant kiest eerst expliciet \"Nieuwste laden\" of \"Mijn versie behouden\".\n if (conflict) return;\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, conflict]);\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 // Twee redenen om te waarschuwen, en ze zijn niet hetzelfde:\n // · `dirtyRef` — de laatste toetsaanslagen staan nog niet in de werkversie (autosave loopt).\n // · `nietLiveRef` — er is wél opgeslagen, maar niets van dit alles staat op de live site.\n // Tot 7.8 waarschuwde alleen de eerste. Wie rustig had zitten typen kreeg dus géén melding,\n // want alles was netjes bewaard — terwijl er nog steeds niets gepubliceerd was. Precies de\n // situatie waarin je denkt dat je klaar bent.\n if (dirtyRef.current || nietLiveRef.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 // Zelfde herkenning als useNestedDialog: Astryx' dialoog is een natieve <dialog open>.\n const dialogOpen = document.querySelector('dialog[open], [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 /** Symbool loskoppelen (5.0): vervang de referentie door een kloon van de symboolblokken —\n * daarna beweegt dit exemplaar niet meer mee met het gedeelde symbool. */\n async function detachSymbol(i: number) {\n const blk = blocks[i] as { _type?: string; symbolId?: string; _key: string };\n if (blk._type !== \"symbolRef\" || !blk.symbolId) return;\n const ok = await confirm({\n title: \"Symbool loskoppelen?\",\n description: \"Dit blok wordt een losse kopie en beweegt niet meer mee met het gedeelde symbool.\",\n confirmLabel: \"Loskoppelen\",\n });\n if (!ok) return;\n const { getSymbolOnce } = await import(\"../lib/symbolsClient\");\n const sym = await getSymbolOnce(siteId, blk.symbolId);\n if (!sym) {\n toast({ title: \"Symbool niet gevonden\", tone: \"error\" });\n return;\n }\n const clones = sym.blocks.map((b) => ({ ...(b as Record<string, unknown>), _key: newKey() })) as Block[];\n const copy = [...blocks];\n copy.splice(i, 1, ...clones);\n setBlocks(copy);\n if (selectedKey === blk._key) setSelectedKey(null);\n toast({ title: \"Losgekoppeld\", description: \"Dit is nu een losse kopie.\", tone: \"success\" });\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 // Zie flushSave: een veld dat pas op blur committeert (JSON-redmiddel) moet die commit eerst\n // nog doen — anders publiceert dit de stand van vóór de laatste bewerking.\n (document.activeElement as HTMLElement | null)?.blur();\n const d = draftRef.current;\n if (!d) return;\n // Openstaand conflict eerst oplossen: publiceren zou anders een stale merge live zetten.\n if (conflict) {\n toast({ title: \"Los eerst het conflict op\", description: \"Kies 'Nieuwste versie laden' of 'Mijn versie behouden' in de gele balk.\", tone: \"error\" });\n return;\n }\n // In een demo gaat er niets naar een live site; dat beloven zou liegen tegen precies de\n // bezoeker die we aan het overtuigen zijn. Zelfde knop, eerlijke woorden.\n const demo = isDemoActive();\n const ok = await confirm({\n title: \"Pagina publiceren?\",\n description: demo\n ? \"In deze demo blijft alles in je eigen browser — er gaat niets echt online.\"\n : `De live site wordt bijgewerkt (${d.path}).`,\n confirmLabel: \"Publiceren\",\n });\n if (!ok) return;\n setPublishing(true);\n try {\n await saveDraft(siteId, d.id, draftPayload(d));\n await publishPage(siteId, d.id);\n /**\n * De maatstaf schuift mee: wat zojuist gepubliceerd is, IS nu de live versie.\n *\n * Zonder deze regel bleef `liveBlocks` op de vorige publicatie staan — en dus bleef élke\n * markering rood en de knop blauw (\"14 wijzigingen\") terwijl alles allang live stond. De\n * hele belofte van de knop (\"grijs = er is niets te publiceren\") brak precies op het moment\n * dat hij zijn werk had gedaan. Lokaal bijschuiven is hier ook juister dan opnieuw lezen:\n * wat de server zojuist heeft weggeschreven is per definitie wat we net stuurden.\n */\n setLiveBlocks(structuredClone(d.blocks) as Block[]);\n toast({\n title: \"Gepubliceerd\",\n description: demo\n ? \"In deze demo blijft de wijziging in je browser staan.\"\n : \"Zelf zie je de nieuwe tekst meteen. Voor overige bezoekers duurt het nog ongeveer 15 minuten.\",\n tone: \"success\",\n });\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 ref={containerRef} className=\"xicms-ve__shell\" data-xicms-tooling=\"\" role=\"group\" aria-label={`Pagina bewerken: ${path}`}>\n {/* Dezelfde balk als op de live site (`EditBar`), met hetzelfde `AdminModes` erin — alleen\n staat \"Bewerken\" nu ingedrukt en hangt het gereedschap ernaast. Zo verspringt er niets\n op het moment dat je overschakelt: de knoppen blijven waar je ze net nog zag.\n Publiceren staat hier BEWUST niet meer; dat is de zwevende knop rechtsonder, bij je werk,\n en niet weggestopt tussen de navigatie. */}\n <header className=\"xicms-ve__bar\" role=\"toolbar\" aria-label=\"Beheer\">\n <span className=\"xicms-editbar__brand\">{isDemoActive() ? \"XiCMS · demo\" : \"XiCMS\"}</span>\n <AdminModes\n actief=\"bewerken\"\n voorSwitch={(m) => {\n if (m === \"live\" && nietLiveRef.current) {\n const n = aantalWijzigingen(wijzigingen);\n toast({\n title: \"Nog niet gepubliceerd\",\n // Enkelvoud/meervoud netjes: \"1 wijziging blijft\", \"14 wijzigingen blijven\".\n description: `${n} ${n.startsWith(\"1 \") ? \"blijft\" : \"blijven\"} bewaard als werkversie. Je ziet ze terug zodra je weer naar Bewerken gaat.`,\n });\n }\n }}\n />\n <span className=\"xicms-ve__sep\" aria-hidden=\"true\" />\n <span className=\"xicms-ve__path\" title={`Je bewerkt ${name} — ${path}`}>{path}</span>\n {localeCfg.locales.length > 1 && (\n <span className=\"xicms-ve__locale\" title=\"Taal — in een andere taal dan de standaard bewerk je vertalingen\">\n 🌐\n <Select\n value={locale}\n onValueChange={(l) => { setLocale(l); if (l !== localeCfg.defaultLocale) setPanelOpen(true); }}\n options={localeCfg.locales.map((l) => ({ value: l, label: l === localeCfg.defaultLocale ? `${l} (standaard)` : l }))}\n />\n </span>\n )}\n <button\n type=\"button\"\n className={`xicms-ve__histbtn${panelOpen && !selectedKey ? \" is-on\" : \"\"}`}\n onClick={() => { if (panelOpen && !selectedKey) { setPanelOpen(false); } else { setSelectedKey(null); setPanelOpen(true); } }}\n title=\"Pagina-instellingen (titel, SEO, versies)\"\n aria-label=\"Pagina-instellingen\"\n aria-pressed={panelOpen && !selectedKey}\n >\n ⚙\n </button>\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 {status === \"none\" && (\n <span className=\"xicms-ve__balkmelding\">\n Nog geen werkversie\n <Button size=\"sm\" onClick={() => void handleCreatePage()}>+ Pagina aanmaken</Button>\n </span>\n )}\n {/* \"Opgeslagen\" gaat over de WERKVERSIE, niet over de live site. Dat onderscheid stond\n nergens en werd daardoor gelezen als \"het staat online\". Vandaar de toevoeging. */}\n <span className=\"xicms-ve__status\" aria-live=\"polite\">\n {status === \"loading\" ? \"Werkversie laden…\" : null}\n {status === \"saving\" ? \"Opslaan…\" : status === \"saved\" ? \"✓ Bewaard als werkversie\" : 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 </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${panelOpen ? \" is-panel-open\" : \"\"}`}>\n {/* GEEN CANVAS MEER. De pagina eronder is de echte pagina en toont de werkversie (zie\n EditorProvider). Wat de editor nog moet zeggen — laden, of nog geen werkversie — staat\n in de balk hierboven, niet als los zwevend kaartje: dat dekte de menubalk van de site af,\n en gereedschap hoort de site niet in de weg te zitten. */}\n\n {panelOpen && (\n <aside className=\"xicms-ve__panel\">\n {/* Blokken toevoegen zat in de werkbalkjes van het canvas. Dat canvas is er niet meer, dus\n staat het hier — zichtbaar wanneer je het paneel toch al open hebt om aan de opbouw\n van de pagina te werken. Zonder deze verhuizing zou het toevoegen van een blok\n stilzwijgend zijn verdwenen. */}\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 {/* Een aangeklikt PRODUCT wint van een aangeklikt blok: je wees het product aan, dus dat\n wil je zien. Blok en product uitsluiten elkaar hier bewust — twee formulieren onder\n elkaar in één smal paneel is niet te lezen. */}\n {selectedProduct ? (\n <div className=\"grid gap-3\">\n <div className=\"xicms-ve__panel-head\">\n <span className=\"text-sm text-slate-500\">Webwinkel</span>\n <Button size=\"sm\" variant=\"ghost\" onClick={() => { setSelectedProduct(null); setPanelOpen(false); }}>\n ✕ Sluiten\n </Button>\n </div>\n <ProductPaneel siteId={siteId} productId={selectedProduct} />\n </div>\n ) : selected && isTranslating ? (\n <div className=\"grid gap-3\">\n <h3 className=\"text-sm font-semibold\">{registry.get(selected._type)?.label ?? selected._type} — vertaling ({locale})</h3>\n <TranslationPanel\n key={`${selected._key}:${locale}`}\n block={selected}\n siteId={siteId}\n registry={registry}\n locale={locale}\n overlay={overlay ?? {}}\n onTranslate={setTranslation}\n />\n </div>\n ) : selected ? (\n <div className=\"grid gap-3\">\n <div className=\"xicms-ve__panel-head\">\n {/* Kleur volgt de autosave-status (zelfde bron als de statustekst in de topbalk):\n blauw = nog niet-opgeslagen wijziging (of een mislukte save die opnieuw moet),\n grijs = alles staat al veilig. Directe visuele bevestiging i.p.v. alleen tekst. */}\n <Button\n size=\"sm\"\n variant={status === \"saving\" || status === \"error\" ? \"primary\" : \"ghost\"}\n onClick={() => void flushSave().catch(() => setStatus(\"error\"))}\n >\n {status === \"saving\" ? \"Opslaan…\" : status === \"error\" ? \"⚠ Opnieuw opslaan\" : \"✓ Opgeslagen\"}\n </Button>\n <Button size=\"sm\" variant=\"ghost\" onClick={() => setPanelOpen(false)}>\n ✕ Sluiten\n </Button>\n </div>\n <SelectedEditor key={selected._key} block={selected} siteId={siteId} registry={registry} onChange={(next) => updateByKey(selected._key, next)} />\n </div>\n ) : draft && isTranslating ? (\n <div className=\"grid gap-4\">\n <p className=\"xicms-ve__hint\">\n Je bewerkt de vertaling <strong>{locale}</strong>. Klik op een blok om zijn teksten te\n vertalen; lege velden vallen terug op de standaardtaal. Blokken toevoegen/verplaatsen\n doe je in de standaardtaal.\n </p>\n <PageTranslationSettings draft={draft} locale={locale} onTranslate={setTranslation} />\n </div>\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 )}\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) => {\n if (imgPicker) {\n commitInline(imgPicker.key, imgPicker.field, m.url);\n // Alt-tekst van de gekozen media meenemen (a11y/SEO): conventie url→alt / imageUrl→alt\n // op hetzelfde niveau. Bestaat het veld niet in het schema, dan stript Zod het bij\n // publiceren — nooit schadelijk, meestal precies goed (Image/Gallery/Cards/Slider).\n const altField = imgPicker.field.replace(/(^|\\.)((image)?[uU]rl)$/, \"$1alt\");\n if (altField !== imgPicker.field) commitInline(imgPicker.key, altField, m.alt ?? \"\");\n }\n setImgPicker(null);\n }}\n />\n\n {/* Potlood én tandwiel per blok, plus — op aanwijzen — de rest van het gereedschap.\n De acties verplaatsen/dupliceren/verwijderen BESTONDEN al die tijd: ze zaten in de\n werkbalkjes van het oude canvas, en toen dat canvas verdween is de UI eromheen nooit\n teruggebouwd. De functies bleven staan, niets riep ze aan, en TypeScript klaagt niet over\n een ongebruikte functiedeclaratie — dus de enige weg om een blok te verplaatsen liep\n maandenlang via het formulierscherm in /beheer. Precies waar de klant weg moest blijven.\n\n Twee knoppen in rust, de rest klapt uit bij aanwijzen: zes knoppen die permanent naast\n élk blok staan zijn geen gereedschap meer maar een hek om de site heen. */}\n {blokknoppen.map((b) => {\n const i = blocks.findIndex((blok) => blok._key === b.key);\n const isSymbool = b.type === \"symbolRef\";\n return (\n <div key={b.key} className={`xicms-ve-blokknoppen${b.key === selectedKey ? \" is-open\" : \"\"}`} style={{ top: b.top, right: b.right }}>\n <button\n type=\"button\"\n className={`xicms-ve-blokknop${b.key === selectedKey ? \" is-on\" : \"\"}`}\n title={`Tekst van ${b.label} bewerken`}\n aria-label={`Tekst van ${b.label} bewerken`}\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => {\n // Selecteren zet de velden van dít blok open (zie het bind-effect hierboven); de vlag\n // zorgt dat de caret meteen in het eerste veld staat. Zonder die vlag moest je na het\n // potlood nóg een potlood aanklikken, en dan is de eerste klik betekenisloos.\n wilInlineRef.current = true;\n setSelectedProduct(null);\n setSelectedKey(b.key);\n }}\n >\n <svg viewBox=\"0 0 24 24\" width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M4 20h4L19.5 8.5a2.1 2.1 0 0 0-3-3L5 17v3z\" />\n <path d=\"M14.5 6.5l3 3\" />\n </svg>\n </button>\n <button\n type=\"button\"\n className={`xicms-ve-blokknop${b.key === selectedKey && panelOpen ? \" is-on\" : \"\"}`}\n title={`Instellingen van ${b.label}`}\n aria-label={`Instellingen van ${b.label}`}\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => { setSelectedProduct(null); setSelectedKey(b.key); setPanelOpen(true); }}\n >\n <svg viewBox=\"0 0 24 24\" width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"3\" />\n <path d=\"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.6a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z\" />\n </svg>\n </button>\n {i >= 0 && (\n <span className=\"xicms-ve-blokknoppen__meer\"><span className=\"xicms-ve-blokknoppen__meer-in\">\n <button type=\"button\" className=\"xicms-ve-blokknop\" title=\"Blok omhoog\" aria-label={`${b.label} omhoog`} disabled={i === 0}\n onMouseDown={(e) => e.preventDefault()} onClick={() => move(i, -1)}>\n <svg viewBox=\"0 0 24 24\" width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\"><path d=\"M12 19V5M5 12l7-7 7 7\" /></svg>\n </button>\n <button type=\"button\" className=\"xicms-ve-blokknop\" title=\"Blok omlaag\" aria-label={`${b.label} omlaag`} disabled={i === blocks.length - 1}\n onMouseDown={(e) => e.preventDefault()} onClick={() => move(i, 1)}>\n <svg viewBox=\"0 0 24 24\" width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\"><path d=\"M12 5v14M19 12l-7 7-7-7\" /></svg>\n </button>\n <button type=\"button\" className=\"xicms-ve-blokknop\" title=\"Blok dupliceren\" aria-label={`${b.label} dupliceren`}\n onMouseDown={(e) => e.preventDefault()} onClick={() => duplicate(i)}>\n <svg viewBox=\"0 0 24 24\" width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\"><rect x=\"9\" y=\"9\" width=\"11\" height=\"11\" rx=\"2\" /><path d=\"M5 15V5a2 2 0 0 1 2-2h10\" /></svg>\n </button>\n {isSymbool && (\n <button type=\"button\" className=\"xicms-ve-blokknop\" title=\"Symbool loskoppelen (wordt een losse kopie)\" aria-label={`${b.label} loskoppelen`}\n onMouseDown={(e) => e.preventDefault()} onClick={() => void detachSymbol(i)}>\n <svg viewBox=\"0 0 24 24\" width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\"><path d=\"M9 12h6M8 7a4 4 0 0 0 0 8M16 7a4 4 0 0 1 0 8\" /><path d=\"M3 3l18 18\" /></svg>\n </button>\n )}\n <button type=\"button\" className=\"xicms-ve-blokknop xicms-ve-blokknop--gevaar\" title=\"Blok verwijderen\" aria-label={`${b.label} verwijderen`}\n onMouseDown={(e) => e.preventDefault()} onClick={() => void remove(i)}>\n <svg viewBox=\"0 0 24 24\" width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\"><path d=\"M3 6h18M8 6V4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2M6 6l1 14a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2l1-14\" /></svg>\n </button>\n </span></span>\n )}\n </div>\n );\n })}\n\n {/* Potloodjes bij elk inline bewerkbaar veld van het geselecteerde blok. Ze staan fixed in de\n editor-laag (niet in de render van de klant) en zetten het bewerken bewust aan.\n onMouseDown-preventDefault: anders neemt de knop de focus en sluit het veld meteen weer. */}\n {spots.map((s, i) => (\n <button\n key={i}\n type=\"button\"\n className=\"xicms-ve-pencil\"\n style={{ top: s.top, left: s.left }}\n title={s.title}\n aria-label={s.title}\n onMouseDown={(e) => e.preventDefault()}\n onMouseEnter={() => targetsRef.current[i]?.el.classList.add(\"is-hover\")}\n onMouseLeave={() => targetsRef.current[i]?.el.classList.remove(\"is-hover\")}\n onClick={() => startInline(i)}\n >\n {/* Fijn getekend potlood i.p.v. een emoji: een emoji is per platform anders, kleurt niet\n mee en oogt op deze schaal als een vlek. Eén pad, currentColor, 1.4 lijndikte. */}\n <svg viewBox=\"0 0 24 24\" width=\"12\" height=\"12\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M4 20h4L19.5 8.5a2.1 2.1 0 0 0-3-3L5 17v3z\" />\n <path d=\"M14.5 6.5l3 3\" />\n </svg>\n </button>\n ))}\n\n {/* Hetzelfde knoppenpaar, maar dan voor een PRODUCT — alleen zichtbaar bij het product dat je\n aanwijst (zie hierboven waarom). Zelfde vorm en zelfde plek als bij een blok, want het is\n dezelfde handeling: potlood voor de tekst, tandwiel voor de rest. */}\n {productKnop && (\n <div\n className=\"xicms-ve-blokknoppen is-product\"\n style={{ top: productKnop.top, right: productKnop.right }}\n >\n <button\n type=\"button\"\n className=\"xicms-ve-blokknop\"\n title=\"Productnaam bewerken (staat meteen live)\"\n aria-label=\"Productnaam bewerken\"\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => startProductInline(productKnop.id)}\n >\n <svg viewBox=\"0 0 24 24\" width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M4 20h4L19.5 8.5a2.1 2.1 0 0 0-3-3L5 17v3z\" />\n <path d=\"M14.5 6.5l3 3\" />\n </svg>\n </button>\n <button\n type=\"button\"\n className={`xicms-ve-blokknop${selectedProduct === productKnop.id ? \" is-on\" : \"\"}`}\n title=\"Product bewerken: prijs, foto's, varianten, voorraad\"\n aria-label=\"Product bewerken\"\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => {\n setSelectedKey(null);\n setSelectedProduct(productKnop.id);\n setPanelOpen(true);\n }}\n >\n <svg viewBox=\"0 0 24 24\" width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"3\" />\n <path d=\"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.6a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z\" />\n </svg>\n </button>\n </div>\n )}\n\n {/*\n Publiceren staat rechtsonder, bij je werk — niet bovenin tussen de navigatie.\n Daar was het een knop die altijd even hard riep, ook als er niets te publiceren viel, en\n dus niets meer betekende. Nu zegt de knop zelf of er iets klaarstaat: grijs en uitgeschakeld\n zolang de pagina gelijk is aan wat er live staat, en pas gekleurd zodra dat niet meer zo is.\n Dat is dezelfde bron als de rode markering op de pagina, dus de knop en de pagina kunnen het\n nooit oneens zijn.\n */}\n {draft && (\n <button\n type=\"button\"\n className={`xicms-ve-publiceer${wijzigingen.iets ? \" is-klaar\" : \"\"}`}\n disabled={publishing || !wijzigingen.iets}\n onClick={() => void onPublish()}\n title={\n wijzigingen.iets\n ? \"Zet deze wijzigingen op de live site\"\n : \"Er is niets veranderd sinds de laatste publicatie\"\n }\n >\n <span className=\"xicms-ve-publiceer__label\">{publishing ? \"Bezig…\" : \"Publiceren\"}</span>\n <span className=\"xicms-ve-publiceer__sub\">\n {wijzigingen.iets ? aantalWijzigingen(wijzigingen) : \"niets gewijzigd\"}\n </span>\n </button>\n )}\n\n {/* Zwevende opmaak-toolbar boven het gefocuste inline rich-text-veld. onMouseDown-preventDefault\n houdt focus + selectie in het veld, zodat execCommand op de selectie werkt. */}\n {fmtBar && (\n <div className=\"xicms-ve-fmtbar\" style={{ top: fmtBar.top, left: fmtBar.left }} role=\"toolbar\" aria-label=\"Tekstopmaak\">\n <FmtBtn label={<b>B</b>} title=\"Vet (Ctrl/Cmd+B)\" onRun={() => document.execCommand(\"bold\")} />\n <FmtBtn label={<i>I</i>} title=\"Cursief (Ctrl/Cmd+I)\" onRun={() => document.execCommand(\"italic\")} />\n <FmtBtn label={<u>U</u>} title=\"Onderstrepen (Ctrl/Cmd+U)\" onRun={() => document.execCommand(\"underline\")} />\n <FmtBtn\n label=\"🔗\"\n title=\"Link (selecteer eerst tekst; leeg = link verwijderen)\"\n onRun={() => {\n const url = window.prompt(\"Link-URL (leeg = link verwijderen)\", \"https://\");\n if (url === null) return;\n if (url === \"\" || url === \"https://\") document.execCommand(\"unlink\");\n else document.execCommand(\"createLink\", false, url);\n }}\n />\n <span className=\"xicms-ve-fmtbar__sep\" aria-hidden />\n <FmtBtn label=\"H2\" title=\"Grote kop\" onRun={() => document.execCommand(\"formatBlock\", false, \"<h2>\")} />\n <FmtBtn label=\"H3\" title=\"Kleine kop\" onRun={() => document.execCommand(\"formatBlock\", false, \"<h3>\")} />\n <FmtBtn label=\"¶\" title=\"Normale tekst\" onRun={() => document.execCommand(\"formatBlock\", false, \"<p>\")} />\n <span className=\"xicms-ve-fmtbar__sep\" aria-hidden />\n <FmtBtn label=\"•\" title=\"Opsomming\" onRun={() => document.execCommand(\"insertUnorderedList\")} />\n </div>\n )}\n </div>\n );\n}\n\n/** Toolbar-knop die de selectie/focus in het contentEditable-veld intact laat. */\nfunction FmtBtn({ label, title, onRun }: { label: React.ReactNode; title: string; onRun: () => void }) {\n return (\n <button\n type=\"button\"\n title={title}\n aria-label={title}\n onMouseDown={(e) => e.preventDefault()}\n onClick={onRun}\n >\n {label}\n </button>\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/** Pagina-instellingen in vertaalmodus (5.0): titel + SEO als overlay-sleutels. */\nfunction PageTranslationSettings({\n draft,\n locale,\n onTranslate,\n}: {\n draft: DraftPage;\n locale: string;\n onTranslate: (key: string, value: string) => void;\n}) {\n const ov = draft.translations?.[locale] ?? {};\n return (\n <details className=\"xicms-ve__section\" open>\n <summary className=\"text-sm font-semibold\">Pagina-instellingen ({locale})</summary>\n <div className=\"mt-2 grid gap-3\">\n <Field label=\"Paginatitel\" hint={`Origineel: ${draft.title ?? \"\"}`}>\n <Input value={ov[\"title\"] ?? \"\"} placeholder={draft.title ?? \"\"} onChange={(e) => onTranslate(\"title\", e.target.value)} />\n </Field>\n <Field label=\"SEO-titel\">\n <Input value={ov[\"seo.title\"] ?? \"\"} placeholder={draft.seo?.title ?? \"\"} onChange={(e) => onTranslate(\"seo.title\", e.target.value)} />\n </Field>\n <Field label=\"SEO-omschrijving\">\n <Textarea rows={3} value={ov[\"seo.description\"] ?? \"\"} placeholder={draft.seo?.description ?? \"\"} onChange={(e) => onTranslate(\"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":["locale","overlay","t","inField"]}
@@ -6,6 +6,8 @@ import { useRegistry, BlockDataProvider } from "../blocks/context.js";
6
6
  import { useEditor } from "../editor/EditorProvider.js";
7
7
  import { isDemoActive, registerSeed } from "../demo/demoStore.js";
8
8
  import { InlineImageEditProvider } from "../media/ImageContext.js";
9
+ import { useSiteConfig } from "../config.js";
10
+ import { parseLocaleSlugs, siteLocales } from "../i18n/overlay.js";
9
11
  function PublishedPage({
10
12
  page,
11
13
  siteId,
@@ -13,6 +15,7 @@ function PublishedPage({
13
15
  blockData
14
16
  }) {
15
17
  const registry = useRegistry();
18
+ const config = useSiteConfig();
16
19
  const { draftBlocks, editPath, fotoVervanger } = useEditor();
17
20
  useEffect(() => {
18
21
  if (!isDemoActive()) return;
@@ -25,7 +28,13 @@ function PublishedPage({
25
28
  path: pad
26
29
  });
27
30
  }, [siteId, page]);
28
- const opDezePagina = draftBlocks !== null && typeof window !== "undefined" && huidigPad(window.location.pathname) === editPath;
31
+ const opDezePagina = draftBlocks !== null && typeof window !== "undefined" && (() => {
32
+ const { slugs } = parseLocaleSlugs(
33
+ window.location.pathname.split("/").filter(Boolean),
34
+ siteLocales(config)
35
+ );
36
+ return huidigPad("/" + slugs.join("/")) === editPath;
37
+ })();
29
38
  const teTonen = opDezePagina ? { ...page, blocks: draftBlocks } : page;
30
39
  return /* @__PURE__ */ jsx(BlockDataProvider, { data: blockData, children: /* @__PURE__ */ jsx(InlineImageEditProvider, { onReplace: opDezePagina ? fotoVervanger : null, children: /* @__PURE__ */ jsx(PageRenderer, { page: teTonen, registry, siteId, mode }) }) });
31
40
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/render/PublishedPage.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect } from \"react\";\nimport { PageRenderer } from \"./PageRenderer\";\nimport { useRegistry, BlockDataProvider } from \"../blocks/context\";\nimport { useEditor } from \"../editor/EditorProvider\";\nimport { isDemoActive, registerSeed } from \"../demo/demoStore\";\nimport { InlineImageEditProvider } from \"../media/ImageContext\";\nimport type { Page } from \"../schema/pages\";\n\n/**\n * Rendert een (gepubliceerde) pagina via de **context-registry** (module-resolved, opgebouwd in\n * XiCMSProvider). Gebruik dit i.p.v. zelf `createRegistry([...builtinBlocks])` server-side te bouwen:\n * sinds de blokken op Astryx draaien zijn de meeste `\"use client\"`, en een server-opgebouwde registry\n * krijgt daar alleen opake client-referenties van → die blokken worden stil overgeslagen. Deze client-\n * component zit binnen de provider en heeft dus de volledige registry.\n *\n * `blockData` (5.6, optioneel): server-gefetchte startdata per domein (bv. `{ shop: … }` uit\n * `getShopPageData`). Data-blokken renderen dan direct met inhoud (SSR/ISR) i.p.v. eerst\n * client-side te fetchen. Moet serialiseerbare plain data zijn (server → client-prop).\n */\nexport function PublishedPage({\n page,\n siteId,\n mode = \"view\",\n blockData,\n}: {\n page: Pick<Page, \"blocks\">;\n siteId: string;\n mode?: \"view\" | \"preview\";\n blockData?: Record<string, unknown>;\n}) {\n const registry = useRegistry();\n const { draftBlocks, editPath, fotoVervanger } = useEditor();\n\n /**\n * In een DEMO is deze pagina zijn eigen uitgangspunt.\n *\n * De demo-bewerkmodus bewaart wijzigingen in de browser en legt die over een \"seed\": de\n * oorspronkelijke pagina waar de bezoeker op verder bewerkt. Zonder geregistreerde seed geeft\n * `resolveDemoPage` niets terug, meldt de editor \"Nog geen werkversie\", en loopt de enige knop\n * die dan overblijft (\"+ Pagina aanmaken\") tegen de échte database aan.\n *\n * Dat overkwam elke demo-site behalve xicms.nl zelf, want daar stond de registratie met de hand\n * in een eigen catch-all route. Een klantsite heeft die route niet en had dus wél een\n * demo-bewerkmodus in de balk, maar geen enkele pagina om te bewerken — op sites die bestaan om\n * te laten zien hoe prettig het bewerken werkt.\n *\n * Hier hoort het thuis: dit component wéét welke pagina er op dit pad staat. Buiten de demo doet\n * het niets, en in de demo is de seed per definitie gelijk aan wat de bezoeker ziet.\n */\n useEffect(() => {\n if (!isDemoActive()) return;\n const pad = huidigPad(window.location.pathname);\n // Titel en slug horen erbij: de editor toont ze in zijn balk en in de pagina-instellingen.\n // `page` levert in de praktijk het hele pagina-object; deze twee zijn de terugval voor een site\n // die alleen blokken doorgeeft — het type belooft immers niet meer dan `blocks`.\n const volledig = page as Partial<Page> & Pick<Page, \"blocks\">;\n registerSeed(siteId, pad, {\n ...volledig,\n title: volledig.title || document.title,\n slug: volledig.slug || (pad.split(\"/\").filter(Boolean).pop() ?? \"\"),\n path: pad,\n } as Page);\n // Alleen op het pad en de inhoud: opnieuw registreren bij elke render zou de seed tijdens het\n // bewerken vervangen door de zojuist bewerkte tekst, en dan is er geen origineel meer om op\n // terug te vallen.\n }, [siteId, page]);\n\n /**\n * Staat de editor open op DEZE pagina, dan tonen we de werkversie in plaats van het\n * gepubliceerde. Zo blijft de site zijn eigen layout en huisstijl tekenen, en is wat je ziet\n * precies wat je bewerkt — in plaats van een nabootsing in een apart editorvenster.\n *\n * De padvergelijking is niet overdreven voorzichtig: een pagina kan meerdere gerenderde pagina's\n * in beeld hebben (een preview, een ingekaderde pagina). Zonder die controle zou de werkversie\n * van de ene pagina in de andere opduiken.\n */\n const opDezePagina =\n draftBlocks !== null &&\n typeof window !== \"undefined\" &&\n huidigPad(window.location.pathname) === editPath;\n\n const teTonen = opDezePagina ? { ...page, blocks: draftBlocks } : page;\n\n return (\n <BlockDataProvider data={blockData}>\n {/* Alleen op de pagina die de editor open heeft: anders krijgt een preview of ingekaderde\n pagina óók 📷-knopjes, terwijl een klik daar in het verkeerde draft zou landen. */}\n <InlineImageEditProvider onReplace={opDezePagina ? fotoVervanger : null}>\n <PageRenderer page={teTonen} registry={registry} siteId={siteId} mode={mode} />\n </InlineImageEditProvider>\n </BlockDataProvider>\n );\n}\n\n/** Normaliseert een pad zoals de editor het bijhoudt: geen trailing slash, root blijft \"/\". */\nfunction huidigPad(p: string): string {\n const schoon = p.replace(/\\/+$/, \"\");\n return schoon === \"\" ? \"/\" : schoon;\n}\n"],"mappings":";AA0FQ;AAxFR,SAAS,iBAAiB;AAC1B,SAAS,oBAAoB;AAC7B,SAAS,aAAa,yBAAyB;AAC/C,SAAS,iBAAiB;AAC1B,SAAS,cAAc,oBAAoB;AAC3C,SAAS,+BAA+B;AAcjC,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP;AACF,GAKG;AACD,QAAM,WAAW,YAAY;AAC7B,QAAM,EAAE,aAAa,UAAU,cAAc,IAAI,UAAU;AAkB3D,YAAU,MAAM;AACd,QAAI,CAAC,aAAa,EAAG;AACrB,UAAM,MAAM,UAAU,OAAO,SAAS,QAAQ;AAI9C,UAAM,WAAW;AACjB,iBAAa,QAAQ,KAAK;AAAA,MACxB,GAAG;AAAA,MACH,OAAO,SAAS,SAAS,SAAS;AAAA,MAClC,MAAM,SAAS,SAAS,IAAI,MAAM,GAAG,EAAE,OAAO,OAAO,EAAE,IAAI,KAAK;AAAA,MAChE,MAAM;AAAA,IACR,CAAS;AAAA,EAIX,GAAG,CAAC,QAAQ,IAAI,CAAC;AAWjB,QAAM,eACJ,gBAAgB,QAChB,OAAO,WAAW,eAClB,UAAU,OAAO,SAAS,QAAQ,MAAM;AAE1C,QAAM,UAAU,eAAe,EAAE,GAAG,MAAM,QAAQ,YAAY,IAAI;AAElE,SACE,oBAAC,qBAAkB,MAAM,WAGvB,8BAAC,2BAAwB,WAAW,eAAe,gBAAgB,MACjE,8BAAC,gBAAa,MAAM,SAAS,UAAoB,QAAgB,MAAY,GAC/E,GACF;AAEJ;AAGA,SAAS,UAAU,GAAmB;AACpC,QAAM,SAAS,EAAE,QAAQ,QAAQ,EAAE;AACnC,SAAO,WAAW,KAAK,MAAM;AAC/B;","names":[]}
1
+ {"version":3,"sources":["../../src/render/PublishedPage.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect } from \"react\";\nimport { PageRenderer } from \"./PageRenderer\";\nimport { useRegistry, BlockDataProvider } from \"../blocks/context\";\nimport { useEditor } from \"../editor/EditorProvider\";\nimport { isDemoActive, registerSeed } from \"../demo/demoStore\";\nimport { InlineImageEditProvider } from \"../media/ImageContext\";\nimport { useSiteConfig } from \"../config\";\nimport { parseLocaleSlugs, siteLocales } from \"../i18n/overlay\";\nimport type { Page } from \"../schema/pages\";\n\n/**\n * Rendert een (gepubliceerde) pagina via de **context-registry** (module-resolved, opgebouwd in\n * XiCMSProvider). Gebruik dit i.p.v. zelf `createRegistry([...builtinBlocks])` server-side te bouwen:\n * sinds de blokken op Astryx draaien zijn de meeste `\"use client\"`, en een server-opgebouwde registry\n * krijgt daar alleen opake client-referenties van → die blokken worden stil overgeslagen. Deze client-\n * component zit binnen de provider en heeft dus de volledige registry.\n *\n * `blockData` (5.6, optioneel): server-gefetchte startdata per domein (bv. `{ shop: … }` uit\n * `getShopPageData`). Data-blokken renderen dan direct met inhoud (SSR/ISR) i.p.v. eerst\n * client-side te fetchen. Moet serialiseerbare plain data zijn (server → client-prop).\n */\nexport function PublishedPage({\n page,\n siteId,\n mode = \"view\",\n blockData,\n}: {\n page: Pick<Page, \"blocks\">;\n siteId: string;\n mode?: \"view\" | \"preview\";\n blockData?: Record<string, unknown>;\n}) {\n const registry = useRegistry();\n const config = useSiteConfig();\n const { draftBlocks, editPath, fotoVervanger } = useEditor();\n\n /**\n * In een DEMO is deze pagina zijn eigen uitgangspunt.\n *\n * De demo-bewerkmodus bewaart wijzigingen in de browser en legt die over een \"seed\": de\n * oorspronkelijke pagina waar de bezoeker op verder bewerkt. Zonder geregistreerde seed geeft\n * `resolveDemoPage` niets terug, meldt de editor \"Nog geen werkversie\", en loopt de enige knop\n * die dan overblijft (\"+ Pagina aanmaken\") tegen de échte database aan.\n *\n * Dat overkwam elke demo-site behalve xicms.nl zelf, want daar stond de registratie met de hand\n * in een eigen catch-all route. Een klantsite heeft die route niet en had dus wél een\n * demo-bewerkmodus in de balk, maar geen enkele pagina om te bewerken — op sites die bestaan om\n * te laten zien hoe prettig het bewerken werkt.\n *\n * Hier hoort het thuis: dit component wéét welke pagina er op dit pad staat. Buiten de demo doet\n * het niets, en in de demo is de seed per definitie gelijk aan wat de bezoeker ziet.\n */\n useEffect(() => {\n if (!isDemoActive()) return;\n const pad = huidigPad(window.location.pathname);\n // Titel en slug horen erbij: de editor toont ze in zijn balk en in de pagina-instellingen.\n // `page` levert in de praktijk het hele pagina-object; deze twee zijn de terugval voor een site\n // die alleen blokken doorgeeft — het type belooft immers niet meer dan `blocks`.\n const volledig = page as Partial<Page> & Pick<Page, \"blocks\">;\n registerSeed(siteId, pad, {\n ...volledig,\n title: volledig.title || document.title,\n slug: volledig.slug || (pad.split(\"/\").filter(Boolean).pop() ?? \"\"),\n path: pad,\n } as Page);\n // Alleen op het pad en de inhoud: opnieuw registreren bij elke render zou de seed tijdens het\n // bewerken vervangen door de zojuist bewerkte tekst, en dan is er geen origineel meer om op\n // terug te vallen.\n }, [siteId, page]);\n\n /**\n * Staat de editor open op DEZE pagina, dan tonen we de werkversie in plaats van het\n * gepubliceerde. Zo blijft de site zijn eigen layout en huisstijl tekenen, en is wat je ziet\n * precies wat je bewerkt — in plaats van een nabootsing in een apart editorvenster.\n *\n * De padvergelijking is niet overdreven voorzichtig: een pagina kan meerdere gerenderde pagina's\n * in beeld hebben (een preview, een ingekaderde pagina). Zonder die controle zou de werkversie\n * van de ene pagina in de andere opduiken.\n */\n /**\n * `editPath` is het CANONIEKE pad — zonder taalprefix, want zo houdt de editor zijn werkversie\n * bij. De URL-balk heeft die prefix wél (`/de/over-ons`). Vergeleken zonder die prefix eraf te\n * halen was de werkversie op elke vertaalde pagina onzichtbaar: de editor stond open, je typte,\n * en de pagina bleef stug het gepubliceerde tonen. Eéntalige sites merkten hier niets van —\n * precies waarom dit zo lang kon blijven staan.\n */\n const opDezePagina =\n draftBlocks !== null &&\n typeof window !== \"undefined\" &&\n (() => {\n const { slugs } = parseLocaleSlugs(\n window.location.pathname.split(\"/\").filter(Boolean),\n siteLocales(config),\n );\n return huidigPad(\"/\" + slugs.join(\"/\")) === editPath;\n })();\n\n const teTonen = opDezePagina ? { ...page, blocks: draftBlocks } : page;\n\n return (\n <BlockDataProvider data={blockData}>\n {/* Alleen op de pagina die de editor open heeft: anders krijgt een preview of ingekaderde\n pagina óók 📷-knopjes, terwijl een klik daar in het verkeerde draft zou landen. */}\n <InlineImageEditProvider onReplace={opDezePagina ? fotoVervanger : null}>\n <PageRenderer page={teTonen} registry={registry} siteId={siteId} mode={mode} />\n </InlineImageEditProvider>\n </BlockDataProvider>\n );\n}\n\n/** Normaliseert een pad zoals de editor het bijhoudt: geen trailing slash, root blijft \"/\". */\nfunction huidigPad(p: string): string {\n const schoon = p.replace(/\\/+$/, \"\");\n return schoon === \"\" ? \"/\" : schoon;\n}\n"],"mappings":";AA0GQ;AAxGR,SAAS,iBAAiB;AAC1B,SAAS,oBAAoB;AAC7B,SAAS,aAAa,yBAAyB;AAC/C,SAAS,iBAAiB;AAC1B,SAAS,cAAc,oBAAoB;AAC3C,SAAS,+BAA+B;AACxC,SAAS,qBAAqB;AAC9B,SAAS,kBAAkB,mBAAmB;AAcvC,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP;AACF,GAKG;AACD,QAAM,WAAW,YAAY;AAC7B,QAAM,SAAS,cAAc;AAC7B,QAAM,EAAE,aAAa,UAAU,cAAc,IAAI,UAAU;AAkB3D,YAAU,MAAM;AACd,QAAI,CAAC,aAAa,EAAG;AACrB,UAAM,MAAM,UAAU,OAAO,SAAS,QAAQ;AAI9C,UAAM,WAAW;AACjB,iBAAa,QAAQ,KAAK;AAAA,MACxB,GAAG;AAAA,MACH,OAAO,SAAS,SAAS,SAAS;AAAA,MAClC,MAAM,SAAS,SAAS,IAAI,MAAM,GAAG,EAAE,OAAO,OAAO,EAAE,IAAI,KAAK;AAAA,MAChE,MAAM;AAAA,IACR,CAAS;AAAA,EAIX,GAAG,CAAC,QAAQ,IAAI,CAAC;AAkBjB,QAAM,eACJ,gBAAgB,QAChB,OAAO,WAAW,gBACjB,MAAM;AACL,UAAM,EAAE,MAAM,IAAI;AAAA,MAChB,OAAO,SAAS,SAAS,MAAM,GAAG,EAAE,OAAO,OAAO;AAAA,MAClD,YAAY,MAAM;AAAA,IACpB;AACA,WAAO,UAAU,MAAM,MAAM,KAAK,GAAG,CAAC,MAAM;AAAA,EAC9C,GAAG;AAEL,QAAM,UAAU,eAAe,EAAE,GAAG,MAAM,QAAQ,YAAY,IAAI;AAElE,SACE,oBAAC,qBAAkB,MAAM,WAGvB,8BAAC,2BAAwB,WAAW,eAAe,gBAAgB,MACjE,8BAAC,gBAAa,MAAM,SAAS,UAAoB,QAAgB,MAAY,GAC/E,GACF;AAEJ;AAGA,SAAS,UAAU,GAAmB;AACpC,QAAM,SAAS,EAAE,QAAQ,QAAQ,EAAE;AACnC,SAAO,WAAW,KAAK,MAAM;AAC/B;","names":[]}
package/dist/styles.css CHANGED
@@ -333,7 +333,11 @@
333
333
  min-height: 0;
334
334
  }
335
335
  .xicms-ve__body.is-panel-open {
336
- grid-template-columns: 1fr 360px;
336
+ /* 360px was gekozen voor blok-instellingen (een handvol velden). Sinds het productformulier in
337
+ ditzelfde paneel opent (7.8) is dat te smal: twee kolommen velden, varianten-tabellen en
338
+ foto-rijen vouwden zich dubbel. clamp: nooit smaller dan de oude 360, nooit meer dan 480, en
339
+ op een gewoon scherm ± een kwart — de pagina blijft de hoofdzaak. */
340
+ grid-template-columns: 1fr clamp(360px, 28vw, 480px);
337
341
  }
338
342
  .xicms-ve__canvas {
339
343
  overflow: auto;
@@ -782,6 +786,29 @@
782
786
  :root:has(.xicms-ve__bar) {
783
787
  padding-top: 2.6rem;
784
788
  }
789
+ /* Smalle schermen: de balk hield alles en werd twee regels hoog — en de pagina-offset (2.6rem,
790
+ vast) rekent op één regel, dus de site schoof onder de balk. Het pad en de statustekst zijn op
791
+ een telefoon ballast; de vier moduskoppen en het gereedschap zijn de balk. */
792
+ @media (max-width: 720px) {
793
+ .xicms-ve__path,
794
+ .xicms-ve__status,
795
+ .xicms-ve__locale {
796
+ display: none;
797
+ }
798
+ .xicms-editbar,
799
+ .xicms-ve__bar {
800
+ gap: 0.15rem;
801
+ padding: 0.4rem 0.5rem;
802
+ overflow-x: auto;
803
+ /* Eén regel afdwingen: krimpen mag, breken niet. */
804
+ flex-wrap: nowrap;
805
+ white-space: nowrap;
806
+ }
807
+ .xicms-editbar__btn {
808
+ padding: 0.42rem 0.55rem;
809
+ }
810
+ }
811
+
785
812
  .xicms-ve__sep {
786
813
  width: 1px;
787
814
  align-self: stretch;
@@ -3112,21 +3139,29 @@
3112
3139
  outline: 2px solid #a5b4fc;
3113
3140
  outline-offset: 1px;
3114
3141
  }
3115
- /* De rest van het gereedschap (verplaatsen, dupliceren, verwijderen) klapt uit bij aanwijzen of
3116
- toetsenbordfocus. Dicht in rust: zes permanente knoppen naast elk blok zijn geen gereedschap
3117
- maar een hek om de site heen. */
3118
- .xicms-ve-blokknoppen__meer {
3119
- display: flex;
3120
- max-width: 0;
3121
- overflow: hidden;
3122
- opacity: 0;
3123
- transition: max-width 0.18s ease, opacity 0.15s ease;
3124
- gap: 2px;
3142
+ /* De rest van het gereedschap (verplaatsen, dupliceren, verwijderen) toont zich bij aanwijzen,
3143
+ toetsenbordfocus of een geselecteerd blok (`is-open` — op een telefoon bestaat aanwijzen niet).
3144
+ Dicht in rust: zes permanente knoppen naast elk blok zijn geen gereedschap maar een hek om de
3145
+ site heen.
3146
+
3147
+ Een kale display-toggle, bewust zonder uitklap-animatie. Hier hebben twee animatietrucs
3148
+ (max-width en 0fr→1fr) allebei stilletjes gefaald in de fixed+flex-context van dit cluster —
3149
+ dicht terwijl de selector aantoonbaar matchte. Een knoppenrij die er ALTIJD is wanneer hij er
3150
+ hoort te zijn is meer waard dan een die er mooi in schuift maar soms niet komt. */
3151
+ .xicms-ve-blokknoppen__meer,
3152
+ .xicms-ve-blokknoppen__meer-in {
3153
+ display: none;
3125
3154
  }
3126
3155
  .xicms-ve-blokknoppen:hover .xicms-ve-blokknoppen__meer,
3127
- .xicms-ve-blokknoppen:focus-within .xicms-ve-blokknoppen__meer {
3128
- max-width: 12rem;
3129
- opacity: 1;
3156
+ .xicms-ve-blokknoppen:focus-within .xicms-ve-blokknoppen__meer,
3157
+ .xicms-ve-blokknoppen.is-open .xicms-ve-blokknoppen__meer {
3158
+ display: contents;
3159
+ }
3160
+ .xicms-ve-blokknoppen:hover .xicms-ve-blokknoppen__meer-in,
3161
+ .xicms-ve-blokknoppen:focus-within .xicms-ve-blokknoppen__meer-in,
3162
+ .xicms-ve-blokknoppen.is-open .xicms-ve-blokknoppen__meer-in {
3163
+ display: flex;
3164
+ gap: 2px;
3130
3165
  }
3131
3166
  .xicms-ve-blokknop:disabled {
3132
3167
  opacity: 0.35;
package/dist/version.js CHANGED
@@ -1,4 +1,4 @@
1
- const CORE_VERSION = true ? "7.9.0" : "0.0.0-dev";
1
+ const CORE_VERSION = true ? "7.9.1" : "0.0.0-dev";
2
2
  const CORE_PACKAGE = "@xiwebdesign/xicms-core";
3
3
  function isNieuwer(beschikbaar, huidig) {
4
4
  const delen = (v) => v.split("-")[0].split(".").map((n) => Number.parseInt(n, 10) || 0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiwebdesign/xicms-core",
3
- "version": "7.9.0",
3
+ "version": "7.9.1",
4
4
  "description": "XiCMS engine: gestructureerde content, pagina's, draft/publish, admin (TipTap+Radix), SEO en beveiliging voor klantsites.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {