@xiwebdesign/xicms-core 7.8.0 → 7.8.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.
- package/dist/admin/visual/VisualEditor.js +3 -1
- package/dist/admin/visual/VisualEditor.js.map +1 -1
- package/dist/components/EditBar.js +3 -1
- package/dist/components/EditBar.js.map +1 -1
- package/dist/components/barOffset.d.ts +23 -0
- package/dist/components/barOffset.js +31 -0
- package/dist/components/barOffset.js.map +1 -0
- package/dist/render/PublishedPage.js +13 -0
- package/dist/render/PublishedPage.js.map +1 -1
- package/dist/styles.css +3 -0
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -21,6 +21,7 @@ import { StyleEditor } from "../blockEditors/StyleEditor.js";
|
|
|
21
21
|
import { siteLocales, parseLocaleSlugs, localizeBlocks } from "../../i18n/overlay.js";
|
|
22
22
|
import { useEditor } from "../../editor/EditorProvider.js";
|
|
23
23
|
import { AdminModes } from "../../components/EditBar.js";
|
|
24
|
+
import { useBalkOffset } from "../../components/barOffset.js";
|
|
24
25
|
import { aantalWijzigingen, bepaalWijzigingen, GEEN_WIJZIGINGEN } from "./wijzigingen.js";
|
|
25
26
|
import { TranslationPanel } from "./TranslationPanel.js";
|
|
26
27
|
import { PatternInserter } from "../patterns/PatternInserter.js";
|
|
@@ -108,6 +109,7 @@ function VisualEditorInner({ onClose }) {
|
|
|
108
109
|
const canvasRef = useRef(null);
|
|
109
110
|
const [conflict, setConflict] = useState(false);
|
|
110
111
|
const [liveBlocks, setLiveBlocks] = useState(void 0);
|
|
112
|
+
useBalkOffset(true);
|
|
111
113
|
const { vars: themeVars, darkAuto } = useThemeVars();
|
|
112
114
|
const nestedDialogOpen = useNestedDialogOpen();
|
|
113
115
|
const { containerRef, focusFirst } = useFocusTrap({ isActive: !nestedDialogOpen });
|
|
@@ -806,7 +808,7 @@ function VisualEditorInner({ onClose }) {
|
|
|
806
808
|
}
|
|
807
809
|
setConflict(false);
|
|
808
810
|
}, [siteId, path, mutateDraft]);
|
|
809
|
-
return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: "xicms-ve__shell", role: "group", "aria-label": `Pagina bewerken: ${path}`, children: [
|
|
811
|
+
return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: "xicms-ve__shell", "data-xicms-tooling": "", role: "group", "aria-label": `Pagina bewerken: ${path}`, children: [
|
|
810
812
|
/* @__PURE__ */ jsxs("header", { className: "xicms-ve__bar", role: "toolbar", "aria-label": "Beheer", children: [
|
|
811
813
|
/* @__PURE__ */ jsx("span", { className: "xicms-editbar__brand", children: isDemoActive() ? "XiCMS \xB7 demo" : "XiCMS" }),
|
|
812
814
|
/* @__PURE__ */ jsx(
|
|
@@ -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 { 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\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 } = useEditor();\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; 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 const meet = () => {\n const veilig = veiligeBovenkant();\n const gevonden: { key: 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 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 meet();\n window.addEventListener(\"scroll\", meet, true);\n window.addEventListener(\"resize\", meet);\n return () => {\n window.removeEventListener(\"scroll\", meet, true);\n window.removeEventListener(\"resize\", meet);\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\" 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. Bewust twee knoppen en geen menu: het zijn twee\n verschillende handelingen, niet twee varianten van dezelfde. Het potlood bewerkt de tekst\n waar hij staat; het tandwiel gaat over alles wat je niet kunt typen — een link achter een\n knop, een kleur, hoeveel items er getoond worden. Wie tekst wil wijzigen hoort niet eerst\n een instellingenpaneel te hoeven openen, en dat moest tot 7.8 wel. */}\n {blokknoppen.map((b) => (\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 hieronder); 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 </div>\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 } = useEditor();\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; 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 const meet = () => {\n const veilig = veiligeBovenkant();\n const gevonden: { key: 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 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 meet();\n window.addEventListener(\"scroll\", meet, true);\n window.addEventListener(\"resize\", meet);\n return () => {\n window.removeEventListener(\"scroll\", meet, true);\n window.removeEventListener(\"resize\", meet);\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. Bewust twee knoppen en geen menu: het zijn twee\n verschillende handelingen, niet twee varianten van dezelfde. Het potlood bewerkt de tekst\n waar hij staat; het tandwiel gaat over alles wat je niet kunt typen — een link achter een\n knop, een kleur, hoeveel items er getoond worden. Wie tekst wil wijzigen hoort niet eerst\n een instellingenpaneel te hoeven openen, en dat moest tot 7.8 wel. */}\n {blokknoppen.map((b) => (\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 hieronder); 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 </div>\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"]}
|
|
@@ -3,11 +3,13 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { useAuth } from "../auth/AuthProvider.js";
|
|
4
4
|
import { canEdit } from "../auth/roles.js";
|
|
5
5
|
import { useEditor } from "../editor/EditorProvider.js";
|
|
6
|
+
import { useBalkOffset } from "./barOffset.js";
|
|
6
7
|
function EditBar() {
|
|
7
8
|
const { user, role } = useAuth();
|
|
8
9
|
const { open } = useEditor();
|
|
10
|
+
useBalkOffset(Boolean(user) && canEdit(role) && !open);
|
|
9
11
|
if (!canEdit(role) || !user || open) return null;
|
|
10
|
-
return /* @__PURE__ */ jsxs("div", { className: "xicms-editbar", role: "toolbar", "aria-label": "Beheer", children: [
|
|
12
|
+
return /* @__PURE__ */ jsxs("div", { className: "xicms-editbar", role: "toolbar", "aria-label": "Beheer", "data-xicms-tooling": "", children: [
|
|
11
13
|
/* @__PURE__ */ jsx("span", { className: "xicms-editbar__brand", children: "XiCMS" }),
|
|
12
14
|
/* @__PURE__ */ jsx(AdminModes, { actief: "live" })
|
|
13
15
|
] });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/EditBar.tsx"],"sourcesContent":["\"use client\";\n\nimport { useAuth } from \"../auth/AuthProvider\";\nimport { canEdit } from \"../auth/roles\";\nimport { useEditor, type Bewerkmodus } from \"../editor/EditorProvider\";\n\n/**\n * Zwevende werkbalk voor ingelogde beheerders/editors.\n *\n * Bewust met EIGEN klassen en eigen kleuren, niet met `.xicms-btn`. Die knop leest de\n * `--xicms-*`-tokens van de klantsite, en dit balkje zweeft over die site heen: het nam daardoor\n * half de huisstijl over (en op plekken waar de tokens niet in scope zijn, viel het terug op een\n * indigo die bij niets hoorde). Gereedschap hoort er als gereedschap uit te zien — hetzelfde op\n * elke site, herkenbaar los van de pagina eronder.\n *\n * ── VIER OPTIES, ALTIJD DEZELFDE (7.8) ────────────────────────────────────────────────────\n *\n * De balk had één hoofdactie (\"Pagina bewerken\") en verdween zodra je die indrukte: de editor\n * bracht zijn eigen balk mee, met andere knoppen op andere plekken. Je verloor dus je houvast op\n * het moment dat je het het hardst nodig had, en er was geen weg terug naar de live site zonder\n * de editor te sluiten.\n *\n * Nu staan er vier vaste opties — Live site · Bewerken · Beheren · Uitloggen — en die staan er in\n * béide toestanden, op dezelfde plek. `AdminModes` hieronder is letterlijk hetzelfde component in\n * de balk van de editor, zodat het overschakelen geen wisseling van balk is maar alleen een andere\n * knop die oplicht.\n */\nexport function EditBar() {\n // Bewust via useAuth (niet via het deprecated ContentProvider-pad): de EditBar hoort bij het\n // block-model en mag niet aan het legacy inline-systeem hangen.\n const { user, role } = useAuth();\n const { open } = useEditor();\n\n // In de bewerkmodus tekent de editor de balk (met dezelfde vier opties plus zijn gereedschap);\n // twee balken onder elkaar zou de site twee keer zo ver omlaag duwen.\n if (!canEdit(role) || !user || open) return null;\n\n return (\n <div className=\"xicms-editbar\" role=\"toolbar\" aria-label=\"Beheer\">\n <span className=\"xicms-editbar__brand\">XiCMS</span>\n <AdminModes actief=\"live\" />\n </div>\n );\n}\n\n/**\n * De vier opties zelf, los zodat de editor ze kan hergebruiken.\n *\n * `actief` zegt welke toestand je nú bekijkt, niet wat de knop doet: \"Live site\" is ingedrukt\n * zolang je de pagina ziet zoals een bezoeker hem ziet. Zonder die markering is er geen verschil\n * te zien tussen \"ik kijk naar de live site\" en \"ik bewerk maar heb nog niets veranderd\", en dat\n * verschil is precies waar deze balk over gaat.\n */\nexport function AdminModes({\n actief,\n voorSwitch,\n}: {\n actief: Bewerkmodus;\n /**\n * Kans voor de editor om iets te zeggen vóór je weggaat — bijvoorbeeld dat er nog wijzigingen\n * klaarstaan die niet live staan. Bewust niet blokkerend: sluiten bewaart je werk, dus een\n * bevestigingsvraag zou alleen maar in de weg zitten. Melden is genoeg, tegenhouden is te veel.\n */\n voorSwitch?: (modus: Bewerkmodus) => void;\n}) {\n const { demo, signOut } = useAuth();\n const { zetModus } = useEditor();\n\n const knop = (modus: Bewerkmodus, label: string, titel: string) => (\n <button\n type=\"button\"\n className={`xicms-editbar__btn${actief === modus ? \" is-actief\" : \"\"}`}\n aria-pressed={actief === modus}\n title={titel}\n onClick={() => {\n if (modus !== actief) voorSwitch?.(modus);\n zetModus(modus);\n }}\n >\n {label}\n </button>\n );\n\n return (\n <>\n {knop(\"live\", \"Live site\", \"De pagina precies zoals een bezoeker hem ziet\")}\n {knop(\"bewerken\", \"Bewerken\", \"Dezelfde pagina, met de werkversie en potlood/tandwiel per blok\")}\n <span className=\"xicms-editbar__sep\" aria-hidden=\"true\" />\n {/* In de demo bestaat er geen /beheer-route; daar bewerk je alleen op de pagina. */}\n {!demo && (\n <a href=\"/beheer\" className=\"xicms-editbar__btn\" title=\"Pagina's, media, instellingen, webwinkel\">\n Beheren\n </a>\n )}\n <button type=\"button\" className=\"xicms-editbar__btn\" onClick={() => void signOut()}>\n Uitloggen\n </button>\n </>\n );\n}\n"],"mappings":";
|
|
1
|
+
{"version":3,"sources":["../../src/components/EditBar.tsx"],"sourcesContent":["\"use client\";\n\nimport { useAuth } from \"../auth/AuthProvider\";\nimport { canEdit } from \"../auth/roles\";\nimport { useEditor, type Bewerkmodus } from \"../editor/EditorProvider\";\nimport { useBalkOffset } from \"./barOffset\";\n\n/**\n * Zwevende werkbalk voor ingelogde beheerders/editors.\n *\n * Bewust met EIGEN klassen en eigen kleuren, niet met `.xicms-btn`. Die knop leest de\n * `--xicms-*`-tokens van de klantsite, en dit balkje zweeft over die site heen: het nam daardoor\n * half de huisstijl over (en op plekken waar de tokens niet in scope zijn, viel het terug op een\n * indigo die bij niets hoorde). Gereedschap hoort er als gereedschap uit te zien — hetzelfde op\n * elke site, herkenbaar los van de pagina eronder.\n *\n * ── VIER OPTIES, ALTIJD DEZELFDE (7.8) ────────────────────────────────────────────────────\n *\n * De balk had één hoofdactie (\"Pagina bewerken\") en verdween zodra je die indrukte: de editor\n * bracht zijn eigen balk mee, met andere knoppen op andere plekken. Je verloor dus je houvast op\n * het moment dat je het het hardst nodig had, en er was geen weg terug naar de live site zonder\n * de editor te sluiten.\n *\n * Nu staan er vier vaste opties — Live site · Bewerken · Beheren · Uitloggen — en die staan er in\n * béide toestanden, op dezelfde plek. `AdminModes` hieronder is letterlijk hetzelfde component in\n * de balk van de editor, zodat het overschakelen geen wisseling van balk is maar alleen een andere\n * knop die oplicht.\n */\nexport function EditBar() {\n // Bewust via useAuth (niet via het deprecated ContentProvider-pad): de EditBar hoort bij het\n // block-model en mag niet aan het legacy inline-systeem hangen.\n const { user, role } = useAuth();\n const { open } = useEditor();\n // Ook in de live-modus: de balk dekt de bovenkant af, dus ook hier moeten plakkende headers\n // eronder vandaan. Alleen wanneer de balk daadwerkelijk getekend wordt (zie de guard hieronder).\n useBalkOffset(Boolean(user) && canEdit(role) && !open);\n\n // In de bewerkmodus tekent de editor de balk (met dezelfde vier opties plus zijn gereedschap);\n // twee balken onder elkaar zou de site twee keer zo ver omlaag duwen.\n if (!canEdit(role) || !user || open) return null;\n\n return (\n <div className=\"xicms-editbar\" role=\"toolbar\" aria-label=\"Beheer\" data-xicms-tooling=\"\">\n <span className=\"xicms-editbar__brand\">XiCMS</span>\n <AdminModes actief=\"live\" />\n </div>\n );\n}\n\n/**\n * De vier opties zelf, los zodat de editor ze kan hergebruiken.\n *\n * `actief` zegt welke toestand je nú bekijkt, niet wat de knop doet: \"Live site\" is ingedrukt\n * zolang je de pagina ziet zoals een bezoeker hem ziet. Zonder die markering is er geen verschil\n * te zien tussen \"ik kijk naar de live site\" en \"ik bewerk maar heb nog niets veranderd\", en dat\n * verschil is precies waar deze balk over gaat.\n */\nexport function AdminModes({\n actief,\n voorSwitch,\n}: {\n actief: Bewerkmodus;\n /**\n * Kans voor de editor om iets te zeggen vóór je weggaat — bijvoorbeeld dat er nog wijzigingen\n * klaarstaan die niet live staan. Bewust niet blokkerend: sluiten bewaart je werk, dus een\n * bevestigingsvraag zou alleen maar in de weg zitten. Melden is genoeg, tegenhouden is te veel.\n */\n voorSwitch?: (modus: Bewerkmodus) => void;\n}) {\n const { demo, signOut } = useAuth();\n const { zetModus } = useEditor();\n\n const knop = (modus: Bewerkmodus, label: string, titel: string) => (\n <button\n type=\"button\"\n className={`xicms-editbar__btn${actief === modus ? \" is-actief\" : \"\"}`}\n aria-pressed={actief === modus}\n title={titel}\n onClick={() => {\n if (modus !== actief) voorSwitch?.(modus);\n zetModus(modus);\n }}\n >\n {label}\n </button>\n );\n\n return (\n <>\n {knop(\"live\", \"Live site\", \"De pagina precies zoals een bezoeker hem ziet\")}\n {knop(\"bewerken\", \"Bewerken\", \"Dezelfde pagina, met de werkversie en potlood/tandwiel per blok\")}\n <span className=\"xicms-editbar__sep\" aria-hidden=\"true\" />\n {/* In de demo bestaat er geen /beheer-route; daar bewerk je alleen op de pagina. */}\n {!demo && (\n <a href=\"/beheer\" className=\"xicms-editbar__btn\" title=\"Pagina's, media, instellingen, webwinkel\">\n Beheren\n </a>\n )}\n <button type=\"button\" className=\"xicms-editbar__btn\" onClick={() => void signOut()}>\n Uitloggen\n </button>\n </>\n );\n}\n"],"mappings":";AA0CI,SA8CA,UA7CE,KADF;AAxCJ,SAAS,eAAe;AACxB,SAAS,eAAe;AACxB,SAAS,iBAAmC;AAC5C,SAAS,qBAAqB;AAuBvB,SAAS,UAAU;AAGxB,QAAM,EAAE,MAAM,KAAK,IAAI,QAAQ;AAC/B,QAAM,EAAE,KAAK,IAAI,UAAU;AAG3B,gBAAc,QAAQ,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI;AAIrD,MAAI,CAAC,QAAQ,IAAI,KAAK,CAAC,QAAQ,KAAM,QAAO;AAE5C,SACE,qBAAC,SAAI,WAAU,iBAAgB,MAAK,WAAU,cAAW,UAAS,sBAAmB,IACnF;AAAA,wBAAC,UAAK,WAAU,wBAAuB,mBAAK;AAAA,IAC5C,oBAAC,cAAW,QAAO,QAAO;AAAA,KAC5B;AAEJ;AAUO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AACF,GAQG;AACD,QAAM,EAAE,MAAM,QAAQ,IAAI,QAAQ;AAClC,QAAM,EAAE,SAAS,IAAI,UAAU;AAE/B,QAAM,OAAO,CAAC,OAAoB,OAAe,UAC/C;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,WAAW,qBAAqB,WAAW,QAAQ,eAAe,EAAE;AAAA,MACpE,gBAAc,WAAW;AAAA,MACzB,OAAO;AAAA,MACP,SAAS,MAAM;AACb,YAAI,UAAU,OAAQ,cAAa,KAAK;AACxC,iBAAS,KAAK;AAAA,MAChB;AAAA,MAEC;AAAA;AAAA,EACH;AAGF,SACE,iCACG;AAAA,SAAK,QAAQ,aAAa,+CAA+C;AAAA,IACzE,KAAK,YAAY,YAAY,iEAAiE;AAAA,IAC/F,oBAAC,UAAK,WAAU,sBAAqB,eAAY,QAAO;AAAA,IAEvD,CAAC,QACA,oBAAC,OAAE,MAAK,WAAU,WAAU,sBAAqB,OAAM,4CAA2C,qBAElG;AAAA,IAEF,oBAAC,YAAO,MAAK,UAAS,WAAU,sBAAqB,SAAS,MAAM,KAAK,QAAQ,GAAG,uBAEpF;AAAA,KACF;AAEJ;","names":[]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schuif de plakkende elementen van de SITE onder onze balk vandaan.
|
|
3
|
+
*
|
|
4
|
+
* ── WAAROM PADDING NIET GENOEG IS ─────────────────────────────────────────────────────────
|
|
5
|
+
*
|
|
6
|
+
* De beheerbalk staat `fixed` bovenaan en de pagina wordt omlaag geduwd met padding op `:root`.
|
|
7
|
+
* Dat werkt voor alles wat gewoon meestroomt — maar niet voor een header met
|
|
8
|
+
* `position: sticky; top: 0`. Die plakt aan de bovenkant van het VENSTER, niet aan de bovenkant
|
|
9
|
+
* van de pagina, en schuift dus vrolijk onder onze balk door. Op groenhof was het logo en het hele
|
|
10
|
+
* menu daardoor half afgedekt zodra je inlogde.
|
|
11
|
+
*
|
|
12
|
+
* Het is geen randgeval: een plakkende header is zo'n beetje de standaard, en elke klantsite kiest
|
|
13
|
+
* daar zijn eigen klassenaam en eigen CSS voor. Wij kunnen die CSS niet kennen en willen hem al
|
|
14
|
+
* helemaal niet overschrijven met een gok van een selector.
|
|
15
|
+
*
|
|
16
|
+
* Dus meten we het gewoon: welke elementen plakken of hangen vast aan de bovenrand, en zet daar
|
|
17
|
+
* zolang de balk er staat een `top` van de balkhoogte op. Bij het opruimen gaat de oorspronkelijke
|
|
18
|
+
* waarde terug — de site blijft van de klant.
|
|
19
|
+
*
|
|
20
|
+
* Bewust GEEN elementen aanraken die al verder naar beneden hangen (een plakkende zijbalk, een
|
|
21
|
+
* knop rechtsonder): die zitten onze balk niet in de weg en verplaatsen zou ze juist verschuiven.
|
|
22
|
+
*/
|
|
23
|
+
export declare function useBalkOffset(actief: boolean, hoogte?: number): void;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useEffect } from "react";
|
|
3
|
+
function useBalkOffset(actief, hoogte = 40) {
|
|
4
|
+
useEffect(() => {
|
|
5
|
+
if (!actief || typeof document === "undefined") return;
|
|
6
|
+
const aangepast = [];
|
|
7
|
+
const verschuif = () => {
|
|
8
|
+
for (const el of Array.from(document.querySelectorAll("body *"))) {
|
|
9
|
+
if (el.closest("[data-xicms-tooling]")) continue;
|
|
10
|
+
if (aangepast.some((a) => a.el === el)) continue;
|
|
11
|
+
const cs = getComputedStyle(el);
|
|
12
|
+
if (cs.position !== "sticky" && cs.position !== "fixed") continue;
|
|
13
|
+
const top = parseFloat(cs.top);
|
|
14
|
+
if (!Number.isFinite(top) || top > 4) continue;
|
|
15
|
+
aangepast.push({ el, top: el.style.top });
|
|
16
|
+
el.style.top = `${top + hoogte}px`;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
verschuif();
|
|
20
|
+
const obs = new MutationObserver(verschuif);
|
|
21
|
+
obs.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["class", "style"] });
|
|
22
|
+
return () => {
|
|
23
|
+
obs.disconnect();
|
|
24
|
+
for (const { el, top } of aangepast) el.style.top = top;
|
|
25
|
+
};
|
|
26
|
+
}, [actief, hoogte]);
|
|
27
|
+
}
|
|
28
|
+
export {
|
|
29
|
+
useBalkOffset
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=barOffset.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/barOffset.ts"],"sourcesContent":["\"use client\";\n\nimport { useEffect } from \"react\";\n\n/**\n * Schuif de plakkende elementen van de SITE onder onze balk vandaan.\n *\n * ── WAAROM PADDING NIET GENOEG IS ─────────────────────────────────────────────────────────\n *\n * De beheerbalk staat `fixed` bovenaan en de pagina wordt omlaag geduwd met padding op `:root`.\n * Dat werkt voor alles wat gewoon meestroomt — maar niet voor een header met\n * `position: sticky; top: 0`. Die plakt aan de bovenkant van het VENSTER, niet aan de bovenkant\n * van de pagina, en schuift dus vrolijk onder onze balk door. Op groenhof was het logo en het hele\n * menu daardoor half afgedekt zodra je inlogde.\n *\n * Het is geen randgeval: een plakkende header is zo'n beetje de standaard, en elke klantsite kiest\n * daar zijn eigen klassenaam en eigen CSS voor. Wij kunnen die CSS niet kennen en willen hem al\n * helemaal niet overschrijven met een gok van een selector.\n *\n * Dus meten we het gewoon: welke elementen plakken of hangen vast aan de bovenrand, en zet daar\n * zolang de balk er staat een `top` van de balkhoogte op. Bij het opruimen gaat de oorspronkelijke\n * waarde terug — de site blijft van de klant.\n *\n * Bewust GEEN elementen aanraken die al verder naar beneden hangen (een plakkende zijbalk, een\n * knop rechtsonder): die zitten onze balk niet in de weg en verplaatsen zou ze juist verschuiven.\n */\nexport function useBalkOffset(actief: boolean, hoogte = 40): void {\n useEffect(() => {\n if (!actief || typeof document === \"undefined\") return;\n\n const aangepast: { el: HTMLElement; top: string }[] = [];\n\n const verschuif = () => {\n for (const el of Array.from(document.querySelectorAll<HTMLElement>(\"body *\"))) {\n // Onze eigen gereedschapslaag overslaan: die staat er met opzet bovenop.\n if (el.closest(\"[data-xicms-tooling]\")) continue;\n if (aangepast.some((a) => a.el === el)) continue;\n const cs = getComputedStyle(el);\n if (cs.position !== \"sticky\" && cs.position !== \"fixed\") continue;\n // `top: auto` betekent \"waar je toevallig staat\" — daar valt niets te corrigeren.\n const top = parseFloat(cs.top);\n if (!Number.isFinite(top) || top > 4) continue;\n aangepast.push({ el, top: el.style.top });\n el.style.top = `${top + hoogte}px`;\n }\n };\n\n verschuif();\n // Een site kan later nog iets plakkends tonen (een aankondigingsbalk die pas na consent komt,\n // een menu dat bij het scrollen van gedrag verandert). Eén meting bij het openen zou dat missen.\n const obs = new MutationObserver(verschuif);\n obs.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: [\"class\", \"style\"] });\n\n return () => {\n obs.disconnect();\n for (const { el, top } of aangepast) el.style.top = top;\n };\n }, [actief, hoogte]);\n}\n"],"mappings":";AAEA,SAAS,iBAAiB;AAwBnB,SAAS,cAAc,QAAiB,SAAS,IAAU;AAChE,YAAU,MAAM;AACd,QAAI,CAAC,UAAU,OAAO,aAAa,YAAa;AAEhD,UAAM,YAAgD,CAAC;AAEvD,UAAM,YAAY,MAAM;AACtB,iBAAW,MAAM,MAAM,KAAK,SAAS,iBAA8B,QAAQ,CAAC,GAAG;AAE7E,YAAI,GAAG,QAAQ,sBAAsB,EAAG;AACxC,YAAI,UAAU,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,EAAG;AACxC,cAAM,KAAK,iBAAiB,EAAE;AAC9B,YAAI,GAAG,aAAa,YAAY,GAAG,aAAa,QAAS;AAEzD,cAAM,MAAM,WAAW,GAAG,GAAG;AAC7B,YAAI,CAAC,OAAO,SAAS,GAAG,KAAK,MAAM,EAAG;AACtC,kBAAU,KAAK,EAAE,IAAI,KAAK,GAAG,MAAM,IAAI,CAAC;AACxC,WAAG,MAAM,MAAM,GAAG,MAAM,MAAM;AAAA,MAChC;AAAA,IACF;AAEA,cAAU;AAGV,UAAM,MAAM,IAAI,iBAAiB,SAAS;AAC1C,QAAI,QAAQ,SAAS,MAAM,EAAE,WAAW,MAAM,SAAS,MAAM,YAAY,MAAM,iBAAiB,CAAC,SAAS,OAAO,EAAE,CAAC;AAEpH,WAAO,MAAM;AACX,UAAI,WAAW;AACf,iBAAW,EAAE,IAAI,IAAI,KAAK,UAAW,IAAG,MAAM,MAAM;AAAA,IACtD;AAAA,EACF,GAAG,CAAC,QAAQ,MAAM,CAAC;AACrB;","names":[]}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect } from "react";
|
|
3
4
|
import { PageRenderer } from "./PageRenderer.js";
|
|
4
5
|
import { useRegistry, BlockDataProvider } from "../blocks/context.js";
|
|
5
6
|
import { useEditor } from "../editor/EditorProvider.js";
|
|
7
|
+
import { isDemoActive, registerSeed } from "../demo/demoStore.js";
|
|
6
8
|
function PublishedPage({
|
|
7
9
|
page,
|
|
8
10
|
siteId,
|
|
@@ -11,6 +13,17 @@ function PublishedPage({
|
|
|
11
13
|
}) {
|
|
12
14
|
const registry = useRegistry();
|
|
13
15
|
const { draftBlocks, editPath } = useEditor();
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (!isDemoActive()) return;
|
|
18
|
+
const pad = huidigPad(window.location.pathname);
|
|
19
|
+
const volledig = page;
|
|
20
|
+
registerSeed(siteId, pad, {
|
|
21
|
+
...volledig,
|
|
22
|
+
title: volledig.title || document.title,
|
|
23
|
+
slug: volledig.slug || (pad.split("/").filter(Boolean).pop() ?? ""),
|
|
24
|
+
path: pad
|
|
25
|
+
});
|
|
26
|
+
}, [siteId, page]);
|
|
14
27
|
const opDezePagina = draftBlocks !== null && typeof window !== "undefined" && huidigPad(window.location.pathname) === editPath;
|
|
15
28
|
const teTonen = opDezePagina ? { ...page, blocks: draftBlocks } : page;
|
|
16
29
|
return /* @__PURE__ */ jsx(BlockDataProvider, { data: blockData, children: /* @__PURE__ */ jsx(PageRenderer, { page: teTonen, registry, siteId, mode }) });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/render/PublishedPage.tsx"],"sourcesContent":["\"use client\";\n\nimport { PageRenderer } from \"./PageRenderer\";\nimport { useRegistry, BlockDataProvider } from \"../blocks/context\";\nimport { useEditor } from \"../editor/EditorProvider\";\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 } = useEditor();\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 <PageRenderer page={teTonen} registry={registry} siteId={siteId} mode={mode} />\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":";
|
|
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 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 } = 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 <PageRenderer page={teTonen} registry={registry} siteId={siteId} mode={mode} />\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":";AAsFM;AApFN,SAAS,iBAAiB;AAC1B,SAAS,oBAAoB;AAC7B,SAAS,aAAa,yBAAyB;AAC/C,SAAS,iBAAiB;AAC1B,SAAS,cAAc,oBAAoB;AAcpC,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP;AACF,GAKG;AACD,QAAM,WAAW,YAAY;AAC7B,QAAM,EAAE,aAAa,SAAS,IAAI,UAAU;AAkB5C,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,WACvB,8BAAC,gBAAa,MAAM,SAAS,UAAoB,QAAgB,MAAY,GAC/E;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
|
@@ -774,6 +774,9 @@
|
|
|
774
774
|
* klantsites veel vaker het element waar eigen CSS aan zit (achtergronden, grid, `min-height`).
|
|
775
775
|
* Hoe minder we aan `body` doen, hoe kleiner de kans dat deze balk iets in de weg legt op een
|
|
776
776
|
* site die we niet kennen. Layout-technisch is het resultaat hetzelfde.
|
|
777
|
+
*
|
|
778
|
+
* Padding alleen is niet genoeg: een `position: sticky; top: 0`-header plakt aan het VENSTER en
|
|
779
|
+
* schuift hier gewoon onderdoor. Die worden apart opzijgezet — zie components/barOffset.ts.
|
|
777
780
|
*/
|
|
778
781
|
:root:has(.xicms-editbar),
|
|
779
782
|
:root:has(.xicms-ve__bar) {
|
package/dist/version.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const CORE_VERSION = true ? "7.8.
|
|
1
|
+
const CORE_VERSION = true ? "7.8.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.8.
|
|
3
|
+
"version": "7.8.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": {
|