@xiwebdesign/xicms-core 6.4.2 → 6.4.4

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.
@@ -4,10 +4,12 @@ import { useEditor, EditorContent } from "@tiptap/react";
4
4
  import StarterKit from "@tiptap/starter-kit";
5
5
  import Link from "@tiptap/extension-link";
6
6
  import Image from "@tiptap/extension-image";
7
+ import Underline from "@tiptap/extension-underline";
7
8
  import DOMPurify from "dompurify";
8
9
  import { useCallback, useEffect } from "react";
9
10
  import { cn } from "../ui/cn";
10
11
  import { MediaPickerButton } from "../media/MediaPicker";
12
+ import { FontSize, FONT_SIZES } from "./fontSizeExtension";
11
13
  function RichTextEditor({
12
14
  value,
13
15
  onChange,
@@ -17,6 +19,8 @@ function RichTextEditor({
17
19
  immediatelyRender: false,
18
20
  extensions: [
19
21
  StarterKit.configure({ heading: { levels: [2, 3, 4] } }),
22
+ Underline,
23
+ FontSize,
20
24
  Link.configure({ openOnClick: false, autolink: true, HTMLAttributes: { rel: "noopener noreferrer nofollow" } }),
21
25
  Image.configure({ inline: false, allowBase64: false, HTMLAttributes: { loading: "lazy", decoding: "async" } })
22
26
  ],
@@ -47,6 +51,13 @@ function RichTextEditor({
47
51
  const alt = window.prompt("Beschrijving (alt-tekst, voor toegankelijkheid)") ?? "";
48
52
  editor.chain().focus().setImage({ src: url, alt }).run();
49
53
  }, [editor]);
54
+ const setFontSize = useCallback(
55
+ (value2) => {
56
+ if (!editor) return;
57
+ editor.chain().focus().setMark("textStyle", { fontSize: value2 || null }).run();
58
+ },
59
+ [editor]
60
+ );
50
61
  if (!editor) return null;
51
62
  const Btn = ({ on, active, label, title }) => /* @__PURE__ */ jsx(
52
63
  "button",
@@ -66,6 +77,19 @@ function RichTextEditor({
66
77
  /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-1 rounded-t-lg border border-slate-300 bg-slate-50 p-1", children: [
67
78
  /* @__PURE__ */ jsx(Btn, { on: () => editor.chain().focus().toggleBold().run(), active: editor.isActive("bold"), label: "B", title: "Vet" }),
68
79
  /* @__PURE__ */ jsx(Btn, { on: () => editor.chain().focus().toggleItalic().run(), active: editor.isActive("italic"), label: "I", title: "Cursief" }),
80
+ /* @__PURE__ */ jsx(Btn, { on: () => editor.chain().focus().toggleUnderline().run(), active: editor.isActive("underline"), label: /* @__PURE__ */ jsx("span", { style: { textDecoration: "underline" }, children: "U" }), title: "Onderstrepen" }),
81
+ /* @__PURE__ */ jsx(Btn, { on: () => editor.chain().focus().toggleStrike().run(), active: editor.isActive("strike"), label: /* @__PURE__ */ jsx("span", { style: { textDecoration: "line-through" }, children: "S" }), title: "Doorstrepen" }),
82
+ /* @__PURE__ */ jsx(
83
+ "select",
84
+ {
85
+ className: "h-8 rounded border border-slate-300 bg-white px-1 text-sm",
86
+ value: editor.getAttributes("textStyle").fontSize ?? "",
87
+ onChange: (e) => setFontSize(e.target.value),
88
+ title: "Lettergrootte",
89
+ "aria-label": "Lettergrootte",
90
+ children: FONT_SIZES.map((s) => /* @__PURE__ */ jsx("option", { value: s.value, children: s.label }, s.label))
91
+ }
92
+ ),
69
93
  /* @__PURE__ */ jsx(Btn, { on: () => editor.chain().focus().toggleHeading({ level: 2 }).run(), active: editor.isActive("heading", { level: 2 }), label: "H2", title: "Kop" }),
70
94
  /* @__PURE__ */ jsx(Btn, { on: () => editor.chain().focus().toggleHeading({ level: 3 }).run(), active: editor.isActive("heading", { level: 3 }), label: "H3", title: "Subkop" }),
71
95
  /* @__PURE__ */ jsx(Btn, { on: () => editor.chain().focus().toggleBulletList().run(), active: editor.isActive("bulletList"), label: "\u2022 Lijst", title: "Opsomming" }),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/editors/RichTextEditor.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEditor, EditorContent } from \"@tiptap/react\";\nimport StarterKit from \"@tiptap/starter-kit\";\nimport Link from \"@tiptap/extension-link\";\nimport Image from \"@tiptap/extension-image\";\nimport DOMPurify from \"dompurify\";\nimport { useCallback, useEffect } from \"react\";\nimport { cn } from \"../ui/cn\";\nimport { MediaPickerButton } from \"../media/MediaPicker\";\n\n/**\n * WYSIWYG-editor (TipTap) met opmaak én afbeeldingen. Output wordt client-side met DOMPurify\n * gesanitized voor preview/opslag; de server sanitized nogmaals bij publiceren (server/sanitize.ts)\n * — nooit op de client vertrouwen. Geef `siteId` mee om afbeeldingen te kunnen uploaden/invoegen.\n */\nexport function RichTextEditor({\n value,\n onChange,\n siteId,\n}: {\n value: string;\n onChange: (html: string) => void;\n siteId?: string;\n}) {\n\n const editor = useEditor({\n immediatelyRender: false,\n extensions: [\n StarterKit.configure({ heading: { levels: [2, 3, 4] } }),\n Link.configure({ openOnClick: false, autolink: true, HTMLAttributes: { rel: \"noopener noreferrer nofollow\" } }),\n Image.configure({ inline: false, allowBase64: false, HTMLAttributes: { loading: \"lazy\", decoding: \"async\" } }),\n ],\n content: value || \"<p></p>\",\n onUpdate: ({ editor }) => onChange(DOMPurify.sanitize(editor.getHTML())),\n editorProps: {\n attributes: { class: \"min-h-[8rem] rounded-b-lg border border-t-0 border-slate-300 bg-white p-3 text-sm\" },\n },\n });\n\n // Externe waarde-wijzigingen (undo/redo in de VisualEditor, taal-wissel in het vertaalpaneel)\n // in de editor doorvoeren: TipTap leest `content` alleen bij init. Zonder deze sync blijft de\n // zichtbare HTML stale terwijl de opgeslagen waarde al veranderd is — data-integriteitsval.\n // Niet syncen terwijl de gebruiker zelf typt (editor.isFocused) — dan is de editor de bron.\n useEffect(() => {\n if (!editor || editor.isFocused) return;\n const current = editor.getHTML();\n const next = value || \"<p></p>\";\n if (current !== next) editor.commands.setContent(next, false); // false = geen onUpdate-echo\n }, [value, editor]);\n\n const setLink = useCallback(() => {\n if (!editor) return;\n const prev = editor.getAttributes(\"link\").href as string | undefined;\n const url = window.prompt(\"Link-URL (leeg = verwijderen)\", prev ?? \"https://\");\n if (url === null) return;\n if (url === \"\") editor.chain().focus().extendMarkRange(\"link\").unsetLink().run();\n else editor.chain().focus().extendMarkRange(\"link\").setLink({ href: url }).run();\n }, [editor]);\n\n const insertImageByUrl = useCallback(() => {\n if (!editor) return;\n const url = window.prompt(\"Afbeeldings-URL (https)\");\n if (!url) return;\n const alt = window.prompt(\"Beschrijving (alt-tekst, voor toegankelijkheid)\") ?? \"\";\n editor.chain().focus().setImage({ src: url, alt }).run();\n }, [editor]);\n\n if (!editor) return null;\n\n const Btn = ({ on, active, label, title }: { on: () => void; active?: boolean; label: string; title?: string }) => (\n <button\n type=\"button\"\n title={title}\n onMouseDown={(e) => e.preventDefault()}\n onClick={on}\n className={cn(\n \"h-8 min-w-8 rounded px-2 text-sm hover:bg-slate-100\",\n active && \"bg-slate-200 font-semibold\",\n )}\n >\n {label}\n </button>\n );\n\n return (\n <div>\n <div className=\"flex flex-wrap gap-1 rounded-t-lg border border-slate-300 bg-slate-50 p-1\">\n <Btn on={() => editor.chain().focus().toggleBold().run()} active={editor.isActive(\"bold\")} label=\"B\" title=\"Vet\" />\n <Btn on={() => editor.chain().focus().toggleItalic().run()} active={editor.isActive(\"italic\")} label=\"I\" title=\"Cursief\" />\n <Btn on={() => editor.chain().focus().toggleHeading({ level: 2 }).run()} active={editor.isActive(\"heading\", { level: 2 })} label=\"H2\" title=\"Kop\" />\n <Btn on={() => editor.chain().focus().toggleHeading({ level: 3 }).run()} active={editor.isActive(\"heading\", { level: 3 })} label=\"H3\" title=\"Subkop\" />\n <Btn on={() => editor.chain().focus().toggleBulletList().run()} active={editor.isActive(\"bulletList\")} label=\"• Lijst\" title=\"Opsomming\" />\n <Btn on={() => editor.chain().focus().toggleOrderedList().run()} active={editor.isActive(\"orderedList\")} label=\"1. Lijst\" title=\"Genummerde lijst\" />\n <Btn on={() => editor.chain().focus().toggleBlockquote().run()} active={editor.isActive(\"blockquote\")} label=\"❝\" title=\"Citaat\" />\n <Btn on={setLink} active={editor.isActive(\"link\")} label=\"🔗\" title=\"Link\" />\n {siteId && (\n <MediaPickerButton\n siteId={siteId}\n label=\"🖼 Afbeelding\"\n variant=\"ghost\"\n onSelect={(m) => editor.chain().focus().setImage({ src: m.url, alt: m.alt ?? \"\" }).run()}\n />\n )}\n <Btn on={insertImageByUrl} label=\"🖼 URL\" title=\"Externe afbeelding via URL invoegen\" />\n </div>\n <EditorContent editor={editor} />\n </div>\n );\n}\n"],"mappings":"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","names":["editor"]}
1
+ {"version":3,"sources":["../../../src/admin/editors/RichTextEditor.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEditor, EditorContent } from \"@tiptap/react\";\nimport StarterKit from \"@tiptap/starter-kit\";\nimport Link from \"@tiptap/extension-link\";\nimport Image from \"@tiptap/extension-image\";\nimport Underline from \"@tiptap/extension-underline\";\nimport DOMPurify from \"dompurify\";\nimport { useCallback, useEffect, type ReactNode } from \"react\";\nimport { cn } from \"../ui/cn\";\nimport { MediaPickerButton } from \"../media/MediaPicker\";\nimport { FontSize, FONT_SIZES } from \"./fontSizeExtension\";\n\n/**\n * WYSIWYG-editor (TipTap) met opmaak én afbeeldingen. Output wordt client-side met DOMPurify\n * gesanitized voor preview/opslag; de server sanitized nogmaals bij publiceren (server/sanitize.ts)\n * — nooit op de client vertrouwen. Geef `siteId` mee om afbeeldingen te kunnen uploaden/invoegen.\n */\nexport function RichTextEditor({\n value,\n onChange,\n siteId,\n}: {\n value: string;\n onChange: (html: string) => void;\n siteId?: string;\n}) {\n\n const editor = useEditor({\n immediatelyRender: false,\n extensions: [\n StarterKit.configure({ heading: { levels: [2, 3, 4] } }),\n Underline,\n FontSize,\n Link.configure({ openOnClick: false, autolink: true, HTMLAttributes: { rel: \"noopener noreferrer nofollow\" } }),\n Image.configure({ inline: false, allowBase64: false, HTMLAttributes: { loading: \"lazy\", decoding: \"async\" } }),\n ],\n content: value || \"<p></p>\",\n onUpdate: ({ editor }) => onChange(DOMPurify.sanitize(editor.getHTML())),\n editorProps: {\n attributes: { class: \"min-h-[8rem] rounded-b-lg border border-t-0 border-slate-300 bg-white p-3 text-sm\" },\n },\n });\n\n // Externe waarde-wijzigingen (undo/redo in de VisualEditor, taal-wissel in het vertaalpaneel)\n // in de editor doorvoeren: TipTap leest `content` alleen bij init. Zonder deze sync blijft de\n // zichtbare HTML stale terwijl de opgeslagen waarde al veranderd is — data-integriteitsval.\n // Niet syncen terwijl de gebruiker zelf typt (editor.isFocused) — dan is de editor de bron.\n useEffect(() => {\n if (!editor || editor.isFocused) return;\n const current = editor.getHTML();\n const next = value || \"<p></p>\";\n if (current !== next) editor.commands.setContent(next, false); // false = geen onUpdate-echo\n }, [value, editor]);\n\n const setLink = useCallback(() => {\n if (!editor) return;\n const prev = editor.getAttributes(\"link\").href as string | undefined;\n const url = window.prompt(\"Link-URL (leeg = verwijderen)\", prev ?? \"https://\");\n if (url === null) return;\n if (url === \"\") editor.chain().focus().extendMarkRange(\"link\").unsetLink().run();\n else editor.chain().focus().extendMarkRange(\"link\").setLink({ href: url }).run();\n }, [editor]);\n\n const insertImageByUrl = useCallback(() => {\n if (!editor) return;\n const url = window.prompt(\"Afbeeldings-URL (https)\");\n if (!url) return;\n const alt = window.prompt(\"Beschrijving (alt-tekst, voor toegankelijkheid)\") ?? \"\";\n editor.chain().focus().setImage({ src: url, alt }).run();\n }, [editor]);\n\n const setFontSize = useCallback(\n (value: string) => {\n if (!editor) return;\n editor.chain().focus().setMark(\"textStyle\", { fontSize: value || null }).run();\n },\n [editor],\n );\n\n if (!editor) return null;\n\n const Btn = ({ on, active, label, title }: { on: () => void; active?: boolean; label: ReactNode; title?: string }) => (\n <button\n type=\"button\"\n title={title}\n onMouseDown={(e) => e.preventDefault()}\n onClick={on}\n className={cn(\n \"h-8 min-w-8 rounded px-2 text-sm hover:bg-slate-100\",\n active && \"bg-slate-200 font-semibold\",\n )}\n >\n {label}\n </button>\n );\n\n return (\n <div>\n <div className=\"flex flex-wrap gap-1 rounded-t-lg border border-slate-300 bg-slate-50 p-1\">\n <Btn on={() => editor.chain().focus().toggleBold().run()} active={editor.isActive(\"bold\")} label=\"B\" title=\"Vet\" />\n <Btn on={() => editor.chain().focus().toggleItalic().run()} active={editor.isActive(\"italic\")} label=\"I\" title=\"Cursief\" />\n <Btn on={() => editor.chain().focus().toggleUnderline().run()} active={editor.isActive(\"underline\")} label={<span style={{ textDecoration: \"underline\" }}>U</span>} title=\"Onderstrepen\" />\n <Btn on={() => editor.chain().focus().toggleStrike().run()} active={editor.isActive(\"strike\")} label={<span style={{ textDecoration: \"line-through\" }}>S</span>} title=\"Doorstrepen\" />\n <select\n className=\"h-8 rounded border border-slate-300 bg-white px-1 text-sm\"\n value={(editor.getAttributes(\"textStyle\").fontSize as string | undefined) ?? \"\"}\n onChange={(e) => setFontSize(e.target.value)}\n title=\"Lettergrootte\"\n aria-label=\"Lettergrootte\"\n >\n {FONT_SIZES.map((s) => (\n <option key={s.label} value={s.value}>\n {s.label}\n </option>\n ))}\n </select>\n <Btn on={() => editor.chain().focus().toggleHeading({ level: 2 }).run()} active={editor.isActive(\"heading\", { level: 2 })} label=\"H2\" title=\"Kop\" />\n <Btn on={() => editor.chain().focus().toggleHeading({ level: 3 }).run()} active={editor.isActive(\"heading\", { level: 3 })} label=\"H3\" title=\"Subkop\" />\n <Btn on={() => editor.chain().focus().toggleBulletList().run()} active={editor.isActive(\"bulletList\")} label=\"• Lijst\" title=\"Opsomming\" />\n <Btn on={() => editor.chain().focus().toggleOrderedList().run()} active={editor.isActive(\"orderedList\")} label=\"1. Lijst\" title=\"Genummerde lijst\" />\n <Btn on={() => editor.chain().focus().toggleBlockquote().run()} active={editor.isActive(\"blockquote\")} label=\"❝\" title=\"Citaat\" />\n <Btn on={setLink} active={editor.isActive(\"link\")} label=\"🔗\" title=\"Link\" />\n {siteId && (\n <MediaPickerButton\n siteId={siteId}\n label=\"🖼 Afbeelding\"\n variant=\"ghost\"\n onSelect={(m) => editor.chain().focus().setImage({ src: m.url, alt: m.alt ?? \"\" }).run()}\n />\n )}\n <Btn on={insertImageByUrl} label=\"🖼 URL\" title=\"Externe afbeelding via URL invoegen\" />\n </div>\n <EditorContent editor={editor} />\n </div>\n );\n}\n"],"mappings":"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","names":["editor","value"]}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Vaste, veilige preset-set — geen vrij invoerveld. `sanitizeRichText` (server/sanitize.ts en de
3
+ * spiegel-kopie in functions/src/lib/sanitize.ts) staat via `allowedStyles` UITSLUITEND exact deze
4
+ * `font-size`-waarden toe; een los tekstveld voor lettergrootte zou een CSS-allowlist met een brede
5
+ * regex vergen (groter injectie-oppervlak) voor weinig extra waarde t.o.v. een paar consistente maten.
6
+ */
7
+ export declare const FONT_SIZES: readonly [{
8
+ readonly label: "Klein";
9
+ readonly value: "0.85rem";
10
+ }, {
11
+ readonly label: "Normaal";
12
+ readonly value: "";
13
+ }, {
14
+ readonly label: "Groot";
15
+ readonly value: "1.25rem";
16
+ }, {
17
+ readonly label: "Extra groot";
18
+ readonly value: "1.5rem";
19
+ }];
20
+ /** `textStyle`-mark (Tiptap) uitgebreid met een `fontSize`-attribuut → `<span style="font-size:...">`. */
21
+ export declare const FontSize: import("@tiptap/core").Mark<import("@tiptap/extension-text-style").TextStyleOptions, any>;
@@ -0,0 +1,27 @@
1
+ import TextStyle from "@tiptap/extension-text-style";
2
+ const FONT_SIZES = [
3
+ { label: "Klein", value: "0.85rem" },
4
+ { label: "Normaal", value: "" },
5
+ { label: "Groot", value: "1.25rem" },
6
+ { label: "Extra groot", value: "1.5rem" }
7
+ ];
8
+ const FontSize = TextStyle.extend({
9
+ addAttributes() {
10
+ return {
11
+ ...this.parent?.(),
12
+ fontSize: {
13
+ default: null,
14
+ parseHTML: (element) => element.style.fontSize || null,
15
+ renderHTML: (attributes) => {
16
+ if (!attributes.fontSize) return {};
17
+ return { style: `font-size: ${attributes.fontSize}` };
18
+ }
19
+ }
20
+ };
21
+ }
22
+ });
23
+ export {
24
+ FONT_SIZES,
25
+ FontSize
26
+ };
27
+ //# sourceMappingURL=fontSizeExtension.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/admin/editors/fontSizeExtension.ts"],"sourcesContent":["import TextStyle from \"@tiptap/extension-text-style\";\n\n/**\n * Vaste, veilige preset-set — geen vrij invoerveld. `sanitizeRichText` (server/sanitize.ts en de\n * spiegel-kopie in functions/src/lib/sanitize.ts) staat via `allowedStyles` UITSLUITEND exact deze\n * `font-size`-waarden toe; een los tekstveld voor lettergrootte zou een CSS-allowlist met een brede\n * regex vergen (groter injectie-oppervlak) voor weinig extra waarde t.o.v. een paar consistente maten.\n */\nexport const FONT_SIZES = [\n { label: \"Klein\", value: \"0.85rem\" },\n { label: \"Normaal\", value: \"\" },\n { label: \"Groot\", value: \"1.25rem\" },\n { label: \"Extra groot\", value: \"1.5rem\" },\n] as const;\n\n/** `textStyle`-mark (Tiptap) uitgebreid met een `fontSize`-attribuut → `<span style=\"font-size:...\">`. */\nexport const FontSize = TextStyle.extend({\n addAttributes() {\n return {\n ...this.parent?.(),\n fontSize: {\n default: null,\n parseHTML: (element) => element.style.fontSize || null,\n renderHTML: (attributes) => {\n if (!attributes.fontSize) return {};\n return { style: `font-size: ${attributes.fontSize}` };\n },\n },\n };\n },\n});\n"],"mappings":"AAAA,OAAO,eAAe;AAQf,MAAM,aAAa;AAAA,EACxB,EAAE,OAAO,SAAS,OAAO,UAAU;AAAA,EACnC,EAAE,OAAO,WAAW,OAAO,GAAG;AAAA,EAC9B,EAAE,OAAO,SAAS,OAAO,UAAU;AAAA,EACnC,EAAE,OAAO,eAAe,OAAO,SAAS;AAC1C;AAGO,MAAM,WAAW,UAAU,OAAO;AAAA,EACvC,gBAAgB;AACd,WAAO;AAAA,MACL,GAAG,KAAK,SAAS;AAAA,MACjB,UAAU;AAAA,QACR,SAAS;AAAA,QACT,WAAW,CAAC,YAAY,QAAQ,MAAM,YAAY;AAAA,QAClD,YAAY,CAAC,eAAe;AAC1B,cAAI,CAAC,WAAW,SAAU,QAAO,CAAC;AAClC,iBAAO,EAAE,OAAO,cAAc,WAAW,QAAQ,GAAG;AAAA,QACtD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF,CAAC;","names":[]}
@@ -480,7 +480,11 @@ function VisualEditorInner({ onClose }) {
480
480
  try {
481
481
  await saveDraft(siteId, draft.id, draftPayload(draft));
482
482
  await publishPage(siteId, draft.id);
483
- toast({ title: "Gepubliceerd", description: "Herlaad de pagina om het live resultaat te zien.", tone: "success" });
483
+ toast({
484
+ title: "Gepubliceerd",
485
+ description: "Live voor bezoekers over ongeveer 15 minuten. Gebruik \u201CBekijk live\u201D hierboven om het direct te controleren.",
486
+ tone: "success"
487
+ });
484
488
  } catch (e) {
485
489
  toast({ title: "Publiceren mislukt", description: e.message, tone: "error" });
486
490
  } finally {
@@ -525,6 +529,17 @@ function VisualEditorInner({ onClose }) {
525
529
  /* @__PURE__ */ jsx("span", { className: "xicms-ve__status", "aria-live": "polite", children: status === "saving" ? "Opslaan\u2026" : status === "saved" ? "\u2713 Opgeslagen" : status === "error" ? "\u26A0 Niet opgeslagen" : "" }),
526
530
  status === "error" && /* @__PURE__ */ jsx(Button, { size: "sm", variant: "secondary", onClick: () => void flushSave().catch(() => setStatus("error")), children: "Opnieuw opslaan" }),
527
531
  /* @__PURE__ */ jsxs("div", { className: "xicms-ve__bar-actions", children: [
532
+ /* @__PURE__ */ jsx(
533
+ "a",
534
+ {
535
+ className: "xicms-btn xicms-btn--ghost",
536
+ href: `${path}${path.includes("?") ? "&" : "?"}preview=1`,
537
+ target: "_blank",
538
+ rel: "noopener noreferrer",
539
+ title: "Toont de gepubliceerde pagina rechtstreeks (zonder cache) \u2014 zo zie je meteen wat je zojuist gepubliceerd hebt.",
540
+ children: "\u{1F441} Bekijk live"
541
+ }
542
+ ),
528
543
  /* @__PURE__ */ jsx(Button, { size: "sm", disabled: publishing || !draft, onClick: onPublish, children: publishing ? "Bezig\u2026" : "Publiceren" }),
529
544
  /* @__PURE__ */ jsx(Button, { size: "sm", variant: "secondary", onClick: handleClose, children: "Sluiten" })
530
545
  ] })
@@ -645,7 +660,21 @@ function VisualEditorInner({ onClose }) {
645
660
  },
646
661
  `${selected._key}:${locale}`
647
662
  )
648
- ] }) : selected ? /* @__PURE__ */ jsx(SelectedEditor, { block: selected, siteId, registry, onChange: (next) => updateByKey(selected._key, next) }, selected._key) : draft && isTranslating ? /* @__PURE__ */ jsxs("div", { className: "grid gap-4", children: [
663
+ ] }) : selected ? /* @__PURE__ */ jsxs("div", { className: "grid gap-3", children: [
664
+ /* @__PURE__ */ jsxs("div", { className: "xicms-ve__panel-head", children: [
665
+ /* @__PURE__ */ jsx(
666
+ "button",
667
+ {
668
+ type: "button",
669
+ className: "xicms-btn xicms-btn--ghost",
670
+ onClick: () => void flushSave().catch(() => setStatus("error")),
671
+ children: "\u2713 Opslaan"
672
+ }
673
+ ),
674
+ /* @__PURE__ */ jsx("button", { type: "button", className: "xicms-btn xicms-btn--ghost", onClick: () => setSelectedKey(null), children: "\u2715 Sluiten" })
675
+ ] }),
676
+ /* @__PURE__ */ jsx(SelectedEditor, { block: selected, siteId, registry, onChange: (next) => updateByKey(selected._key, next) }, selected._key)
677
+ ] }) : draft && isTranslating ? /* @__PURE__ */ jsxs("div", { className: "grid gap-4", children: [
649
678
  /* @__PURE__ */ jsxs("p", { className: "xicms-ve__hint", children: [
650
679
  "Je bewerkt de vertaling ",
651
680
  /* @__PURE__ */ jsx("strong", { children: locale }),
@@ -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 { buildBlockAddOptions } from \"../../blocks/registry\";\nimport DOMPurify from \"dompurify\";\nimport { createPage, getDraftByPath, saveDraft, publishPage, subscribeDraft, subscribeVersions, restoreVersion, type DraftPage } from \"../lib/pagesClient\";\nimport { isDemoActive } from \"../../demo/demoStore\";\nimport { builtinBlockEditors, GenericEditor, type BlockEditorProps } from \"../blockEditors\";\nimport { StyleEditor } from \"../blockEditors/StyleEditor\";\nimport { siteLocales, parseLocaleSlugs, localizeBlocks } from \"../../i18n/overlay\";\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 { 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/** Dev-vangnet (5.0): meld tekstvelden zonder inline-haak — één keer per bloktype. */\nconst inlineCoverageWarned = new Set<string>();\nfunction warnInlineCoverage(type: string, def: { schema: Parameters<typeof introspectToFields>[0]; fields?: Parameters<typeof introspectToFields>[1] } | undefined, host: HTMLElement) {\n if (process.env.NODE_ENV === \"production\" || !def || inlineCoverageWarned.has(type)) return;\n inlineCoverageWarned.add(type);\n const missing = introspectToFields(def.schema, def.fields)\n .filter((f) => f.type === \"text\" || f.type === \"textarea\")\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 const [status, setStatus] = useState<Status>(\"loading\");\n const [selectedKey, setSelectedKey] = useState<string | null>(null);\n const [publishing, setPublishing] = useState(false);\n const [imgPicker, setImgPicker] = useState<{ key: string; field: string } | null>(null);\n const firstSave = useRef(true);\n const canvasRef = useRef<HTMLDivElement>(null);\n const [conflict, setConflict] = useState(false);\n\n // Drag-and-drop herordenen: dragIndex = het gesleepte blok, dropIndex = invoegpunt (0..length).\n // draggingRef pauzeert de inline-bind MutationObserver tijdens het slepen (re-bind-ruis).\n const [dragIndex, setDragIndex] = useState<number | null>(null);\n const [dropIndex, setDropIndex] = useState<number | null>(null);\n const draggingRef = useRef(false);\n const endDrag = useCallback(() => {\n draggingRef.current = false;\n setDragIndex(null);\n setDropIndex(null);\n }, []);\n\n // Altijd de laatste draft + \"zijn er niet-opgeslagen wijzigingen\" bij de hand houden, zodat we\n // bij sluiten/wegnavigeren nog kunnen flushen (de debounce zou de laatste 700ms anders weggooien).\n const draftRef = useRef<DraftPage | null>(null);\n const dirtyRef = useRef(false);\n // Inhoud (stable-stringified) van de laatst succesvol opgeslagen payload — voor conflictdetectie.\n const lastSavedRef = useRef<string | null>(null);\n useEffect(() => {\n draftRef.current = draft;\n }, [draft]);\n\n // ── Undo/redo ──────────────────────────────────────────────────────────────\n // History over draft-snapshots. Tekstedits (per toetsaanslag) worden ge-coalesced: binnen één\n // typ-burst (<800ms) staat het pre-burst-snapshot al op de stack, dus niet opnieuw pushen —\n // Ctrl+Z springt dan per burst terug i.p.v. per letter. `histVersion` forceert re-render van\n // de knop-states (de stacks leven in een ref).\n const historyRef = useRef<{ past: DraftPage[]; future: DraftPage[]; lastPush: number }>({ past: [], future: [], lastPush: 0 });\n const [, setHistVersion] = useState(0);\n\n const pushHistory = useCallback((prev: DraftPage, coalesce: boolean) => {\n const h = historyRef.current;\n const now = Date.now();\n if (!(coalesce && now - h.lastPush < 800 && h.past.length > 0)) {\n h.past.push(prev);\n if (h.past.length > 100) h.past.shift();\n }\n h.lastPush = now;\n h.future = [];\n setHistVersion((v) => v + 1);\n }, []);\n\n /** Enige route waarlangs de draft muteert: bewaart eerst een undo-snapshot. */\n const mutateDraft = useCallback(\n (updater: (d: DraftPage) => DraftPage, opts?: { coalesce?: boolean }) => {\n const d = draftRef.current;\n if (!d) return;\n const next = updater(d);\n if (next === d) return;\n pushHistory(d, opts?.coalesce ?? false);\n draftRef.current = next;\n // 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 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 /** 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 // 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 // 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 // Maak de tekstvelden van het GESELECTEERDE blok ter plekke bewerkbaar (contentEditable) en synct\n // op blur terug — zo bewerk je tekst inline op het canvas i.p.v. via de zijbalk. We binden via een\n // MutationObserver én her-binden zodra React de DOM-node vervangt (re-render), met een dataset-flag\n // zodat we nooit dubbele listeners zetten.\n useEffect(() => {\n if (!selectedKey) return;\n const host = canvasRef.current?.querySelector<HTMLElement>(`[data-ve-content=\"${selectedKey}\"]`);\n if (!host) return;\n const type = blocks.find((b) => b._key === selectedKey)?._type ?? \"\";\n const specs = [...GLOBAL_INLINE];\n warnInlineCoverage(String(type), registry.get(String(type)), host);\n\n const bound = new Set<HTMLElement>();\n const bind = (el: HTMLElement, field: string, html: boolean) => {\n if (el.dataset.veInline === \"1\") return;\n el.dataset.veInline = \"1\";\n el.setAttribute(\"contenteditable\", \"true\");\n el.setAttribute(\"spellcheck\", \"true\");\n el.classList.add(\"xicms-ve-inline\");\n // 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 el.addEventListener(\"blur\", () => commitText(selectedKey, field, html ? DOMPurify.sanitize(el.innerHTML) : (el.textContent ?? \"\")));\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\") el.blur();\n else if (!html && e.key === \"Enter\") { e.preventDefault(); el.blur(); }\n });\n bound.add(el);\n };\n const apply = () => {\n for (const m of specs) {\n host.querySelectorAll<HTMLElement>(m.selector).forEach((el) =>\n bind(el, \"fromData\" in m && m.fromData ? (el.dataset.xicmsEdit as string) : m.field, !!m.html),\n );\n }\n };\n\n apply();\n // Her-bind na elke re-render die de tekstnodes vervangt (niet tijdens slepen: dan muteert de\n // DOM continu door de drop-indicatoren en zou her-binden alleen maar ruis geven).\n const obs = new MutationObserver(() => {\n if (!draggingRef.current) apply();\n });\n obs.observe(host, { childList: true, subtree: true });\n return () => {\n obs.disconnect();\n bound.forEach((el) => {\n el.removeAttribute(\"contenteditable\");\n el.classList.remove(\"xicms-ve-inline\");\n delete el.dataset.veInline;\n });\n };\n }, [selectedKey, blocks, commitText, positionFmtBar]);\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 if (dirtyRef.current) {\n e.preventDefault();\n e.returnValue = \"\";\n }\n };\n const onVisibility = () => {\n // Tab-wissel/minimaliseren: beste moment om pending werk veilig te stellen.\n if (document.visibilityState === \"hidden\") void flushSave().catch(() => {});\n };\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") {\n // Escape sluit alléén de editor als er niets \"boven\" de editor actief is: een open\n // dialog (mediakiezer, bevestiging — Radix rendert [role=dialog] buiten deze shell) of\n // een inline bewerking/invoerveld vangt zijn eigen Escape. Anders verdween bij het\n // sluiten van de mediakiezer meteen de hele editor.\n const t = e.target as HTMLElement | null;\n const inField = !!t && (t.isContentEditable || t.tagName === \"INPUT\" || t.tagName === \"TEXTAREA\" || t.tagName === \"SELECT\");\n const dialogOpen = document.querySelector('[role=\"dialog\"][data-state=\"open\"], [role=\"alertdialog\"][data-state=\"open\"]');\n if (!inField && !dialogOpen) handleClose();\n return;\n }\n // Undo/redo op canvas-niveau. In invoervelden/contentEditable laten we de native\n // tekst-undo van de browser zijn werk doen (anders vecht Ctrl+Z met het veld zelf).\n const t = e.target as HTMLElement | null;\n const inField = !!t && (t.isContentEditable || t.tagName === \"INPUT\" || t.tagName === \"TEXTAREA\" || t.tagName === \"SELECT\");\n if ((e.metaKey || e.ctrlKey) && !inField && (e.key.toLowerCase() === \"z\" || e.key.toLowerCase() === \"y\")) {\n e.preventDefault();\n if (e.key.toLowerCase() === \"y\" || e.shiftKey) redo();\n else undo();\n }\n };\n window.addEventListener(\"beforeunload\", beforeUnload);\n document.addEventListener(\"visibilitychange\", onVisibility);\n window.addEventListener(\"keydown\", onKeyDown);\n return () => {\n window.removeEventListener(\"beforeunload\", beforeUnload);\n document.removeEventListener(\"visibilitychange\", onVisibility);\n window.removeEventListener(\"keydown\", onKeyDown);\n const d = draftRef.current;\n if (d && dirtyRef.current) {\n // Laatste redmiddel bij unmount; log een mislukking i.p.v. stil verliezen.\n void saveDraft(siteId, d.id, draftPayload(d)).catch((err) =>\n console.warn(\"[xicms] autosave bij sluiten mislukt:\", err),\n );\n }\n };\n }, [siteId, handleClose, flushSave, undo, redo]);\n\n function setBlocks(next: Block[], opts?: { coalesce?: boolean }) {\n mutateDraft((d) => ({ ...d, blocks: next }), opts);\n }\n function updateByKey(key: string, next: Record<string, unknown>) {\n // Zijpaneel-edits komen per toetsaanslag binnen → coalesce voor zinnige undo-stappen.\n setBlocks(blocks.map((b) => (b._key === key ? ({ ...(next as Block), _key: b._key, _type: b._type }) : b)), { coalesce: true });\n }\n function move(i: number, dir: -1 | 1) {\n const j = i + dir;\n if (j < 0 || j >= blocks.length) return;\n const copy = [...blocks];\n [copy[i], copy[j]] = [copy[j], copy[i]];\n setBlocks(copy);\n }\n function duplicate(i: number) {\n const copy = [...blocks];\n copy.splice(i + 1, 0, { ...blocks[i], _key: newKey() });\n setBlocks(copy);\n }\n /** 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 if (!draft) 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 (${draft.path}).`, confirmLabel: \"Publiceren\" });\n if (!ok) return;\n setPublishing(true);\n try {\n await saveDraft(siteId, draft.id, draftPayload(draft));\n await publishPage(siteId, draft.id);\n toast({ title: \"Gepubliceerd\", description: \"Herlaad de pagina om het live resultaat te zien.\", tone: \"success\" });\n } catch (e) {\n toast({ title: \"Publiceren mislukt\", description: (e as Error).message, tone: \"error\" });\n } finally {\n setPublishing(false);\n }\n }\n\n const addOptions = useMemo(() => buildBlockAddOptions(registry.values()), [registry]);\n\n const selectedIndex = blocks.findIndex((b) => b._key === selectedKey);\n const selected = selectedIndex >= 0 ? blocks[selectedIndex] : null;\n const canUndo = historyRef.current.past.length > 0;\n const canRedo = historyRef.current.future.length > 0;\n\n /** Laad de nieuwste (remote) versie in de editor — bij een gedetecteerd conflict. */\n const reloadRemote = useCallback(async () => {\n const remote = await getDraftByPath(siteId, path).catch(() => null);\n if (remote) {\n mutateDraft(() => remote);\n lastSavedRef.current = stableStringify(draftPayload(remote));\n dirtyRef.current = false;\n setStatus(\"idle\");\n }\n setConflict(false);\n }, [siteId, path, mutateDraft]);\n\n return (\n <div className=\"xicms-ve__shell\" role=\"dialog\" aria-modal=\"true\" aria-label={`Pagina bewerken: ${path}`}>\n <header className=\"xicms-ve__bar\">\n <strong className=\"xicms-ve__title\">Bewerken · {name}</strong>\n <span className=\"xicms-ve__path\">{path}</span>\n {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={setLocale}\n options={localeCfg.locales.map((l) => ({ value: l, label: l === localeCfg.defaultLocale ? `${l} (standaard)` : l }))}\n />\n </span>\n )}\n <button type=\"button\" className=\"xicms-ve__histbtn\" disabled={!canUndo} onClick={undo} title=\"Ongedaan maken (Ctrl+Z)\" aria-label=\"Ongedaan maken (Ctrl+Z)\">↶</button>\n <button type=\"button\" className=\"xicms-ve__histbtn\" disabled={!canRedo} onClick={redo} title=\"Opnieuw (Ctrl+Shift+Z)\" aria-label=\"Opnieuw (Ctrl+Shift+Z)\">↷</button>\n <span className=\"xicms-ve__status\" aria-live=\"polite\">\n {status === \"saving\" ? \"Opslaan…\" : status === \"saved\" ? \"✓ Opgeslagen\" : status === \"error\" ? \"⚠ Niet opgeslagen\" : \"\"}\n </span>\n {status === \"error\" && (\n <Button size=\"sm\" variant=\"secondary\" onClick={() => void flushSave().catch(() => setStatus(\"error\"))}>\n Opnieuw opslaan\n </Button>\n )}\n <div className=\"xicms-ve__bar-actions\">\n <Button size=\"sm\" disabled={publishing || !draft} onClick={onPublish}>\n {publishing ? \"Bezig…\" : \"Publiceren\"}\n </Button>\n <Button size=\"sm\" variant=\"secondary\" onClick={handleClose}>Sluiten</Button>\n </div>\n </header>\n\n {conflict && (\n <div className=\"xicms-ve__conflict\" role=\"alert\">\n <span>⚠ Deze pagina is intussen ergens anders gewijzigd (andere tab of collega). Doorwerken kan die wijzigingen overschrijven.</span>\n <Button size=\"sm\" onClick={() => void reloadRemote()}>Nieuwste versie laden</Button>\n <Button size=\"sm\" variant=\"secondary\" onClick={() => setConflict(false)}>Mijn versie behouden</Button>\n </div>\n )}\n\n <div className=\"xicms-ve__body\">\n <main className=\"xicms-ve__canvas\">\n {status === \"loading\" && <p className=\"xicms-ve__hint\">Laden…</p>}\n {status === \"none\" && (\n <div className=\"xicms-ve__empty\">\n <EmptyState\n icon=\"📄\"\n title=\"Geen werkversie gevonden\"\n description={`Voor deze pagina (${path}) bestaat nog geen werkversie. Maak hem hier direct aan, of ga naar /beheer → Pagina's.`}\n />\n <p style={{ textAlign: \"center\", marginTop: \"1rem\" }}>\n <button type=\"button\" className=\"xicms-btn\" onClick={() => void handleCreatePage()}>\n + Deze pagina aanmaken\n </button>\n </p>\n </div>\n )}\n {draft && (\n <div className=\"container\" ref={canvasRef}>\n {blocks.length === 0 && (\n <EmptyState icon=\"🧱\" title=\"Nog geen blokken\" description=\"Voeg hieronder je eerste blok toe.\" />\n )}\n {displayBlocks.map((b, i) => {\n const def = registry.get(b._type);\n const Render = def?.Render;\n // Coerce via het schema zodat defaults zijn toegepast (zoals de live pagina via\n // validateBlocks doet). Ruwe drafts missen anders defaults (bv. hero.buttons) en\n // zouden de Render laten throwen. Lukt het parsen niet, val terug op het ruwe blok\n // (de error-boundary hieronder vangt een eventuele fout dan netjes op).\n const safe = (() => {\n const parsed = def?.schema?.safeParse(b);\n return parsed?.success ? (parsed.data as Block) : b;\n })();\n const dropClass = dropIndex === i ? \" is-drop-before\" : dropIndex === i + 1 ? \" is-drop-after\" : \"\";\n return (\n <div\n key={b._key}\n className={`xicms-ve-block${b._key === selectedKey ? \" is-selected\" : \"\"}${dragIndex === i ? \" is-dragging\" : \"\"}${dropClass}`}\n onDragOver={(e) => {\n if (dragIndex === null) return;\n e.preventDefault();\n e.dataTransfer.dropEffect = \"move\";\n const rect = e.currentTarget.getBoundingClientRect();\n setDropIndex(e.clientY < rect.top + rect.height / 2 ? i : i + 1);\n }}\n onDrop={(e) => {\n if (dragIndex === null || dropIndex === null) return;\n e.preventDefault();\n setBlocks(reorderBlocks(blocks, dragIndex, dropIndex));\n endDrag();\n }}\n >\n <div className=\"xicms-ve-block__toolbar\">\n {/* Sleep-greep: apart element (niet het hele blok) zodat slepen nooit botst met\n het inline contentEditable-bewerken; ↑/↓ blijven het toetsenbord-alternatief. */}\n <button\n type=\"button\"\n className=\"xicms-ve-block__drag\"\n draggable\n onDragStart={(e) => {\n draggingRef.current = true;\n setDragIndex(i);\n e.dataTransfer.effectAllowed = \"move\";\n e.dataTransfer.setData(\"text/plain\", String(i));\n const blockEl = e.currentTarget.closest(\".xicms-ve-block\");\n if (blockEl) e.dataTransfer.setDragImage(blockEl, 24, 24);\n }}\n onDragEnd={endDrag}\n aria-label={`Versleep ${def?.label ?? b._type}`}\n title=\"Slepen om te verplaatsen\"\n >\n ⋮⋮\n </button>\n <span className=\"xicms-ve-block__label\">{def?.label ?? b._type}</span>\n <button type=\"button\" onClick={() => setSelectedKey(b._key)} aria-label={`Bewerk ${def?.label ?? b._type}`} title=\"Bewerken\">✏️</button>\n <button type=\"button\" disabled={i === 0} onClick={() => move(i, -1)} aria-label=\"Omhoog\">↑</button>\n <button type=\"button\" disabled={i === blocks.length - 1} onClick={() => move(i, 1)} aria-label=\"Omlaag\">↓</button>\n {b._type === \"symbolRef\" && (\n <button type=\"button\" onClick={() => void detachSymbol(i)} aria-label=\"Symbool loskoppelen\" title=\"Loskoppelen (losse kopie)\">🔗✂</button>\n )}\n <button type=\"button\" onClick={() => duplicate(i)} aria-label=\"Dupliceren\">⎘</button>\n <button type=\"button\" onClick={() => void remove(i)} aria-label=\"Verwijderen\">🗑</button>\n </div>\n {/* Altijd renderen (geen conditie) → geen remount van de inhoud bij selecteren,\n anders verliest het inline-bewerken zijn contentEditable. Bij selectie zet de\n CSS deze overlay op pointer-events:none zodat je de tekst kunt bewerken. */}\n <button type=\"button\" className=\"xicms-ve-block__select\" aria-label={`Selecteer ${def?.label ?? b._type}`} onClick={() => setSelectedKey(b._key)} />\n <div className=\"xicms-ve-block__content\" data-ve-content={b._key}>\n {/* Net als BlockRenderer: elk blok in een per-blok error-boundary zodat één\n falend (maatwerk-)blok de hele editor niet blank maakt. mode=\"preview\". */}\n {Render ? (\n <BlockErrorBoundary mode=\"preview\" type={b._type} blockKey={b._key}>\n {/* Alleen het geselecteerde blok krijgt de inline beeld-vervangen-overlay. */}\n <InlineImageEditProvider onReplace={b._key === selectedKey ? (field) => setImgPicker({ key: b._key, field }) : null}>\n <Render block={safe} siteId={siteId} mode=\"preview\" />\n </InlineImageEditProvider>\n </BlockErrorBoundary>\n ) : (\n <p className=\"xicms-ve__hint\">Onbekend blok: {b._type}</p>\n )}\n </div>\n </div>\n );\n })}\n <div className=\"xicms-ve__inserter\">\n <span>Toevoegen{selected ? \" (na selectie)\" : \"\"}:</span>\n <div className=\"xicms-ve__inserter-select\">\n <Select value=\"\" placeholder=\"Kies een blok…\" onValueChange={(v) => v && addBlock(v)} options={addOptions} />\n </div>\n <PatternInserter onInsert={insertAfterSelected} />\n </div>\n </div>\n )}\n </main>\n\n <aside className=\"xicms-ve__panel\">\n {selected && 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 <SelectedEditor key={selected._key} block={selected} siteId={siteId} registry={registry} onChange={(next) => updateByKey(selected._key, next)} />\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 </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 {/* 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 { buildBlockAddOptions } from \"../../blocks/registry\";\nimport DOMPurify from \"dompurify\";\nimport { createPage, getDraftByPath, saveDraft, publishPage, subscribeDraft, subscribeVersions, restoreVersion, type DraftPage } from \"../lib/pagesClient\";\nimport { isDemoActive } from \"../../demo/demoStore\";\nimport { builtinBlockEditors, GenericEditor, type BlockEditorProps } from \"../blockEditors\";\nimport { StyleEditor } from \"../blockEditors/StyleEditor\";\nimport { siteLocales, parseLocaleSlugs, localizeBlocks } from \"../../i18n/overlay\";\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 { 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/** Dev-vangnet (5.0): meld tekstvelden zonder inline-haak — één keer per bloktype. */\nconst inlineCoverageWarned = new Set<string>();\nfunction warnInlineCoverage(type: string, def: { schema: Parameters<typeof introspectToFields>[0]; fields?: Parameters<typeof introspectToFields>[1] } | undefined, host: HTMLElement) {\n if (process.env.NODE_ENV === \"production\" || !def || inlineCoverageWarned.has(type)) return;\n inlineCoverageWarned.add(type);\n const missing = introspectToFields(def.schema, def.fields)\n .filter((f) => f.type === \"text\" || f.type === \"textarea\")\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 const [status, setStatus] = useState<Status>(\"loading\");\n const [selectedKey, setSelectedKey] = useState<string | null>(null);\n const [publishing, setPublishing] = useState(false);\n const [imgPicker, setImgPicker] = useState<{ key: string; field: string } | null>(null);\n const firstSave = useRef(true);\n const canvasRef = useRef<HTMLDivElement>(null);\n const [conflict, setConflict] = useState(false);\n\n // Drag-and-drop herordenen: dragIndex = het gesleepte blok, dropIndex = invoegpunt (0..length).\n // draggingRef pauzeert de inline-bind MutationObserver tijdens het slepen (re-bind-ruis).\n const [dragIndex, setDragIndex] = useState<number | null>(null);\n const [dropIndex, setDropIndex] = useState<number | null>(null);\n const draggingRef = useRef(false);\n const endDrag = useCallback(() => {\n draggingRef.current = false;\n setDragIndex(null);\n setDropIndex(null);\n }, []);\n\n // Altijd de laatste draft + \"zijn er niet-opgeslagen wijzigingen\" bij de hand houden, zodat we\n // bij sluiten/wegnavigeren nog kunnen flushen (de debounce zou de laatste 700ms anders weggooien).\n const draftRef = useRef<DraftPage | null>(null);\n const dirtyRef = useRef(false);\n // Inhoud (stable-stringified) van de laatst succesvol opgeslagen payload — voor conflictdetectie.\n const lastSavedRef = useRef<string | null>(null);\n useEffect(() => {\n draftRef.current = draft;\n }, [draft]);\n\n // ── Undo/redo ──────────────────────────────────────────────────────────────\n // History over draft-snapshots. Tekstedits (per toetsaanslag) worden ge-coalesced: binnen één\n // typ-burst (<800ms) staat het pre-burst-snapshot al op de stack, dus niet opnieuw pushen —\n // Ctrl+Z springt dan per burst terug i.p.v. per letter. `histVersion` forceert re-render van\n // de knop-states (de stacks leven in een ref).\n const historyRef = useRef<{ past: DraftPage[]; future: DraftPage[]; lastPush: number }>({ past: [], future: [], lastPush: 0 });\n const [, setHistVersion] = useState(0);\n\n const pushHistory = useCallback((prev: DraftPage, coalesce: boolean) => {\n const h = historyRef.current;\n const now = Date.now();\n if (!(coalesce && now - h.lastPush < 800 && h.past.length > 0)) {\n h.past.push(prev);\n if (h.past.length > 100) h.past.shift();\n }\n h.lastPush = now;\n h.future = [];\n setHistVersion((v) => v + 1);\n }, []);\n\n /** Enige route waarlangs de draft muteert: bewaart eerst een undo-snapshot. */\n const mutateDraft = useCallback(\n (updater: (d: DraftPage) => DraftPage, opts?: { coalesce?: boolean }) => {\n const d = draftRef.current;\n if (!d) return;\n const next = updater(d);\n if (next === d) return;\n pushHistory(d, opts?.coalesce ?? false);\n draftRef.current = next;\n // 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 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 /** 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 // 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 // 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 // Maak de tekstvelden van het GESELECTEERDE blok ter plekke bewerkbaar (contentEditable) en synct\n // op blur terug — zo bewerk je tekst inline op het canvas i.p.v. via de zijbalk. We binden via een\n // MutationObserver én her-binden zodra React de DOM-node vervangt (re-render), met een dataset-flag\n // zodat we nooit dubbele listeners zetten.\n useEffect(() => {\n if (!selectedKey) return;\n const host = canvasRef.current?.querySelector<HTMLElement>(`[data-ve-content=\"${selectedKey}\"]`);\n if (!host) return;\n const type = blocks.find((b) => b._key === selectedKey)?._type ?? \"\";\n const specs = [...GLOBAL_INLINE];\n warnInlineCoverage(String(type), registry.get(String(type)), host);\n\n const bound = new Set<HTMLElement>();\n const bind = (el: HTMLElement, field: string, html: boolean) => {\n if (el.dataset.veInline === \"1\") return;\n el.dataset.veInline = \"1\";\n el.setAttribute(\"contenteditable\", \"true\");\n el.setAttribute(\"spellcheck\", \"true\");\n el.classList.add(\"xicms-ve-inline\");\n // 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 el.addEventListener(\"blur\", () => commitText(selectedKey, field, html ? DOMPurify.sanitize(el.innerHTML) : (el.textContent ?? \"\")));\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\") el.blur();\n else if (!html && e.key === \"Enter\") { e.preventDefault(); el.blur(); }\n });\n bound.add(el);\n };\n const apply = () => {\n for (const m of specs) {\n host.querySelectorAll<HTMLElement>(m.selector).forEach((el) =>\n bind(el, \"fromData\" in m && m.fromData ? (el.dataset.xicmsEdit as string) : m.field, !!m.html),\n );\n }\n };\n\n apply();\n // Her-bind na elke re-render die de tekstnodes vervangt (niet tijdens slepen: dan muteert de\n // DOM continu door de drop-indicatoren en zou her-binden alleen maar ruis geven).\n const obs = new MutationObserver(() => {\n if (!draggingRef.current) apply();\n });\n obs.observe(host, { childList: true, subtree: true });\n return () => {\n obs.disconnect();\n bound.forEach((el) => {\n el.removeAttribute(\"contenteditable\");\n el.classList.remove(\"xicms-ve-inline\");\n delete el.dataset.veInline;\n });\n };\n }, [selectedKey, blocks, commitText, positionFmtBar]);\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 if (dirtyRef.current) {\n e.preventDefault();\n e.returnValue = \"\";\n }\n };\n const onVisibility = () => {\n // Tab-wissel/minimaliseren: beste moment om pending werk veilig te stellen.\n if (document.visibilityState === \"hidden\") void flushSave().catch(() => {});\n };\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") {\n // Escape sluit alléén de editor als er niets \"boven\" de editor actief is: een open\n // dialog (mediakiezer, bevestiging — Radix rendert [role=dialog] buiten deze shell) of\n // een inline bewerking/invoerveld vangt zijn eigen Escape. Anders verdween bij het\n // sluiten van de mediakiezer meteen de hele editor.\n const t = e.target as HTMLElement | null;\n const inField = !!t && (t.isContentEditable || t.tagName === \"INPUT\" || t.tagName === \"TEXTAREA\" || t.tagName === \"SELECT\");\n const dialogOpen = document.querySelector('[role=\"dialog\"][data-state=\"open\"], [role=\"alertdialog\"][data-state=\"open\"]');\n if (!inField && !dialogOpen) handleClose();\n return;\n }\n // Undo/redo op canvas-niveau. In invoervelden/contentEditable laten we de native\n // tekst-undo van de browser zijn werk doen (anders vecht Ctrl+Z met het veld zelf).\n const t = e.target as HTMLElement | null;\n const inField = !!t && (t.isContentEditable || t.tagName === \"INPUT\" || t.tagName === \"TEXTAREA\" || t.tagName === \"SELECT\");\n if ((e.metaKey || e.ctrlKey) && !inField && (e.key.toLowerCase() === \"z\" || e.key.toLowerCase() === \"y\")) {\n e.preventDefault();\n if (e.key.toLowerCase() === \"y\" || e.shiftKey) redo();\n else undo();\n }\n };\n window.addEventListener(\"beforeunload\", beforeUnload);\n document.addEventListener(\"visibilitychange\", onVisibility);\n window.addEventListener(\"keydown\", onKeyDown);\n return () => {\n window.removeEventListener(\"beforeunload\", beforeUnload);\n document.removeEventListener(\"visibilitychange\", onVisibility);\n window.removeEventListener(\"keydown\", onKeyDown);\n const d = draftRef.current;\n if (d && dirtyRef.current) {\n // Laatste redmiddel bij unmount; log een mislukking i.p.v. stil verliezen.\n void saveDraft(siteId, d.id, draftPayload(d)).catch((err) =>\n console.warn(\"[xicms] autosave bij sluiten mislukt:\", err),\n );\n }\n };\n }, [siteId, handleClose, flushSave, undo, redo]);\n\n function setBlocks(next: Block[], opts?: { coalesce?: boolean }) {\n mutateDraft((d) => ({ ...d, blocks: next }), opts);\n }\n function updateByKey(key: string, next: Record<string, unknown>) {\n // Zijpaneel-edits komen per toetsaanslag binnen → coalesce voor zinnige undo-stappen.\n setBlocks(blocks.map((b) => (b._key === key ? ({ ...(next as Block), _key: b._key, _type: b._type }) : b)), { coalesce: true });\n }\n function move(i: number, dir: -1 | 1) {\n const j = i + dir;\n if (j < 0 || j >= blocks.length) return;\n const copy = [...blocks];\n [copy[i], copy[j]] = [copy[j], copy[i]];\n setBlocks(copy);\n }\n function duplicate(i: number) {\n const copy = [...blocks];\n copy.splice(i + 1, 0, { ...blocks[i], _key: newKey() });\n setBlocks(copy);\n }\n /** 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 if (!draft) 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 (${draft.path}).`, confirmLabel: \"Publiceren\" });\n if (!ok) return;\n setPublishing(true);\n try {\n await saveDraft(siteId, draft.id, draftPayload(draft));\n await publishPage(siteId, draft.id);\n toast({\n title: \"Gepubliceerd\",\n description: \"Live voor bezoekers over ongeveer 15 minuten. Gebruik “Bekijk live” hierboven om het direct te controleren.\",\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 className=\"xicms-ve__shell\" role=\"dialog\" aria-modal=\"true\" aria-label={`Pagina bewerken: ${path}`}>\n <header className=\"xicms-ve__bar\">\n <strong className=\"xicms-ve__title\">Bewerken · {name}</strong>\n <span className=\"xicms-ve__path\">{path}</span>\n {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={setLocale}\n options={localeCfg.locales.map((l) => ({ value: l, label: l === localeCfg.defaultLocale ? `${l} (standaard)` : l }))}\n />\n </span>\n )}\n <button type=\"button\" className=\"xicms-ve__histbtn\" disabled={!canUndo} onClick={undo} title=\"Ongedaan maken (Ctrl+Z)\" aria-label=\"Ongedaan maken (Ctrl+Z)\">↶</button>\n <button type=\"button\" className=\"xicms-ve__histbtn\" disabled={!canRedo} onClick={redo} title=\"Opnieuw (Ctrl+Shift+Z)\" aria-label=\"Opnieuw (Ctrl+Shift+Z)\">↷</button>\n <span className=\"xicms-ve__status\" aria-live=\"polite\">\n {status === \"saving\" ? \"Opslaan…\" : status === \"saved\" ? \"✓ Opgeslagen\" : status === \"error\" ? \"⚠ Niet opgeslagen\" : \"\"}\n </span>\n {status === \"error\" && (\n <Button size=\"sm\" variant=\"secondary\" onClick={() => void flushSave().catch(() => setStatus(\"error\"))}>\n Opnieuw opslaan\n </Button>\n )}\n <div className=\"xicms-ve__bar-actions\">\n {/* Publiceren zet de pagina live in Firestore, maar de site zelf leest voor bezoekers uit\n een gecommitte bundel die pas na ~5-15 min meekomt (debounced deploy). `?preview=1`\n laat de pagina de gepubliceerde stand rechtstreeks en ongecachet tonen, zodat je als\n beheerder je eigen publicatie meteen kunt controleren zonder te hoeven wachten. */}\n <a\n className=\"xicms-btn xicms-btn--ghost\"\n href={`${path}${path.includes(\"?\") ? \"&\" : \"?\"}preview=1`}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n title=\"Toont de gepubliceerde pagina rechtstreeks (zonder cache) — zo zie je meteen wat je zojuist gepubliceerd hebt.\"\n >\n 👁 Bekijk live\n </a>\n <Button size=\"sm\" disabled={publishing || !draft} onClick={onPublish}>\n {publishing ? \"Bezig…\" : \"Publiceren\"}\n </Button>\n <Button size=\"sm\" variant=\"secondary\" onClick={handleClose}>Sluiten</Button>\n </div>\n </header>\n\n {conflict && (\n <div className=\"xicms-ve__conflict\" role=\"alert\">\n <span>⚠ Deze pagina is intussen ergens anders gewijzigd (andere tab of collega). Doorwerken kan die wijzigingen overschrijven.</span>\n <Button size=\"sm\" onClick={() => void reloadRemote()}>Nieuwste versie laden</Button>\n <Button size=\"sm\" variant=\"secondary\" onClick={() => setConflict(false)}>Mijn versie behouden</Button>\n </div>\n )}\n\n <div className=\"xicms-ve__body\">\n <main className=\"xicms-ve__canvas\">\n {status === \"loading\" && <p className=\"xicms-ve__hint\">Laden…</p>}\n {status === \"none\" && (\n <div className=\"xicms-ve__empty\">\n <EmptyState\n icon=\"📄\"\n title=\"Geen werkversie gevonden\"\n description={`Voor deze pagina (${path}) bestaat nog geen werkversie. Maak hem hier direct aan, of ga naar /beheer → Pagina's.`}\n />\n <p style={{ textAlign: \"center\", marginTop: \"1rem\" }}>\n <button type=\"button\" className=\"xicms-btn\" onClick={() => void handleCreatePage()}>\n + Deze pagina aanmaken\n </button>\n </p>\n </div>\n )}\n {draft && (\n <div className=\"container\" ref={canvasRef}>\n {blocks.length === 0 && (\n <EmptyState icon=\"🧱\" title=\"Nog geen blokken\" description=\"Voeg hieronder je eerste blok toe.\" />\n )}\n {displayBlocks.map((b, i) => {\n const def = registry.get(b._type);\n const Render = def?.Render;\n // Coerce via het schema zodat defaults zijn toegepast (zoals de live pagina via\n // validateBlocks doet). Ruwe drafts missen anders defaults (bv. hero.buttons) en\n // zouden de Render laten throwen. Lukt het parsen niet, val terug op het ruwe blok\n // (de error-boundary hieronder vangt een eventuele fout dan netjes op).\n const safe = (() => {\n const parsed = def?.schema?.safeParse(b);\n return parsed?.success ? (parsed.data as Block) : b;\n })();\n const dropClass = dropIndex === i ? \" is-drop-before\" : dropIndex === i + 1 ? \" is-drop-after\" : \"\";\n return (\n <div\n key={b._key}\n className={`xicms-ve-block${b._key === selectedKey ? \" is-selected\" : \"\"}${dragIndex === i ? \" is-dragging\" : \"\"}${dropClass}`}\n onDragOver={(e) => {\n if (dragIndex === null) return;\n e.preventDefault();\n e.dataTransfer.dropEffect = \"move\";\n const rect = e.currentTarget.getBoundingClientRect();\n setDropIndex(e.clientY < rect.top + rect.height / 2 ? i : i + 1);\n }}\n onDrop={(e) => {\n if (dragIndex === null || dropIndex === null) return;\n e.preventDefault();\n setBlocks(reorderBlocks(blocks, dragIndex, dropIndex));\n endDrag();\n }}\n >\n <div className=\"xicms-ve-block__toolbar\">\n {/* Sleep-greep: apart element (niet het hele blok) zodat slepen nooit botst met\n het inline contentEditable-bewerken; ↑/↓ blijven het toetsenbord-alternatief. */}\n <button\n type=\"button\"\n className=\"xicms-ve-block__drag\"\n draggable\n onDragStart={(e) => {\n draggingRef.current = true;\n setDragIndex(i);\n e.dataTransfer.effectAllowed = \"move\";\n e.dataTransfer.setData(\"text/plain\", String(i));\n const blockEl = e.currentTarget.closest(\".xicms-ve-block\");\n if (blockEl) e.dataTransfer.setDragImage(blockEl, 24, 24);\n }}\n onDragEnd={endDrag}\n aria-label={`Versleep ${def?.label ?? b._type}`}\n title=\"Slepen om te verplaatsen\"\n >\n ⋮⋮\n </button>\n <span className=\"xicms-ve-block__label\">{def?.label ?? b._type}</span>\n <button type=\"button\" onClick={() => setSelectedKey(b._key)} aria-label={`Bewerk ${def?.label ?? b._type}`} title=\"Bewerken\">✏️</button>\n <button type=\"button\" disabled={i === 0} onClick={() => move(i, -1)} aria-label=\"Omhoog\">↑</button>\n <button type=\"button\" disabled={i === blocks.length - 1} onClick={() => move(i, 1)} aria-label=\"Omlaag\">↓</button>\n {b._type === \"symbolRef\" && (\n <button type=\"button\" onClick={() => void detachSymbol(i)} aria-label=\"Symbool loskoppelen\" title=\"Loskoppelen (losse kopie)\">🔗✂</button>\n )}\n <button type=\"button\" onClick={() => duplicate(i)} aria-label=\"Dupliceren\">⎘</button>\n <button type=\"button\" onClick={() => void remove(i)} aria-label=\"Verwijderen\">🗑</button>\n </div>\n {/* Altijd renderen (geen conditie) → geen remount van de inhoud bij selecteren,\n anders verliest het inline-bewerken zijn contentEditable. Bij selectie zet de\n CSS deze overlay op pointer-events:none zodat je de tekst kunt bewerken. */}\n <button type=\"button\" className=\"xicms-ve-block__select\" aria-label={`Selecteer ${def?.label ?? b._type}`} onClick={() => setSelectedKey(b._key)} />\n <div className=\"xicms-ve-block__content\" data-ve-content={b._key}>\n {/* Net als BlockRenderer: elk blok in een per-blok error-boundary zodat één\n falend (maatwerk-)blok de hele editor niet blank maakt. mode=\"preview\". */}\n {Render ? (\n <BlockErrorBoundary mode=\"preview\" type={b._type} blockKey={b._key}>\n {/* Alleen het geselecteerde blok krijgt de inline beeld-vervangen-overlay. */}\n <InlineImageEditProvider onReplace={b._key === selectedKey ? (field) => setImgPicker({ key: b._key, field }) : null}>\n <Render block={safe} siteId={siteId} mode=\"preview\" />\n </InlineImageEditProvider>\n </BlockErrorBoundary>\n ) : (\n <p className=\"xicms-ve__hint\">Onbekend blok: {b._type}</p>\n )}\n </div>\n </div>\n );\n })}\n <div className=\"xicms-ve__inserter\">\n <span>Toevoegen{selected ? \" (na selectie)\" : \"\"}:</span>\n <div className=\"xicms-ve__inserter-select\">\n <Select value=\"\" placeholder=\"Kies een blok…\" onValueChange={(v) => v && addBlock(v)} options={addOptions} />\n </div>\n <PatternInserter onInsert={insertAfterSelected} />\n </div>\n </div>\n )}\n </main>\n\n <aside className=\"xicms-ve__panel\">\n {selected && 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 <button\n type=\"button\"\n className=\"xicms-btn xicms-btn--ghost\"\n onClick={() => void flushSave().catch(() => setStatus(\"error\"))}\n >\n ✓ Opslaan\n </button>\n <button type=\"button\" className=\"xicms-btn xicms-btn--ghost\" onClick={() => setSelectedKey(null)}>\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 </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 {/* 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"]}
package/dist/admin.css CHANGED
@@ -1 +1 @@
1
- .container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.xicms-admin .visible{visibility:visible}.xicms-admin .static{position:static}.xicms-admin .fixed{position:fixed}.xicms-admin .absolute{position:absolute}.xicms-admin .relative{position:relative}.xicms-admin .sticky{position:sticky}.xicms-admin .inset-0{inset:0}.xicms-admin .-right-2{right:-.5rem}.xicms-admin .-top-2{top:-.5rem}.xicms-admin .z-\[10010\]{z-index:10010}.xicms-admin .mb-1{margin-bottom:.25rem}.xicms-admin .mb-2{margin-bottom:.5rem}.xicms-admin .mb-3{margin-bottom:.75rem}.xicms-admin .ml-1{margin-left:.25rem}.xicms-admin .ml-auto{margin-left:auto}.xicms-admin .mr-1{margin-right:.25rem}.xicms-admin .mr-2{margin-right:.5rem}.xicms-admin .mt-0\.5{margin-top:.125rem}.xicms-admin .mt-1{margin-top:.25rem}.xicms-admin .mt-2{margin-top:.5rem}.xicms-admin .mt-3{margin-top:.75rem}.xicms-admin .mt-4{margin-top:1rem}.xicms-admin .mt-5{margin-top:1.25rem}.xicms-admin .mt-6{margin-top:1.5rem}.xicms-admin .\!block{display:block!important}.xicms-admin .block{display:block}.xicms-admin .inline{display:inline}.xicms-admin .flex{display:flex}.xicms-admin .inline-flex{display:inline-flex}.xicms-admin .table{display:table}.xicms-admin .grid{display:grid}.xicms-admin .hidden{display:none}.xicms-admin .aspect-square{aspect-ratio:1/1}.xicms-admin .h-10{height:2.5rem}.xicms-admin .h-14{height:3.5rem}.xicms-admin .h-16{height:4rem}.xicms-admin .h-28{height:7rem}.xicms-admin .h-32{height:8rem}.xicms-admin .h-5{height:1.25rem}.xicms-admin .h-6{height:1.5rem}.xicms-admin .h-8{height:2rem}.xicms-admin .h-9{height:2.25rem}.xicms-admin .h-\[70vh\]{height:70vh}.xicms-admin .h-fit{height:-moz-fit-content;height:fit-content}.xicms-admin .max-h-\[55vh\]{max-height:55vh}.xicms-admin .max-h-\[60vh\]{max-height:60vh}.xicms-admin .min-h-\[8rem\]{min-height:8rem}.xicms-admin .w-14{width:3.5rem}.xicms-admin .w-16{width:4rem}.xicms-admin .w-20{width:5rem}.xicms-admin .w-24{width:6rem}.xicms-admin .w-28{width:7rem}.xicms-admin .w-32{width:8rem}.xicms-admin .w-36{width:9rem}.xicms-admin .w-5{width:1.25rem}.xicms-admin .w-56{width:14rem}.xicms-admin .w-6{width:1.5rem}.xicms-admin .w-8{width:2rem}.xicms-admin .w-fit{width:-moz-fit-content;width:fit-content}.xicms-admin .w-full{width:100%}.xicms-admin .min-w-0{min-width:0}.xicms-admin .min-w-8{min-width:2rem}.xicms-admin .max-w-2xl{max-width:42rem}.xicms-admin .max-w-3xl{max-width:48rem}.xicms-admin .max-w-xl{max-width:36rem}.xicms-admin .max-w-xs{max-width:20rem}.xicms-admin .flex-1{flex:1 1 0%}.xicms-admin .shrink-0{flex-shrink:0}.xicms-admin .-translate-x-1\/2{--tw-translate-x:-50%}.xicms-admin .-translate-x-1\/2,.xicms-admin .transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.xicms-admin .cursor-pointer{cursor:pointer}.xicms-admin .resize{resize:both}.xicms-admin .list-decimal{list-style-type:decimal}.xicms-admin .list-disc{list-style-type:disc}.xicms-admin .grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xicms-admin .grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xicms-admin .grid-cols-\[120px_1fr\]{grid-template-columns:120px 1fr}.xicms-admin .flex-col{flex-direction:column}.xicms-admin .flex-wrap{flex-wrap:wrap}.xicms-admin .place-items-center{place-items:center}.xicms-admin .items-start{align-items:flex-start}.xicms-admin .items-end{align-items:flex-end}.xicms-admin .items-center{align-items:center}.xicms-admin .justify-end{justify-content:flex-end}.xicms-admin .justify-center{justify-content:center}.xicms-admin .justify-between{justify-content:space-between}.xicms-admin .gap-0\.5{gap:.125rem}.xicms-admin .gap-1{gap:.25rem}.xicms-admin .gap-1\.5{gap:.375rem}.xicms-admin .gap-2{gap:.5rem}.xicms-admin .gap-3{gap:.75rem}.xicms-admin .gap-4{gap:1rem}.xicms-admin .gap-6{gap:1.5rem}.xicms-admin .self-end{align-self:flex-end}.xicms-admin .self-center{align-self:center}.xicms-admin .overflow-auto{overflow:auto}.xicms-admin .overflow-hidden{overflow:hidden}.xicms-admin .overflow-x-auto{overflow-x:auto}.xicms-admin .overflow-y-auto{overflow-y:auto}.xicms-admin .truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.xicms-admin .whitespace-pre-wrap{white-space:pre-wrap}.xicms-admin .rounded{border-radius:.25rem}.xicms-admin .rounded-full{border-radius:9999px}.xicms-admin .rounded-lg{border-radius:.5rem}.xicms-admin .rounded-md{border-radius:.375rem}.xicms-admin .rounded-xl{border-radius:.75rem}.xicms-admin .rounded-b-lg{border-bottom-right-radius:.5rem;border-bottom-left-radius:.5rem}.xicms-admin .rounded-t{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.xicms-admin .rounded-t-lg{border-top-left-radius:.5rem;border-top-right-radius:.5rem}.xicms-admin .border{border-width:1px}.xicms-admin .border-b{border-bottom-width:1px}.xicms-admin .border-t{border-top-width:1px}.xicms-admin .border-t-0{border-top-width:0}.xicms-admin .border-amber-300{--tw-border-opacity:1;border-color:rgb(252 211 77/var(--tw-border-opacity,1))}.xicms-admin .border-brand-500{border-color:var(--xicms-primary,#4f46e5)}.xicms-admin .border-red-200{--tw-border-opacity:1;border-color:rgb(254 202 202/var(--tw-border-opacity,1))}.xicms-admin .border-slate-100{--tw-border-opacity:1;border-color:rgb(241 245 249/var(--tw-border-opacity,1))}.xicms-admin .border-slate-200{--tw-border-opacity:1;border-color:rgb(226 232 240/var(--tw-border-opacity,1))}.xicms-admin .border-slate-300{--tw-border-opacity:1;border-color:rgb(203 213 225/var(--tw-border-opacity,1))}.xicms-admin .bg-amber-50{--tw-bg-opacity:1;background-color:rgb(255 251 235/var(--tw-bg-opacity,1))}.xicms-admin .bg-black\/50{background-color:rgba(0,0,0,.5)}.xicms-admin .bg-brand-50{background-color:color-mix(in srgb,var(--xicms-primary,#4f46e5) 12%,#fff)}.xicms-admin .bg-brand-500{background-color:var(--xicms-primary,#4f46e5)}.xicms-admin .bg-red-50{--tw-bg-opacity:1;background-color:rgb(254 242 242/var(--tw-bg-opacity,1))}.xicms-admin .bg-slate-100{--tw-bg-opacity:1;background-color:rgb(241 245 249/var(--tw-bg-opacity,1))}.xicms-admin .bg-slate-200{--tw-bg-opacity:1;background-color:rgb(226 232 240/var(--tw-bg-opacity,1))}.xicms-admin .bg-slate-50{--tw-bg-opacity:1;background-color:rgb(248 250 252/var(--tw-bg-opacity,1))}.xicms-admin .bg-slate-700{--tw-bg-opacity:1;background-color:rgb(51 65 85/var(--tw-bg-opacity,1))}.xicms-admin .bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.xicms-admin .object-cover{-o-object-fit:cover;object-fit:cover}.xicms-admin .p-0\.5{padding:.125rem}.xicms-admin .p-1{padding:.25rem}.xicms-admin .p-2{padding:.5rem}.xicms-admin .p-3{padding:.75rem}.xicms-admin .p-4{padding:1rem}.xicms-admin .p-5{padding:1.25rem}.xicms-admin .p-6{padding:1.5rem}.xicms-admin .px-1{padding-left:.25rem;padding-right:.25rem}.xicms-admin .px-2{padding-left:.5rem;padding-right:.5rem}.xicms-admin .px-3{padding-left:.75rem;padding-right:.75rem}.xicms-admin .px-4{padding-left:1rem;padding-right:1rem}.xicms-admin .py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.xicms-admin .py-1{padding-top:.25rem;padding-bottom:.25rem}.xicms-admin .py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.xicms-admin .py-2{padding-top:.5rem;padding-bottom:.5rem}.xicms-admin .py-3{padding-top:.75rem;padding-bottom:.75rem}.xicms-admin .py-8{padding-top:2rem;padding-bottom:2rem}.xicms-admin .pl-5{padding-left:1.25rem}.xicms-admin .pr-2{padding-right:.5rem}.xicms-admin .pt-4{padding-top:1rem}.xicms-admin .pt-5{padding-top:1.25rem}.xicms-admin .text-left{text-align:left}.xicms-admin .text-center{text-align:center}.xicms-admin .text-right{text-align:right}.xicms-admin .align-top{vertical-align:top}.xicms-admin .font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.xicms-admin .text-3xl{font-size:1.875rem;line-height:2.25rem}.xicms-admin .text-\[11px\]{font-size:11px}.xicms-admin .text-base{font-size:1rem;line-height:1.5rem}.xicms-admin .text-lg{font-size:1.125rem;line-height:1.75rem}.xicms-admin .text-sm{font-size:.875rem;line-height:1.25rem}.xicms-admin .text-xl{font-size:1.25rem;line-height:1.75rem}.xicms-admin .text-xs{font-size:.75rem;line-height:1rem}.xicms-admin .font-bold{font-weight:700}.xicms-admin .font-medium{font-weight:500}.xicms-admin .font-semibold{font-weight:600}.xicms-admin .uppercase{text-transform:uppercase}.xicms-admin .italic{font-style:italic}.xicms-admin .tracking-wide{letter-spacing:.025em}.xicms-admin .text-amber-600{--tw-text-opacity:1;color:rgb(217 119 6/var(--tw-text-opacity,1))}.xicms-admin .text-amber-700{--tw-text-opacity:1;color:rgb(180 83 9/var(--tw-text-opacity,1))}.xicms-admin .text-amber-800{--tw-text-opacity:1;color:rgb(146 64 14/var(--tw-text-opacity,1))}.xicms-admin .text-brand-700{color:color-mix(in srgb,var(--xicms-primary,#4f46e5) 82%,#000)}.xicms-admin .text-red-600{--tw-text-opacity:1;color:rgb(220 38 38/var(--tw-text-opacity,1))}.xicms-admin .text-red-700{--tw-text-opacity:1;color:rgb(185 28 28/var(--tw-text-opacity,1))}.xicms-admin .text-slate-400{--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity,1))}.xicms-admin .text-slate-500{--tw-text-opacity:1;color:rgb(100 116 139/var(--tw-text-opacity,1))}.xicms-admin .text-slate-600{--tw-text-opacity:1;color:rgb(71 85 105/var(--tw-text-opacity,1))}.xicms-admin .text-slate-700{--tw-text-opacity:1;color:rgb(51 65 85/var(--tw-text-opacity,1))}.xicms-admin .text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.xicms-admin .underline{text-decoration-line:underline}.xicms-admin .opacity-70{opacity:.7}.xicms-admin .shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.xicms-admin .shadow-sm,.xicms-admin .shadow-xl{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.xicms-admin .shadow-xl{--tw-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1);--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color)}.xicms-admin .outline-none{outline:2px solid transparent;outline-offset:2px}.xicms-admin .outline{outline-style:solid}.xicms-admin .blur{--tw-blur:blur(8px)}.xicms-admin .blur,.xicms-admin .filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.xicms-admin .transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.xicms-admin .transition-\[filter\]{transition-property:filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.xicms-admin,.xicms-admin *,.xicms-admin :after,.xicms-admin :before{box-sizing:border-box}.xicms-admin{font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;color:#0f172a;line-height:1.5;-webkit-font-smoothing:antialiased;color-scheme:light;background:#fff;--color-background-body:#fff;--color-background-surface:#fff;--color-background-card:#fff;--color-background-muted:#f1f5f9;--color-text-primary:#0f172a;--color-text-secondary:#475569;--color-border:#e2e8f0;--color-accent:#4f46e5;--color-on-accent:#fff;--color-error:#dc2626}.xicms-admin figure,.xicms-admin h1,.xicms-admin h2,.xicms-admin h3,.xicms-admin p{margin:0}.xicms-admin button{font:inherit;cursor:pointer}.xicms-admin input,.xicms-admin select,.xicms-admin textarea{font:inherit;color:inherit}.xicms-admin .ProseMirror{min-height:8rem;outline:none}.xicms-admin .ProseMirror p{margin:0 0 .6rem}.xicms-admin .ProseMirror:focus{outline:none}@media (prefers-reduced-motion:reduce){.xicms-admin *,.xicms-admin :after,.xicms-admin :before,.xicms-ve *,.xicms-ve :after,.xicms-ve :before{animation-duration:.001ms!important;animation-iteration-count:1!important;transition-duration:.001ms!important}}.xicms-admin .last\:border-0:last-child{border-width:0}.xicms-admin .hover\:border-brand-500:hover{border-color:var(--xicms-primary,#4f46e5)}.xicms-admin .hover\:bg-slate-100:hover{--tw-bg-opacity:1;background-color:rgb(241 245 249/var(--tw-bg-opacity,1))}.xicms-admin .hover\:bg-slate-50:hover{--tw-bg-opacity:1;background-color:rgb(248 250 252/var(--tw-bg-opacity,1))}.xicms-admin .hover\:text-slate-700:hover{--tw-text-opacity:1;color:rgb(51 65 85/var(--tw-text-opacity,1))}.xicms-admin .hover\:text-slate-900:hover{--tw-text-opacity:1;color:rgb(15 23 42/var(--tw-text-opacity,1))}.xicms-admin .hover\:underline:hover{text-decoration-line:underline}.xicms-admin .hover\:brightness-95:hover{--tw-brightness:brightness(.95);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.xicms-admin .focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.xicms-admin .focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.xicms-admin .focus\:ring-indigo-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(99 102 241/var(--tw-ring-opacity,1))}.xicms-admin .focus-visible\:outline-none:focus-visible{outline:2px solid transparent;outline-offset:2px}.xicms-admin .focus-visible\:ring-2:focus-visible{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.xicms-admin .focus-visible\:ring-brand-500:focus-visible{--tw-ring-color:var(--xicms-primary,#4f46e5)}.xicms-admin .disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.xicms-admin .disabled\:opacity-50:disabled{opacity:.5}.xicms-admin :is(.group:hover .group-hover\:opacity-80){opacity:.8}.xicms-admin .data-\[state\=active\]\:border-b-2[data-state=active]{border-bottom-width:2px}.xicms-admin .data-\[state\=active\]\:border-brand-600[data-state=active]{border-color:var(--xicms-primary,#4f46e5)}.xicms-admin .data-\[state\=active\]\:text-brand-700[data-state=active]{color:color-mix(in srgb,var(--xicms-primary,#4f46e5) 82%,#000)}@media (min-width:640px){.xicms-admin .sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xicms-admin .sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xicms-admin .sm\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}@media (min-width:768px){.xicms-admin .md\:col-span-5{grid-column:span 5/span 5}.xicms-admin .md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xicms-admin .md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xicms-admin .md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xicms-admin .md\:grid-cols-\[1fr_110px\]{grid-template-columns:1fr 110px}.xicms-admin .md\:grid-cols-\[1fr_130px_90px_110px_auto\]{grid-template-columns:1fr 130px 90px 110px auto}.xicms-admin .md\:grid-cols-\[1fr_1fr_140px_auto_auto\]{grid-template-columns:1fr 1fr 140px auto auto}.xicms-admin .md\:grid-cols-\[260px_1fr\]{grid-template-columns:260px 1fr}.xicms-admin .md\:grid-cols-\[280px_1fr\]{grid-template-columns:280px 1fr}.xicms-admin .md\:grid-cols-\[300px_1fr\]{grid-template-columns:300px 1fr}.xicms-admin .md\:grid-cols-\[340px_1fr\]{grid-template-columns:340px 1fr}}@media (min-width:1024px){.xicms-admin .lg\:col-span-2{grid-column:span 2/span 2}.xicms-admin .lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xicms-admin .lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}
1
+ .container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.xicms-admin .visible{visibility:visible}.xicms-admin .static{position:static}.xicms-admin .fixed{position:fixed}.xicms-admin .absolute{position:absolute}.xicms-admin .relative{position:relative}.xicms-admin .sticky{position:sticky}.xicms-admin .inset-0{inset:0}.xicms-admin .-right-2{right:-.5rem}.xicms-admin .-top-2{top:-.5rem}.xicms-admin .z-\[10010\]{z-index:10010}.xicms-admin .mb-1{margin-bottom:.25rem}.xicms-admin .mb-2{margin-bottom:.5rem}.xicms-admin .mb-3{margin-bottom:.75rem}.xicms-admin .ml-1{margin-left:.25rem}.xicms-admin .ml-auto{margin-left:auto}.xicms-admin .mr-1{margin-right:.25rem}.xicms-admin .mr-2{margin-right:.5rem}.xicms-admin .mt-0\.5{margin-top:.125rem}.xicms-admin .mt-1{margin-top:.25rem}.xicms-admin .mt-2{margin-top:.5rem}.xicms-admin .mt-3{margin-top:.75rem}.xicms-admin .mt-4{margin-top:1rem}.xicms-admin .mt-5{margin-top:1.25rem}.xicms-admin .mt-6{margin-top:1.5rem}.xicms-admin .\!block{display:block!important}.xicms-admin .block{display:block}.xicms-admin .inline{display:inline}.xicms-admin .flex{display:flex}.xicms-admin .inline-flex{display:inline-flex}.xicms-admin .table{display:table}.xicms-admin .grid{display:grid}.xicms-admin .hidden{display:none}.xicms-admin .aspect-square{aspect-ratio:1/1}.xicms-admin .h-10{height:2.5rem}.xicms-admin .h-14{height:3.5rem}.xicms-admin .h-16{height:4rem}.xicms-admin .h-28{height:7rem}.xicms-admin .h-32{height:8rem}.xicms-admin .h-5{height:1.25rem}.xicms-admin .h-6{height:1.5rem}.xicms-admin .h-8{height:2rem}.xicms-admin .h-9{height:2.25rem}.xicms-admin .h-\[70vh\]{height:70vh}.xicms-admin .h-fit{height:-moz-fit-content;height:fit-content}.xicms-admin .max-h-\[55vh\]{max-height:55vh}.xicms-admin .max-h-\[60vh\]{max-height:60vh}.xicms-admin .min-h-\[8rem\]{min-height:8rem}.xicms-admin .w-14{width:3.5rem}.xicms-admin .w-16{width:4rem}.xicms-admin .w-20{width:5rem}.xicms-admin .w-24{width:6rem}.xicms-admin .w-28{width:7rem}.xicms-admin .w-32{width:8rem}.xicms-admin .w-36{width:9rem}.xicms-admin .w-5{width:1.25rem}.xicms-admin .w-56{width:14rem}.xicms-admin .w-6{width:1.5rem}.xicms-admin .w-8{width:2rem}.xicms-admin .w-fit{width:-moz-fit-content;width:fit-content}.xicms-admin .w-full{width:100%}.xicms-admin .min-w-0{min-width:0}.xicms-admin .min-w-8{min-width:2rem}.xicms-admin .max-w-2xl{max-width:42rem}.xicms-admin .max-w-3xl{max-width:48rem}.xicms-admin .max-w-xl{max-width:36rem}.xicms-admin .max-w-xs{max-width:20rem}.xicms-admin .flex-1{flex:1 1 0%}.xicms-admin .shrink-0{flex-shrink:0}.xicms-admin .-translate-x-1\/2{--tw-translate-x:-50%}.xicms-admin .-translate-x-1\/2,.xicms-admin .transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.xicms-admin .cursor-pointer{cursor:pointer}.xicms-admin .resize{resize:both}.xicms-admin .list-decimal{list-style-type:decimal}.xicms-admin .list-disc{list-style-type:disc}.xicms-admin .grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xicms-admin .grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xicms-admin .grid-cols-\[120px_1fr\]{grid-template-columns:120px 1fr}.xicms-admin .flex-col{flex-direction:column}.xicms-admin .flex-wrap{flex-wrap:wrap}.xicms-admin .place-items-center{place-items:center}.xicms-admin .items-start{align-items:flex-start}.xicms-admin .items-end{align-items:flex-end}.xicms-admin .items-center{align-items:center}.xicms-admin .justify-end{justify-content:flex-end}.xicms-admin .justify-center{justify-content:center}.xicms-admin .justify-between{justify-content:space-between}.xicms-admin .gap-0\.5{gap:.125rem}.xicms-admin .gap-1{gap:.25rem}.xicms-admin .gap-1\.5{gap:.375rem}.xicms-admin .gap-2{gap:.5rem}.xicms-admin .gap-3{gap:.75rem}.xicms-admin .gap-4{gap:1rem}.xicms-admin .gap-6{gap:1.5rem}.xicms-admin .self-end{align-self:flex-end}.xicms-admin .self-center{align-self:center}.xicms-admin .overflow-auto{overflow:auto}.xicms-admin .overflow-hidden{overflow:hidden}.xicms-admin .overflow-x-auto{overflow-x:auto}.xicms-admin .overflow-y-auto{overflow-y:auto}.xicms-admin .truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.xicms-admin .whitespace-pre-wrap{white-space:pre-wrap}.xicms-admin .rounded{border-radius:.25rem}.xicms-admin .rounded-full{border-radius:9999px}.xicms-admin .rounded-lg{border-radius:.5rem}.xicms-admin .rounded-md{border-radius:.375rem}.xicms-admin .rounded-xl{border-radius:.75rem}.xicms-admin .rounded-b-lg{border-bottom-right-radius:.5rem;border-bottom-left-radius:.5rem}.xicms-admin .rounded-t{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.xicms-admin .rounded-t-lg{border-top-left-radius:.5rem;border-top-right-radius:.5rem}.xicms-admin .border{border-width:1px}.xicms-admin .border-b{border-bottom-width:1px}.xicms-admin .border-t{border-top-width:1px}.xicms-admin .border-t-0{border-top-width:0}.xicms-admin .border-amber-300{--tw-border-opacity:1;border-color:rgb(252 211 77/var(--tw-border-opacity,1))}.xicms-admin .border-brand-500{border-color:var(--xicms-primary,#4f46e5)}.xicms-admin .border-red-200{--tw-border-opacity:1;border-color:rgb(254 202 202/var(--tw-border-opacity,1))}.xicms-admin .border-slate-100{--tw-border-opacity:1;border-color:rgb(241 245 249/var(--tw-border-opacity,1))}.xicms-admin .border-slate-200{--tw-border-opacity:1;border-color:rgb(226 232 240/var(--tw-border-opacity,1))}.xicms-admin .border-slate-300{--tw-border-opacity:1;border-color:rgb(203 213 225/var(--tw-border-opacity,1))}.xicms-admin .bg-amber-50{--tw-bg-opacity:1;background-color:rgb(255 251 235/var(--tw-bg-opacity,1))}.xicms-admin .bg-black\/50{background-color:rgba(0,0,0,.5)}.xicms-admin .bg-brand-50{background-color:color-mix(in srgb,var(--xicms-primary,#4f46e5) 12%,#fff)}.xicms-admin .bg-brand-500{background-color:var(--xicms-primary,#4f46e5)}.xicms-admin .bg-red-50{--tw-bg-opacity:1;background-color:rgb(254 242 242/var(--tw-bg-opacity,1))}.xicms-admin .bg-slate-100{--tw-bg-opacity:1;background-color:rgb(241 245 249/var(--tw-bg-opacity,1))}.xicms-admin .bg-slate-200{--tw-bg-opacity:1;background-color:rgb(226 232 240/var(--tw-bg-opacity,1))}.xicms-admin .bg-slate-50{--tw-bg-opacity:1;background-color:rgb(248 250 252/var(--tw-bg-opacity,1))}.xicms-admin .bg-slate-700{--tw-bg-opacity:1;background-color:rgb(51 65 85/var(--tw-bg-opacity,1))}.xicms-admin .bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.xicms-admin .object-cover{-o-object-fit:cover;object-fit:cover}.xicms-admin .p-0\.5{padding:.125rem}.xicms-admin .p-1{padding:.25rem}.xicms-admin .p-2{padding:.5rem}.xicms-admin .p-3{padding:.75rem}.xicms-admin .p-4{padding:1rem}.xicms-admin .p-5{padding:1.25rem}.xicms-admin .p-6{padding:1.5rem}.xicms-admin .px-1{padding-left:.25rem;padding-right:.25rem}.xicms-admin .px-2{padding-left:.5rem;padding-right:.5rem}.xicms-admin .px-3{padding-left:.75rem;padding-right:.75rem}.xicms-admin .px-4{padding-left:1rem;padding-right:1rem}.xicms-admin .py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.xicms-admin .py-1{padding-top:.25rem;padding-bottom:.25rem}.xicms-admin .py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.xicms-admin .py-2{padding-top:.5rem;padding-bottom:.5rem}.xicms-admin .py-3{padding-top:.75rem;padding-bottom:.75rem}.xicms-admin .py-8{padding-top:2rem;padding-bottom:2rem}.xicms-admin .pl-5{padding-left:1.25rem}.xicms-admin .pr-2{padding-right:.5rem}.xicms-admin .pt-4{padding-top:1rem}.xicms-admin .pt-5{padding-top:1.25rem}.xicms-admin .text-left{text-align:left}.xicms-admin .text-center{text-align:center}.xicms-admin .text-right{text-align:right}.xicms-admin .align-top{vertical-align:top}.xicms-admin .font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.xicms-admin .text-3xl{font-size:1.875rem;line-height:2.25rem}.xicms-admin .text-\[11px\]{font-size:11px}.xicms-admin .text-base{font-size:1rem;line-height:1.5rem}.xicms-admin .text-lg{font-size:1.125rem;line-height:1.75rem}.xicms-admin .text-sm{font-size:.875rem;line-height:1.25rem}.xicms-admin .text-xl{font-size:1.25rem;line-height:1.75rem}.xicms-admin .text-xs{font-size:.75rem;line-height:1rem}.xicms-admin .font-bold{font-weight:700}.xicms-admin .font-medium{font-weight:500}.xicms-admin .font-semibold{font-weight:600}.xicms-admin .uppercase{text-transform:uppercase}.xicms-admin .italic{font-style:italic}.xicms-admin .tracking-wide{letter-spacing:.025em}.xicms-admin .text-amber-600{--tw-text-opacity:1;color:rgb(217 119 6/var(--tw-text-opacity,1))}.xicms-admin .text-amber-700{--tw-text-opacity:1;color:rgb(180 83 9/var(--tw-text-opacity,1))}.xicms-admin .text-amber-800{--tw-text-opacity:1;color:rgb(146 64 14/var(--tw-text-opacity,1))}.xicms-admin .text-brand-700{color:color-mix(in srgb,var(--xicms-primary,#4f46e5) 82%,#000)}.xicms-admin .text-red-600{--tw-text-opacity:1;color:rgb(220 38 38/var(--tw-text-opacity,1))}.xicms-admin .text-red-700{--tw-text-opacity:1;color:rgb(185 28 28/var(--tw-text-opacity,1))}.xicms-admin .text-slate-400{--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity,1))}.xicms-admin .text-slate-500{--tw-text-opacity:1;color:rgb(100 116 139/var(--tw-text-opacity,1))}.xicms-admin .text-slate-600{--tw-text-opacity:1;color:rgb(71 85 105/var(--tw-text-opacity,1))}.xicms-admin .text-slate-700{--tw-text-opacity:1;color:rgb(51 65 85/var(--tw-text-opacity,1))}.xicms-admin .text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.xicms-admin .underline{text-decoration-line:underline}.xicms-admin .line-through{text-decoration-line:line-through}.xicms-admin .opacity-70{opacity:.7}.xicms-admin .shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.xicms-admin .shadow-sm,.xicms-admin .shadow-xl{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.xicms-admin .shadow-xl{--tw-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1);--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color)}.xicms-admin .outline-none{outline:2px solid transparent;outline-offset:2px}.xicms-admin .outline{outline-style:solid}.xicms-admin .blur{--tw-blur:blur(8px)}.xicms-admin .blur,.xicms-admin .filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.xicms-admin .transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.xicms-admin .transition-\[filter\]{transition-property:filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.xicms-admin,.xicms-admin *,.xicms-admin :after,.xicms-admin :before{box-sizing:border-box}.xicms-admin{font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;color:#0f172a;line-height:1.5;-webkit-font-smoothing:antialiased;color-scheme:light;background:#fff;--color-background-body:#fff;--color-background-surface:#fff;--color-background-card:#fff;--color-background-muted:#f1f5f9;--color-text-primary:#0f172a;--color-text-secondary:#475569;--color-border:#e2e8f0;--color-accent:#4f46e5;--color-on-accent:#fff;--color-error:#dc2626}.xicms-admin figure,.xicms-admin h1,.xicms-admin h2,.xicms-admin h3,.xicms-admin p{margin:0}.xicms-admin button{font:inherit;cursor:pointer}.xicms-admin input,.xicms-admin select,.xicms-admin textarea{font:inherit;color:inherit}.xicms-admin .ProseMirror{min-height:8rem;outline:none}.xicms-admin .ProseMirror p{margin:0 0 .6rem}.xicms-admin .ProseMirror:focus{outline:none}@media (prefers-reduced-motion:reduce){.xicms-admin *,.xicms-admin :after,.xicms-admin :before,.xicms-ve *,.xicms-ve :after,.xicms-ve :before{animation-duration:.001ms!important;animation-iteration-count:1!important;transition-duration:.001ms!important}}.xicms-admin .last\:border-0:last-child{border-width:0}.xicms-admin .hover\:border-brand-500:hover{border-color:var(--xicms-primary,#4f46e5)}.xicms-admin .hover\:bg-slate-100:hover{--tw-bg-opacity:1;background-color:rgb(241 245 249/var(--tw-bg-opacity,1))}.xicms-admin .hover\:bg-slate-50:hover{--tw-bg-opacity:1;background-color:rgb(248 250 252/var(--tw-bg-opacity,1))}.xicms-admin .hover\:text-slate-700:hover{--tw-text-opacity:1;color:rgb(51 65 85/var(--tw-text-opacity,1))}.xicms-admin .hover\:text-slate-900:hover{--tw-text-opacity:1;color:rgb(15 23 42/var(--tw-text-opacity,1))}.xicms-admin .hover\:underline:hover{text-decoration-line:underline}.xicms-admin .hover\:brightness-95:hover{--tw-brightness:brightness(.95);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.xicms-admin .focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.xicms-admin .focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.xicms-admin .focus\:ring-indigo-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(99 102 241/var(--tw-ring-opacity,1))}.xicms-admin .focus-visible\:outline-none:focus-visible{outline:2px solid transparent;outline-offset:2px}.xicms-admin .focus-visible\:ring-2:focus-visible{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.xicms-admin .focus-visible\:ring-brand-500:focus-visible{--tw-ring-color:var(--xicms-primary,#4f46e5)}.xicms-admin .disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.xicms-admin .disabled\:opacity-50:disabled{opacity:.5}.xicms-admin :is(.group:hover .group-hover\:opacity-80){opacity:.8}.xicms-admin .data-\[state\=active\]\:border-b-2[data-state=active]{border-bottom-width:2px}.xicms-admin .data-\[state\=active\]\:border-brand-600[data-state=active]{border-color:var(--xicms-primary,#4f46e5)}.xicms-admin .data-\[state\=active\]\:text-brand-700[data-state=active]{color:color-mix(in srgb,var(--xicms-primary,#4f46e5) 82%,#000)}@media (min-width:640px){.xicms-admin .sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xicms-admin .sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xicms-admin .sm\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}@media (min-width:768px){.xicms-admin .md\:col-span-5{grid-column:span 5/span 5}.xicms-admin .md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xicms-admin .md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xicms-admin .md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xicms-admin .md\:grid-cols-\[1fr_110px\]{grid-template-columns:1fr 110px}.xicms-admin .md\:grid-cols-\[1fr_130px_90px_110px_auto\]{grid-template-columns:1fr 130px 90px 110px auto}.xicms-admin .md\:grid-cols-\[1fr_1fr_140px_auto_auto\]{grid-template-columns:1fr 1fr 140px auto auto}.xicms-admin .md\:grid-cols-\[260px_1fr\]{grid-template-columns:260px 1fr}.xicms-admin .md\:grid-cols-\[280px_1fr\]{grid-template-columns:280px 1fr}.xicms-admin .md\:grid-cols-\[300px_1fr\]{grid-template-columns:300px 1fr}.xicms-admin .md\:grid-cols-\[340px_1fr\]{grid-template-columns:340px 1fr}}@media (min-width:1024px){.xicms-admin .lg\:col-span-2{grid-column:span 2/span 2}.xicms-admin .lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xicms-admin .lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}
@@ -29,8 +29,15 @@ export declare const getPublishedPageByPath: typeof getPublishedPageByPathUncach
29
29
  export declare const getSiteSymbols: typeof getSiteSymbolsUncached;
30
30
  export declare const getSiteCustomBlocks: typeof getSiteCustomBlocksUncached;
31
31
  export declare const getPublicSiteSettings: typeof getPublicSiteSettingsUncached;
32
- /** Eén gepubliceerde pagina op pad. Bundle-first, anders keyless via REST/Admin. */
33
- declare function getPublishedPageByPathUncached(siteId: string, path: string): Promise<StoredPage | null>;
32
+ /**
33
+ * Eén gepubliceerde pagina op pad. Bundle-first, anders keyless via REST/Admin.
34
+ * `opts.skipBundle` (voorbeeld-modus, `?preview=1` in de visuele editor): negeert de gecommitte
35
+ * bundel en leest rechtstreeks — de bundel komt pas met vertraging mee via de gedebouncede deploy-
36
+ * worker, dus een beheerder die net gepubliceerd heeft ziet anders tot ~15 min lang de oude tekst.
37
+ */
38
+ declare function getPublishedPageByPathUncached(siteId: string, path: string, opts?: {
39
+ skipBundle?: boolean;
40
+ }): Promise<StoredPage | null>;
34
41
  /** Draft-versie (voor preview achter auth). */
35
42
  export declare function getDraftPageById(siteId: string, pageId: string): Promise<StoredPage | null>;
36
43
  /** Draft op pad (preview). */
@@ -55,7 +62,9 @@ export declare function expandSymbols<T extends {
55
62
  * `getSiteSymbols`, die beide al bundle-first zijn — de i18n-overlay en expansie draaien identiek
56
63
  * op bundle- én Firestore-data.
57
64
  */
58
- export declare function getPublishedPageByPathExpanded(siteId: string, path: string, locale?: string): Promise<StoredPage | null>;
65
+ export declare function getPublishedPageByPathExpanded(siteId: string, path: string, locale?: string, opts?: {
66
+ skipBundle?: boolean;
67
+ }): Promise<StoredPage | null>;
59
68
  /**
60
69
  * Publieke site-instellingen (`sites/{siteId}/settings/public`) server-side — voor de
61
70
  * `settingsData`-prop van XiCMSProvider. Zo zit het klant-thema/nav/footer al in de server-HTML
@@ -93,8 +93,8 @@ const getPublishedPageByPath = cache(getPublishedPageByPathUncached);
93
93
  const getSiteSymbols = cache(getSiteSymbolsUncached);
94
94
  const getSiteCustomBlocks = cache(getSiteCustomBlocksUncached);
95
95
  const getPublicSiteSettings = cache(getPublicSiteSettingsUncached);
96
- async function getPublishedPageByPathUncached(siteId, path) {
97
- const bundle = getBundle();
96
+ async function getPublishedPageByPathUncached(siteId, path, opts) {
97
+ const bundle = opts?.skipBundle ? null : getBundle();
98
98
  if (bundle && bundle.siteId === siteId) {
99
99
  const hit = getBundlePage(bundle, path);
100
100
  return hit ? parse(hit.id, hit) : null;
@@ -176,8 +176,8 @@ function expandSymbols(page, symbols) {
176
176
  });
177
177
  return { ...page, blocks };
178
178
  }
179
- async function getPublishedPageByPathExpanded(siteId, path, locale) {
180
- let page = await getPublishedPageByPath(siteId, path);
179
+ async function getPublishedPageByPathExpanded(siteId, path, locale, opts) {
180
+ let page = await getPublishedPageByPath(siteId, path, opts);
181
181
  if (!page) return null;
182
182
  if (locale) page = localizePage(page, locale, "");
183
183
  if (!(page.blocks ?? []).some((b) => b._type === "symbolRef")) return page;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/server/pages.ts"],"sourcesContent":["import \"server-only\";\n\nimport { cache } from \"react\";\n\nimport { getAdminDb } from \"../firebase/admin\";\nimport { PageSchema, type Page } from \"../schema/pages\";\nimport { SymbolSchema, type StoredSymbol } from \"../schema/symbols\";\nimport { CustomBlockSchema, type CustomBlock } from \"../schema/customBlocks\";\nimport { PublicSiteSettingsSchema, type PublicSiteSettings } from \"../schema/settings\";\nimport { localizePage, localizeBlocks } from \"../i18n/overlay\";\nimport { getBundle, getBundlePage } from \"./bundle\";\n\n/**\n * BUNDLE-FIRST, Firestore-fallback (5.7).\n *\n * Elke publieke reader hieronder kijkt eerst of er een gecommit content-bundle is (server/bundle.ts,\n * `content/published.bundle.json`). Zo ja → serveer daaruit met exact dezelfde return-vormen als het\n * Firestore-pad (incl. schema-validatie, i18n-overlay en symbol-expansie). Zo nee (dev/lokaal of een\n * repo zonder gecommit bundle) → val ongewijzigd terug op de Admin-SDK / keyless-REST-implementatie.\n * De bundle-tak is puur additief: signatures en gedrag van de fallback blijven identiek.\n */\n\nexport type StoredPage = Page & { id: string };\n\nfunction toPath(slugs: string[] | undefined): string {\n const clean = (slugs ?? []).filter(Boolean);\n return \"/\" + clean.join(\"/\");\n}\n\n/** Pad uit Next catch-all params (`[[...slug]]`). \"/\" voor de root. */\nexport function pathFromSlugs(slugs: string[] | undefined): string {\n const p = toPath(slugs);\n return p === \"/\" ? \"/\" : p.replace(/\\/$/, \"\");\n}\n\nfunction parse(id: string, data: unknown): StoredPage | null {\n const res = PageSchema.safeParse(data);\n if (!res.success) {\n // Niet stil 404'en: een gepubliceerde pagina die het schema niet haalt is een diagnose-signaal\n // (schema-evolutie zonder default, of corrupte write) — zelfde patroon als symbols/customBlocks.\n console.warn(`[xicms] pagina \"${id}\" faalt schema-validatie en wordt overgeslagen:`, res.error.issues[0]);\n return null;\n }\n return { id, ...res.data };\n}\n\n/**\n * Admin-SDK-reads mogen een PUBLIEKE pagina nooit laten 500'en — zelfde belofte als het REST-pad\n * (dat al overal try/catch → lege fallback heeft). Een Firestore-hikje wordt gelogd en levert de\n * fallback op; de route rendert dan 404/fallback i.p.v. een servererror.\n */\nasync function safeAdminRead<T>(fallback: T, what: string, fn: () => Promise<T>): Promise<T> {\n try {\n return await fn();\n } catch (e) {\n console.warn(`[xicms] Admin-read mislukt (${what}) — fallback gebruikt:`, (e as Error)?.message ?? e);\n return fallback;\n }\n}\n\n/* ---------------- keyless public reads via Firestore REST (alleen apiKey) ----------------\n * Gepubliceerde pagina's zijn publiek leesbaar (rules). De Firestore REST-API honoreert die\n * rules met enkel de web-apiKey (geen Admin-credentials, geen client-SDK), dus dit werkt\n * server-side op keyless deployments (org-policy zonder service-account-keys).\n */\ntype FsValue = Record<string, unknown>;\nfunction decode(v: FsValue): unknown {\n if (v == null) return null;\n if (\"stringValue\" in v) return v.stringValue;\n if (\"integerValue\" in v) return Number(v.integerValue);\n if (\"doubleValue\" in v) return v.doubleValue;\n if (\"booleanValue\" in v) return v.booleanValue;\n if (\"nullValue\" in v) return null;\n if (\"timestampValue\" in v) return v.timestampValue;\n if (\"referenceValue\" in v) return v.referenceValue;\n if (\"geoPointValue\" in v) return v.geoPointValue;\n if (\"arrayValue\" in v) return (((v.arrayValue as FsValue)?.values as FsValue[]) ?? []).map(decode);\n if (\"mapValue\" in v) return decodeFields(((v.mapValue as FsValue)?.fields as Record<string, FsValue>) ?? {});\n return null;\n}\nfunction decodeFields(fields: Record<string, FsValue>): Record<string, unknown> {\n return Object.fromEntries(Object.entries(fields).map(([k, x]) => [k, decode(x)]));\n}\n\ninterface Filter { fieldFilter: { field: { fieldPath: string }; op: \"EQUAL\"; value: { stringValue: string } } }\nfunction eq(fieldPath: string, value: string): Filter {\n return { fieldFilter: { field: { fieldPath }, op: \"EQUAL\", value: { stringValue: value } } };\n}\n\nasync function restQuery(siteId: string, filters: Filter[], limit?: number): Promise<StoredPage[]> {\n const apiKey = process.env.NEXT_PUBLIC_FIREBASE_API_KEY;\n const projectId = process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID;\n if (!apiKey || !projectId) return [];\n const url = `https://firestore.googleapis.com/v1/projects/${projectId}/databases/(default)/documents/sites/${siteId}:runQuery?key=${apiKey}`;\n const where = filters.length === 1 ? filters[0] : { compositeFilter: { op: \"AND\", filters } };\n const structuredQuery: Record<string, unknown> = { from: [{ collectionId: \"pages\" }], where };\n if (limit) structuredQuery.limit = limit;\n // Netwerk-/parsefouten mogen een publieke pagina nooit laten crashen (500) — dan liever een\n // lege lijst (Next toont dan een 404 of de fallback, en de volgende revalidate probeert opnieuw).\n try {\n const res = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ structuredQuery }),\n // Pagina's zijn ISR-gecached; next-cache mag dit hergebruiken.\n next: { revalidate: 300 },\n });\n if (!res.ok) return [];\n const rows = (await res.json()) as { document?: { name: string; fields: Record<string, FsValue> } }[];\n return rows\n .filter((r) => r.document)\n .map((r) => parse(r.document!.name.split(\"/\").pop()!, decodeFields(r.document!.fields)))\n .filter((p): p is StoredPage => p !== null);\n } catch (err) {\n console.error(\"[xicms] Firestore REST-query mislukt:\", err);\n return [];\n }\n}\n\n/** Alle gepubliceerde pagina's (sitemap / generateStaticParams). Bundle-first, anders keyless/Admin. */\nexport async function listPublishedPages(siteId: string): Promise<StoredPage[]> {\n const bundle = getBundle();\n if (bundle && bundle.siteId === siteId) {\n return bundle.pages.map((p) => parse(p.id, p)).filter((p): p is StoredPage => p !== null);\n }\n const adb = getAdminDb();\n if (adb) {\n return safeAdminRead([], `listPublishedPages ${siteId}`, async () => {\n const snap = await adb.collection(`sites/${siteId}/pages`).where(\"status\", \"==\", \"published\").get();\n return snap.docs.map((d) => parse(d.id, d.data())).filter((p): p is StoredPage => p !== null);\n });\n }\n return restQuery(siteId, [eq(\"status\", \"published\")]);\n}\n\n/**\n * Per-request-dedup via React cache(): metadata (generateMetadata) én de render lezen dezelfde\n * pagina — zonder cache() waren dat twee identieke Firestore-reads per request. NB: de\n * `next:{revalidate}`-hint op het REST-pad dekt dit niet (POST-fetches cachen niet); route-level\n * ISR (revalidate=300 in de consumer) blijft de echte kosten-demper tussen requests.\n */\nexport const getPublishedPageByPath = cache(getPublishedPageByPathUncached);\nexport const getSiteSymbols = cache(getSiteSymbolsUncached);\nexport const getSiteCustomBlocks = cache(getSiteCustomBlocksUncached);\nexport const getPublicSiteSettings = cache(getPublicSiteSettingsUncached);\n\n/** Eén gepubliceerde pagina op pad. Bundle-first, anders keyless via REST/Admin. */\nasync function getPublishedPageByPathUncached(siteId: string, path: string): Promise<StoredPage | null> {\n const bundle = getBundle();\n if (bundle && bundle.siteId === siteId) {\n const hit = getBundlePage(bundle, path);\n return hit ? parse(hit.id, hit) : null;\n }\n const adb = getAdminDb();\n if (adb) {\n return safeAdminRead(null, `getPublishedPageByPath ${siteId}${path}`, async () => {\n const snap = await adb\n .collection(`sites/${siteId}/pages`)\n .where(\"status\", \"==\", \"published\")\n .where(\"path\", \"==\", path)\n .limit(1)\n .get();\n const d = snap.docs[0];\n return d ? parse(d.id, d.data()) : null;\n });\n }\n const rows = await restQuery(siteId, [eq(\"status\", \"published\"), eq(\"path\", path)], 1);\n return rows[0] ?? null;\n}\n\n/** Draft-versie (voor preview achter auth). */\nexport async function getDraftPageById(siteId: string, pageId: string): Promise<StoredPage | null> {\n const db = getAdminDb();\n if (!db) return null;\n return safeAdminRead(null, `getDraftPageById ${siteId}/${pageId}`, async () => {\n const d = await db.doc(`sites/${siteId}/drafts/${pageId}`).get();\n return d.exists ? parse(d.id, d.data()) : null;\n });\n}\n\n/** Draft op pad (preview). */\nexport async function getDraftPageByPath(siteId: string, path: string): Promise<StoredPage | null> {\n const db = getAdminDb();\n if (!db) return null;\n return safeAdminRead(null, `getDraftPageByPath ${siteId}${path}`, async () => {\n const snap = await db.collection(`sites/${siteId}/drafts`).where(\"path\", \"==\", path).limit(1).get();\n const d = snap.docs[0];\n return d ? parse(d.id, d.data()) : null;\n });\n}\n\n/* ---------------- symbols & custom blocks (5.0) ---------------- */\n\n/** Generieke publieke collectie-read onder sites/{siteId}/ — Admin als beschikbaar, anders REST. */\nasync function readCollection(siteId: string, collectionId: string): Promise<{ id: string; data: Record<string, unknown> }[]> {\n const adb = getAdminDb();\n if (adb) {\n return safeAdminRead([], `readCollection ${siteId}/${collectionId}`, async () => {\n const snap = await adb.collection(`sites/${siteId}/${collectionId}`).get();\n return snap.docs.map((d) => ({ id: d.id, data: d.data() as Record<string, unknown> }));\n });\n }\n const apiKey = process.env.NEXT_PUBLIC_FIREBASE_API_KEY;\n const projectId = process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID;\n if (!apiKey || !projectId) return [];\n const url = `https://firestore.googleapis.com/v1/projects/${projectId}/databases/(default)/documents/sites/${siteId}:runQuery?key=${apiKey}`;\n try {\n const res = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ structuredQuery: { from: [{ collectionId }] } }),\n next: { revalidate: 300 },\n });\n if (!res.ok) return [];\n const rows = (await res.json()) as { document?: { name: string; fields: Record<string, FsValue> } }[];\n return rows\n .filter((r) => r.document)\n .map((r) => ({ id: r.document!.name.split(\"/\").pop()!, data: decodeFields(r.document!.fields ?? {}) }));\n } catch (err) {\n console.error(`[xicms] Firestore REST-read van ${collectionId} mislukt:`, err);\n return [];\n }\n}\n\n/** Alle symbolen van een site (gevalideerd; ongeldige overgeslagen met waarschuwing). Bundle-first. */\nasync function getSiteSymbolsUncached(siteId: string): Promise<StoredSymbol[]> {\n const bundle = getBundle();\n const rows =\n bundle && bundle.siteId === siteId\n ? bundle.symbols.map((s) => ({ id: s.id, data: s as Record<string, unknown> }))\n : await readCollection(siteId, \"symbols\");\n const out: StoredSymbol[] = [];\n for (const r of rows) {\n const res = SymbolSchema.safeParse(r.data);\n if (res.success) out.push({ id: r.id, ...res.data });\n else console.warn(`[xicms] symbool \"${r.id}\" ongeldig — overgeslagen.`);\n }\n return out;\n}\n\n/**\n * Vervangt symbolRef-blokken door de blokken van hun symbool (één niveau — symbolen kunnen zelf\n * geen symbolRef bevatten). `_key`s van de ingevoegde blokken worden geprefixt met de ref-key\n * zodat ze uniek blijven wanneer hetzelfde symbool vaker op een pagina staat. Verweesde refs\n * verdwijnen stil (de pagina mag nooit breken op een verwijderd symbool).\n */\nexport function expandSymbols<T extends { blocks?: unknown[] }>(page: T, symbols: StoredSymbol[]): T {\n const byId = new Map(symbols.map((s) => [s.id, s]));\n const blocks = (page.blocks ?? []).flatMap((b) => {\n const blk = b as { _type?: string; _key?: string; symbolId?: string };\n if (blk?._type !== \"symbolRef\") return [b];\n const sym = blk.symbolId ? byId.get(blk.symbolId) : undefined;\n if (!sym) return [];\n return sym.blocks.map((sb, i) => ({ ...(sb as Record<string, unknown>), _key: `${blk._key ?? \"sym\"}-${(sb as { _key?: string })._key ?? i}` }));\n });\n return { ...page, blocks };\n}\n\n/**\n * Gepubliceerde pagina op pad mét symbolRef-expansie (SEO: symboolinhoud zit in de SSR-HTML).\n * Geef `locale` mee (een NIET-default taal) om de vertaal-overlay toe te passen — op de pagina\n * én op de ingevoegde symbolen (elk symbool draagt zijn eigen `translations`).\n *\n * Bundle-first werkt hier automatisch: deze functie componeert `getPublishedPageByPath` en\n * `getSiteSymbols`, die beide al bundle-first zijn — de i18n-overlay en expansie draaien identiek\n * op bundle- én Firestore-data.\n */\nexport async function getPublishedPageByPathExpanded(\n siteId: string,\n path: string,\n locale?: string,\n): Promise<StoredPage | null> {\n let page = await getPublishedPageByPath(siteId, path);\n if (!page) return null;\n // De caller geeft alléén een niet-default taal door; \"\" als default matcht dus nooit.\n if (locale) page = localizePage(page, locale, \"\");\n if (!(page.blocks ?? []).some((b) => (b as { _type?: string })._type === \"symbolRef\")) return page;\n let symbols = await getSiteSymbols(siteId);\n if (locale) {\n symbols = symbols.map((s) => ({\n ...s,\n blocks: localizeBlocks(s.blocks as Array<{ _key?: string }>, s.translations?.[locale]) as typeof s.blocks,\n }));\n }\n return expandSymbols(page, symbols);\n}\n\n/**\n * Publieke site-instellingen (`sites/{siteId}/settings/public`) server-side — voor de\n * `settingsData`-prop van XiCMSProvider. Zo zit het klant-thema/nav/footer al in de server-HTML\n * (geen theme-flits bij eerste paint) én vervalt de per-bezoeker `getDoc` in de browser: de\n * layout-route is ISR-gecached, dus dit kost ~1 read per revalidate i.p.v. per bezoeker.\n * Fouten leveren `{}` op — de site valt dan terug op de statische SiteConfig.\n */\nasync function getPublicSiteSettingsUncached(siteId: string): Promise<PublicSiteSettings> {\n const bundle = getBundle();\n if (bundle && bundle.siteId === siteId) {\n const parsed = PublicSiteSettingsSchema.safeParse(bundle.settings ?? {});\n return parsed.success ? parsed.data : {};\n }\n const adb = getAdminDb();\n let data: unknown = {};\n if (adb) {\n data = await safeAdminRead<unknown>({}, `getPublicSiteSettings ${siteId}`, async () => {\n const d = await adb.doc(`sites/${siteId}/settings/public`).get();\n return d.exists ? d.data() : {};\n });\n } else {\n const apiKey = process.env.NEXT_PUBLIC_FIREBASE_API_KEY;\n const projectId = process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID;\n if (apiKey && projectId) {\n const url = `https://firestore.googleapis.com/v1/projects/${projectId}/databases/(default)/documents/sites/${siteId}/settings/public?key=${apiKey}`;\n try {\n // GET-fetches worden — anders dan de runQuery-POSTs — wél door de Next-datacache gedekt.\n const res = await fetch(url, { next: { revalidate: 300 } });\n if (res.ok) {\n const doc = (await res.json()) as { fields?: Record<string, FsValue> };\n data = decodeFields(doc.fields ?? {});\n }\n } catch (err) {\n console.error(\"[xicms] Firestore REST-read van settings/public mislukt:\", err);\n }\n }\n }\n const parsed = PublicSiteSettingsSchema.safeParse(data ?? {});\n return parsed.success ? parsed.data : {};\n}\n\n/** Alle custom blocks van een site (gevalideerd) — voor SSR-registratie via XiCMSProvider. Bundle-first. */\nasync function getSiteCustomBlocksUncached(siteId: string): Promise<CustomBlock[]> {\n const bundle = getBundle();\n const rows =\n bundle && bundle.siteId === siteId\n ? bundle.customBlocks.map((c) => ({ id: c.id, data: c as Record<string, unknown> }))\n : await readCollection(siteId, \"customBlocks\");\n const out: CustomBlock[] = [];\n for (const r of rows) {\n const res = CustomBlockSchema.safeParse({ ...r.data, type: r.data.type ?? r.id });\n if (res.success) out.push(res.data);\n else console.warn(`[xicms] custom block \"${r.id}\" ongeldig — overgeslagen.`);\n }\n return out;\n}\n"],"mappings":"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","names":["parsed"]}
1
+ {"version":3,"sources":["../../src/server/pages.ts"],"sourcesContent":["import \"server-only\";\n\nimport { cache } from \"react\";\n\nimport { getAdminDb } from \"../firebase/admin\";\nimport { PageSchema, type Page } from \"../schema/pages\";\nimport { SymbolSchema, type StoredSymbol } from \"../schema/symbols\";\nimport { CustomBlockSchema, type CustomBlock } from \"../schema/customBlocks\";\nimport { PublicSiteSettingsSchema, type PublicSiteSettings } from \"../schema/settings\";\nimport { localizePage, localizeBlocks } from \"../i18n/overlay\";\nimport { getBundle, getBundlePage } from \"./bundle\";\n\n/**\n * BUNDLE-FIRST, Firestore-fallback (5.7).\n *\n * Elke publieke reader hieronder kijkt eerst of er een gecommit content-bundle is (server/bundle.ts,\n * `content/published.bundle.json`). Zo ja → serveer daaruit met exact dezelfde return-vormen als het\n * Firestore-pad (incl. schema-validatie, i18n-overlay en symbol-expansie). Zo nee (dev/lokaal of een\n * repo zonder gecommit bundle) → val ongewijzigd terug op de Admin-SDK / keyless-REST-implementatie.\n * De bundle-tak is puur additief: signatures en gedrag van de fallback blijven identiek.\n */\n\nexport type StoredPage = Page & { id: string };\n\nfunction toPath(slugs: string[] | undefined): string {\n const clean = (slugs ?? []).filter(Boolean);\n return \"/\" + clean.join(\"/\");\n}\n\n/** Pad uit Next catch-all params (`[[...slug]]`). \"/\" voor de root. */\nexport function pathFromSlugs(slugs: string[] | undefined): string {\n const p = toPath(slugs);\n return p === \"/\" ? \"/\" : p.replace(/\\/$/, \"\");\n}\n\nfunction parse(id: string, data: unknown): StoredPage | null {\n const res = PageSchema.safeParse(data);\n if (!res.success) {\n // Niet stil 404'en: een gepubliceerde pagina die het schema niet haalt is een diagnose-signaal\n // (schema-evolutie zonder default, of corrupte write) — zelfde patroon als symbols/customBlocks.\n console.warn(`[xicms] pagina \"${id}\" faalt schema-validatie en wordt overgeslagen:`, res.error.issues[0]);\n return null;\n }\n return { id, ...res.data };\n}\n\n/**\n * Admin-SDK-reads mogen een PUBLIEKE pagina nooit laten 500'en — zelfde belofte als het REST-pad\n * (dat al overal try/catch → lege fallback heeft). Een Firestore-hikje wordt gelogd en levert de\n * fallback op; de route rendert dan 404/fallback i.p.v. een servererror.\n */\nasync function safeAdminRead<T>(fallback: T, what: string, fn: () => Promise<T>): Promise<T> {\n try {\n return await fn();\n } catch (e) {\n console.warn(`[xicms] Admin-read mislukt (${what}) — fallback gebruikt:`, (e as Error)?.message ?? e);\n return fallback;\n }\n}\n\n/* ---------------- keyless public reads via Firestore REST (alleen apiKey) ----------------\n * Gepubliceerde pagina's zijn publiek leesbaar (rules). De Firestore REST-API honoreert die\n * rules met enkel de web-apiKey (geen Admin-credentials, geen client-SDK), dus dit werkt\n * server-side op keyless deployments (org-policy zonder service-account-keys).\n */\ntype FsValue = Record<string, unknown>;\nfunction decode(v: FsValue): unknown {\n if (v == null) return null;\n if (\"stringValue\" in v) return v.stringValue;\n if (\"integerValue\" in v) return Number(v.integerValue);\n if (\"doubleValue\" in v) return v.doubleValue;\n if (\"booleanValue\" in v) return v.booleanValue;\n if (\"nullValue\" in v) return null;\n if (\"timestampValue\" in v) return v.timestampValue;\n if (\"referenceValue\" in v) return v.referenceValue;\n if (\"geoPointValue\" in v) return v.geoPointValue;\n if (\"arrayValue\" in v) return (((v.arrayValue as FsValue)?.values as FsValue[]) ?? []).map(decode);\n if (\"mapValue\" in v) return decodeFields(((v.mapValue as FsValue)?.fields as Record<string, FsValue>) ?? {});\n return null;\n}\nfunction decodeFields(fields: Record<string, FsValue>): Record<string, unknown> {\n return Object.fromEntries(Object.entries(fields).map(([k, x]) => [k, decode(x)]));\n}\n\ninterface Filter { fieldFilter: { field: { fieldPath: string }; op: \"EQUAL\"; value: { stringValue: string } } }\nfunction eq(fieldPath: string, value: string): Filter {\n return { fieldFilter: { field: { fieldPath }, op: \"EQUAL\", value: { stringValue: value } } };\n}\n\nasync function restQuery(siteId: string, filters: Filter[], limit?: number): Promise<StoredPage[]> {\n const apiKey = process.env.NEXT_PUBLIC_FIREBASE_API_KEY;\n const projectId = process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID;\n if (!apiKey || !projectId) return [];\n const url = `https://firestore.googleapis.com/v1/projects/${projectId}/databases/(default)/documents/sites/${siteId}:runQuery?key=${apiKey}`;\n const where = filters.length === 1 ? filters[0] : { compositeFilter: { op: \"AND\", filters } };\n const structuredQuery: Record<string, unknown> = { from: [{ collectionId: \"pages\" }], where };\n if (limit) structuredQuery.limit = limit;\n // Netwerk-/parsefouten mogen een publieke pagina nooit laten crashen (500) — dan liever een\n // lege lijst (Next toont dan een 404 of de fallback, en de volgende revalidate probeert opnieuw).\n try {\n const res = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ structuredQuery }),\n // Pagina's zijn ISR-gecached; next-cache mag dit hergebruiken.\n next: { revalidate: 300 },\n });\n if (!res.ok) return [];\n const rows = (await res.json()) as { document?: { name: string; fields: Record<string, FsValue> } }[];\n return rows\n .filter((r) => r.document)\n .map((r) => parse(r.document!.name.split(\"/\").pop()!, decodeFields(r.document!.fields)))\n .filter((p): p is StoredPage => p !== null);\n } catch (err) {\n console.error(\"[xicms] Firestore REST-query mislukt:\", err);\n return [];\n }\n}\n\n/** Alle gepubliceerde pagina's (sitemap / generateStaticParams). Bundle-first, anders keyless/Admin. */\nexport async function listPublishedPages(siteId: string): Promise<StoredPage[]> {\n const bundle = getBundle();\n if (bundle && bundle.siteId === siteId) {\n return bundle.pages.map((p) => parse(p.id, p)).filter((p): p is StoredPage => p !== null);\n }\n const adb = getAdminDb();\n if (adb) {\n return safeAdminRead([], `listPublishedPages ${siteId}`, async () => {\n const snap = await adb.collection(`sites/${siteId}/pages`).where(\"status\", \"==\", \"published\").get();\n return snap.docs.map((d) => parse(d.id, d.data())).filter((p): p is StoredPage => p !== null);\n });\n }\n return restQuery(siteId, [eq(\"status\", \"published\")]);\n}\n\n/**\n * Per-request-dedup via React cache(): metadata (generateMetadata) én de render lezen dezelfde\n * pagina — zonder cache() waren dat twee identieke Firestore-reads per request. NB: de\n * `next:{revalidate}`-hint op het REST-pad dekt dit niet (POST-fetches cachen niet); route-level\n * ISR (revalidate=300 in de consumer) blijft de echte kosten-demper tussen requests.\n */\nexport const getPublishedPageByPath = cache(getPublishedPageByPathUncached);\nexport const getSiteSymbols = cache(getSiteSymbolsUncached);\nexport const getSiteCustomBlocks = cache(getSiteCustomBlocksUncached);\nexport const getPublicSiteSettings = cache(getPublicSiteSettingsUncached);\n\n/**\n * Eén gepubliceerde pagina op pad. Bundle-first, anders keyless via REST/Admin.\n * `opts.skipBundle` (voorbeeld-modus, `?preview=1` in de visuele editor): negeert de gecommitte\n * bundel en leest rechtstreeks — de bundel komt pas met vertraging mee via de gedebouncede deploy-\n * worker, dus een beheerder die net gepubliceerd heeft ziet anders tot ~15 min lang de oude tekst.\n */\nasync function getPublishedPageByPathUncached(\n siteId: string,\n path: string,\n opts?: { skipBundle?: boolean },\n): Promise<StoredPage | null> {\n const bundle = opts?.skipBundle ? null : getBundle();\n if (bundle && bundle.siteId === siteId) {\n const hit = getBundlePage(bundle, path);\n return hit ? parse(hit.id, hit) : null;\n }\n const adb = getAdminDb();\n if (adb) {\n return safeAdminRead(null, `getPublishedPageByPath ${siteId}${path}`, async () => {\n const snap = await adb\n .collection(`sites/${siteId}/pages`)\n .where(\"status\", \"==\", \"published\")\n .where(\"path\", \"==\", path)\n .limit(1)\n .get();\n const d = snap.docs[0];\n return d ? parse(d.id, d.data()) : null;\n });\n }\n const rows = await restQuery(siteId, [eq(\"status\", \"published\"), eq(\"path\", path)], 1);\n return rows[0] ?? null;\n}\n\n/** Draft-versie (voor preview achter auth). */\nexport async function getDraftPageById(siteId: string, pageId: string): Promise<StoredPage | null> {\n const db = getAdminDb();\n if (!db) return null;\n return safeAdminRead(null, `getDraftPageById ${siteId}/${pageId}`, async () => {\n const d = await db.doc(`sites/${siteId}/drafts/${pageId}`).get();\n return d.exists ? parse(d.id, d.data()) : null;\n });\n}\n\n/** Draft op pad (preview). */\nexport async function getDraftPageByPath(siteId: string, path: string): Promise<StoredPage | null> {\n const db = getAdminDb();\n if (!db) return null;\n return safeAdminRead(null, `getDraftPageByPath ${siteId}${path}`, async () => {\n const snap = await db.collection(`sites/${siteId}/drafts`).where(\"path\", \"==\", path).limit(1).get();\n const d = snap.docs[0];\n return d ? parse(d.id, d.data()) : null;\n });\n}\n\n/* ---------------- symbols & custom blocks (5.0) ---------------- */\n\n/** Generieke publieke collectie-read onder sites/{siteId}/ — Admin als beschikbaar, anders REST. */\nasync function readCollection(siteId: string, collectionId: string): Promise<{ id: string; data: Record<string, unknown> }[]> {\n const adb = getAdminDb();\n if (adb) {\n return safeAdminRead([], `readCollection ${siteId}/${collectionId}`, async () => {\n const snap = await adb.collection(`sites/${siteId}/${collectionId}`).get();\n return snap.docs.map((d) => ({ id: d.id, data: d.data() as Record<string, unknown> }));\n });\n }\n const apiKey = process.env.NEXT_PUBLIC_FIREBASE_API_KEY;\n const projectId = process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID;\n if (!apiKey || !projectId) return [];\n const url = `https://firestore.googleapis.com/v1/projects/${projectId}/databases/(default)/documents/sites/${siteId}:runQuery?key=${apiKey}`;\n try {\n const res = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ structuredQuery: { from: [{ collectionId }] } }),\n next: { revalidate: 300 },\n });\n if (!res.ok) return [];\n const rows = (await res.json()) as { document?: { name: string; fields: Record<string, FsValue> } }[];\n return rows\n .filter((r) => r.document)\n .map((r) => ({ id: r.document!.name.split(\"/\").pop()!, data: decodeFields(r.document!.fields ?? {}) }));\n } catch (err) {\n console.error(`[xicms] Firestore REST-read van ${collectionId} mislukt:`, err);\n return [];\n }\n}\n\n/** Alle symbolen van een site (gevalideerd; ongeldige overgeslagen met waarschuwing). Bundle-first. */\nasync function getSiteSymbolsUncached(siteId: string): Promise<StoredSymbol[]> {\n const bundle = getBundle();\n const rows =\n bundle && bundle.siteId === siteId\n ? bundle.symbols.map((s) => ({ id: s.id, data: s as Record<string, unknown> }))\n : await readCollection(siteId, \"symbols\");\n const out: StoredSymbol[] = [];\n for (const r of rows) {\n const res = SymbolSchema.safeParse(r.data);\n if (res.success) out.push({ id: r.id, ...res.data });\n else console.warn(`[xicms] symbool \"${r.id}\" ongeldig — overgeslagen.`);\n }\n return out;\n}\n\n/**\n * Vervangt symbolRef-blokken door de blokken van hun symbool (één niveau — symbolen kunnen zelf\n * geen symbolRef bevatten). `_key`s van de ingevoegde blokken worden geprefixt met de ref-key\n * zodat ze uniek blijven wanneer hetzelfde symbool vaker op een pagina staat. Verweesde refs\n * verdwijnen stil (de pagina mag nooit breken op een verwijderd symbool).\n */\nexport function expandSymbols<T extends { blocks?: unknown[] }>(page: T, symbols: StoredSymbol[]): T {\n const byId = new Map(symbols.map((s) => [s.id, s]));\n const blocks = (page.blocks ?? []).flatMap((b) => {\n const blk = b as { _type?: string; _key?: string; symbolId?: string };\n if (blk?._type !== \"symbolRef\") return [b];\n const sym = blk.symbolId ? byId.get(blk.symbolId) : undefined;\n if (!sym) return [];\n return sym.blocks.map((sb, i) => ({ ...(sb as Record<string, unknown>), _key: `${blk._key ?? \"sym\"}-${(sb as { _key?: string })._key ?? i}` }));\n });\n return { ...page, blocks };\n}\n\n/**\n * Gepubliceerde pagina op pad mét symbolRef-expansie (SEO: symboolinhoud zit in de SSR-HTML).\n * Geef `locale` mee (een NIET-default taal) om de vertaal-overlay toe te passen — op de pagina\n * én op de ingevoegde symbolen (elk symbool draagt zijn eigen `translations`).\n *\n * Bundle-first werkt hier automatisch: deze functie componeert `getPublishedPageByPath` en\n * `getSiteSymbols`, die beide al bundle-first zijn — de i18n-overlay en expansie draaien identiek\n * op bundle- én Firestore-data.\n */\nexport async function getPublishedPageByPathExpanded(\n siteId: string,\n path: string,\n locale?: string,\n opts?: { skipBundle?: boolean },\n): Promise<StoredPage | null> {\n let page = await getPublishedPageByPath(siteId, path, opts);\n if (!page) return null;\n // De caller geeft alléén een niet-default taal door; \"\" als default matcht dus nooit.\n if (locale) page = localizePage(page, locale, \"\");\n if (!(page.blocks ?? []).some((b) => (b as { _type?: string })._type === \"symbolRef\")) return page;\n let symbols = await getSiteSymbols(siteId);\n if (locale) {\n symbols = symbols.map((s) => ({\n ...s,\n blocks: localizeBlocks(s.blocks as Array<{ _key?: string }>, s.translations?.[locale]) as typeof s.blocks,\n }));\n }\n return expandSymbols(page, symbols);\n}\n\n/**\n * Publieke site-instellingen (`sites/{siteId}/settings/public`) server-side — voor de\n * `settingsData`-prop van XiCMSProvider. Zo zit het klant-thema/nav/footer al in de server-HTML\n * (geen theme-flits bij eerste paint) én vervalt de per-bezoeker `getDoc` in de browser: de\n * layout-route is ISR-gecached, dus dit kost ~1 read per revalidate i.p.v. per bezoeker.\n * Fouten leveren `{}` op — de site valt dan terug op de statische SiteConfig.\n */\nasync function getPublicSiteSettingsUncached(siteId: string): Promise<PublicSiteSettings> {\n const bundle = getBundle();\n if (bundle && bundle.siteId === siteId) {\n const parsed = PublicSiteSettingsSchema.safeParse(bundle.settings ?? {});\n return parsed.success ? parsed.data : {};\n }\n const adb = getAdminDb();\n let data: unknown = {};\n if (adb) {\n data = await safeAdminRead<unknown>({}, `getPublicSiteSettings ${siteId}`, async () => {\n const d = await adb.doc(`sites/${siteId}/settings/public`).get();\n return d.exists ? d.data() : {};\n });\n } else {\n const apiKey = process.env.NEXT_PUBLIC_FIREBASE_API_KEY;\n const projectId = process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID;\n if (apiKey && projectId) {\n const url = `https://firestore.googleapis.com/v1/projects/${projectId}/databases/(default)/documents/sites/${siteId}/settings/public?key=${apiKey}`;\n try {\n // GET-fetches worden — anders dan de runQuery-POSTs — wél door de Next-datacache gedekt.\n const res = await fetch(url, { next: { revalidate: 300 } });\n if (res.ok) {\n const doc = (await res.json()) as { fields?: Record<string, FsValue> };\n data = decodeFields(doc.fields ?? {});\n }\n } catch (err) {\n console.error(\"[xicms] Firestore REST-read van settings/public mislukt:\", err);\n }\n }\n }\n const parsed = PublicSiteSettingsSchema.safeParse(data ?? {});\n return parsed.success ? parsed.data : {};\n}\n\n/** Alle custom blocks van een site (gevalideerd) — voor SSR-registratie via XiCMSProvider. Bundle-first. */\nasync function getSiteCustomBlocksUncached(siteId: string): Promise<CustomBlock[]> {\n const bundle = getBundle();\n const rows =\n bundle && bundle.siteId === siteId\n ? bundle.customBlocks.map((c) => ({ id: c.id, data: c as Record<string, unknown> }))\n : await readCollection(siteId, \"customBlocks\");\n const out: CustomBlock[] = [];\n for (const r of rows) {\n const res = CustomBlockSchema.safeParse({ ...r.data, type: r.data.type ?? r.id });\n if (res.success) out.push(res.data);\n else console.warn(`[xicms] custom block \"${r.id}\" ongeldig — overgeslagen.`);\n }\n return out;\n}\n"],"mappings":"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","names":["parsed"]}
@@ -1,5 +1,6 @@
1
1
  import "server-only";
2
2
  import sanitizeHtml from "sanitize-html";
3
+ const ALLOWED_FONT_SIZES = /^(0\.85|1\.25|1\.5)rem$/;
3
4
  const OPTIONS = {
4
5
  allowedTags: [
5
6
  "p",
@@ -12,6 +13,7 @@ const OPTIONS = {
12
13
  "s",
13
14
  "a",
14
15
  "img",
16
+ "span",
15
17
  "ul",
16
18
  "ol",
17
19
  "li",
@@ -26,7 +28,13 @@ const OPTIONS = {
26
28
  allowedAttributes: {
27
29
  a: ["href", "target", "rel"],
28
30
  // Afbeeldingen in rich text (WYSIWYG): alleen veilige presentatie-attributen, geen on*-handlers.
29
- img: ["src", "alt", "title", "width", "height", "loading", "decoding"]
31
+ img: ["src", "alt", "title", "width", "height", "loading", "decoding"],
32
+ // `span` mag uitsluitend `style` dragen — en zelfs dat alleen voor de font-size-preset (zie
33
+ // `allowedStyles` hieronder). Geen andere attributen (geen class/id/data-*).
34
+ span: ["style"]
35
+ },
36
+ allowedStyles: {
37
+ span: { "font-size": [ALLOWED_FONT_SIZES] }
30
38
  },
31
39
  // Geen data:-URI's voor afbeeldingen (alleen http/https + relatieve paden naar onze eigen Storage).
32
40
  allowedSchemesByTag: { img: ["http", "https"] },
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/server/sanitize.ts"],"sourcesContent":["import \"server-only\";\n\nimport sanitizeHtml from \"sanitize-html\";\n\n/**\n * Server-side sanitization van rich text (TipTap-output) met een strikte allowlist.\n * Dit draait bij elke write/publish, zodat opgeslagen HTML gegarandeerd veilig te renderen is\n * (de RichText-block rendert met dangerouslySetInnerHTML). Nooit op vertrouwen van de client.\n */\nconst OPTIONS: sanitizeHtml.IOptions = {\n allowedTags: [\n \"p\", \"br\", \"strong\", \"b\", \"em\", \"i\", \"u\", \"s\", \"a\", \"img\",\n \"ul\", \"ol\", \"li\", \"blockquote\", \"h2\", \"h3\", \"h4\", \"code\", \"pre\", \"hr\",\n ],\n allowedAttributes: {\n a: [\"href\", \"target\", \"rel\"],\n // Afbeeldingen in rich text (WYSIWYG): alleen veilige presentatie-attributen, geen on*-handlers.\n img: [\"src\", \"alt\", \"title\", \"width\", \"height\", \"loading\", \"decoding\"],\n },\n // Geen data:-URI's voor afbeeldingen (alleen http/https + relatieve paden naar onze eigen Storage).\n allowedSchemesByTag: { img: [\"http\", \"https\"] },\n allowedSchemes: [\"http\", \"https\", \"mailto\", \"tel\"],\n transformTags: {\n // Externe links altijd veilig openen.\n a: (tagName, attribs) => ({\n tagName,\n attribs: {\n ...attribs,\n rel: \"noopener noreferrer nofollow\",\n ...(attribs.target === \"_blank\" ? { target: \"_blank\" } : {}),\n },\n }),\n // Afbeeldingen lazy + async laten laden voor performance.\n img: (tagName, attribs) => ({ tagName, attribs: { ...attribs, loading: \"lazy\", decoding: \"async\" } }),\n },\n disallowedTagsMode: \"discard\",\n};\n\nexport function sanitizeRichText(html: string): string {\n return sanitizeHtml(html ?? \"\", OPTIONS);\n}\n"],"mappings":"AAAA,OAAO;AAEP,OAAO,kBAAkB;AAOzB,MAAM,UAAiC;AAAA,EACrC,aAAa;AAAA,IACX;AAAA,IAAK;AAAA,IAAM;AAAA,IAAU;AAAA,IAAK;AAAA,IAAM;AAAA,IAAK;AAAA,IAAK;AAAA,IAAK;AAAA,IAAK;AAAA,IACpD;AAAA,IAAM;AAAA,IAAM;AAAA,IAAM;AAAA,IAAc;AAAA,IAAM;AAAA,IAAM;AAAA,IAAM;AAAA,IAAQ;AAAA,IAAO;AAAA,EACnE;AAAA,EACA,mBAAmB;AAAA,IACjB,GAAG,CAAC,QAAQ,UAAU,KAAK;AAAA;AAAA,IAE3B,KAAK,CAAC,OAAO,OAAO,SAAS,SAAS,UAAU,WAAW,UAAU;AAAA,EACvE;AAAA;AAAA,EAEA,qBAAqB,EAAE,KAAK,CAAC,QAAQ,OAAO,EAAE;AAAA,EAC9C,gBAAgB,CAAC,QAAQ,SAAS,UAAU,KAAK;AAAA,EACjD,eAAe;AAAA;AAAA,IAEb,GAAG,CAAC,SAAS,aAAa;AAAA,MACxB;AAAA,MACA,SAAS;AAAA,QACP,GAAG;AAAA,QACH,KAAK;AAAA,QACL,GAAI,QAAQ,WAAW,WAAW,EAAE,QAAQ,SAAS,IAAI,CAAC;AAAA,MAC5D;AAAA,IACF;AAAA;AAAA,IAEA,KAAK,CAAC,SAAS,aAAa,EAAE,SAAS,SAAS,EAAE,GAAG,SAAS,SAAS,QAAQ,UAAU,QAAQ,EAAE;AAAA,EACrG;AAAA,EACA,oBAAoB;AACtB;AAEO,SAAS,iBAAiB,MAAsB;AACrD,SAAO,aAAa,QAAQ,IAAI,OAAO;AACzC;","names":[]}
1
+ {"version":3,"sources":["../../src/server/sanitize.ts"],"sourcesContent":["import \"server-only\";\n\nimport sanitizeHtml from \"sanitize-html\";\n\n/**\n * Server-side sanitization van rich text (TipTap-output) met een strikte allowlist.\n * Dit draait bij elke write/publish, zodat opgeslagen HTML gegarandeerd veilig te renderen is\n * (de RichText-block rendert met dangerouslySetInnerHTML). Nooit op vertrouwen van de client.\n */\n// Lettergrootte (RichTextEditor.tsx, FONT_SIZES): vaste preset-waarden, geen vrij invoerveld — de\n// regex staat UITSLUITEND deze exacte strings toe (geen url()/expression()/willekeurige CSS).\nconst ALLOWED_FONT_SIZES = /^(0\\.85|1\\.25|1\\.5)rem$/;\n\nconst OPTIONS: sanitizeHtml.IOptions = {\n allowedTags: [\n \"p\", \"br\", \"strong\", \"b\", \"em\", \"i\", \"u\", \"s\", \"a\", \"img\", \"span\",\n \"ul\", \"ol\", \"li\", \"blockquote\", \"h2\", \"h3\", \"h4\", \"code\", \"pre\", \"hr\",\n ],\n allowedAttributes: {\n a: [\"href\", \"target\", \"rel\"],\n // Afbeeldingen in rich text (WYSIWYG): alleen veilige presentatie-attributen, geen on*-handlers.\n img: [\"src\", \"alt\", \"title\", \"width\", \"height\", \"loading\", \"decoding\"],\n // `span` mag uitsluitend `style` dragen — en zelfs dat alleen voor de font-size-preset (zie\n // `allowedStyles` hieronder). Geen andere attributen (geen class/id/data-*).\n span: [\"style\"],\n },\n allowedStyles: {\n span: { \"font-size\": [ALLOWED_FONT_SIZES] },\n },\n // Geen data:-URI's voor afbeeldingen (alleen http/https + relatieve paden naar onze eigen Storage).\n allowedSchemesByTag: { img: [\"http\", \"https\"] },\n allowedSchemes: [\"http\", \"https\", \"mailto\", \"tel\"],\n transformTags: {\n // Externe links altijd veilig openen.\n a: (tagName, attribs) => ({\n tagName,\n attribs: {\n ...attribs,\n rel: \"noopener noreferrer nofollow\",\n ...(attribs.target === \"_blank\" ? { target: \"_blank\" } : {}),\n },\n }),\n // Afbeeldingen lazy + async laten laden voor performance.\n img: (tagName, attribs) => ({ tagName, attribs: { ...attribs, loading: \"lazy\", decoding: \"async\" } }),\n },\n disallowedTagsMode: \"discard\",\n};\n\nexport function sanitizeRichText(html: string): string {\n return sanitizeHtml(html ?? \"\", OPTIONS);\n}\n"],"mappings":"AAAA,OAAO;AAEP,OAAO,kBAAkB;AASzB,MAAM,qBAAqB;AAE3B,MAAM,UAAiC;AAAA,EACrC,aAAa;AAAA,IACX;AAAA,IAAK;AAAA,IAAM;AAAA,IAAU;AAAA,IAAK;AAAA,IAAM;AAAA,IAAK;AAAA,IAAK;AAAA,IAAK;AAAA,IAAK;AAAA,IAAO;AAAA,IAC3D;AAAA,IAAM;AAAA,IAAM;AAAA,IAAM;AAAA,IAAc;AAAA,IAAM;AAAA,IAAM;AAAA,IAAM;AAAA,IAAQ;AAAA,IAAO;AAAA,EACnE;AAAA,EACA,mBAAmB;AAAA,IACjB,GAAG,CAAC,QAAQ,UAAU,KAAK;AAAA;AAAA,IAE3B,KAAK,CAAC,OAAO,OAAO,SAAS,SAAS,UAAU,WAAW,UAAU;AAAA;AAAA;AAAA,IAGrE,MAAM,CAAC,OAAO;AAAA,EAChB;AAAA,EACA,eAAe;AAAA,IACb,MAAM,EAAE,aAAa,CAAC,kBAAkB,EAAE;AAAA,EAC5C;AAAA;AAAA,EAEA,qBAAqB,EAAE,KAAK,CAAC,QAAQ,OAAO,EAAE;AAAA,EAC9C,gBAAgB,CAAC,QAAQ,SAAS,UAAU,KAAK;AAAA,EACjD,eAAe;AAAA;AAAA,IAEb,GAAG,CAAC,SAAS,aAAa;AAAA,MACxB;AAAA,MACA,SAAS;AAAA,QACP,GAAG;AAAA,QACH,KAAK;AAAA,QACL,GAAI,QAAQ,WAAW,WAAW,EAAE,QAAQ,SAAS,IAAI,CAAC;AAAA,MAC5D;AAAA,IACF;AAAA;AAAA,IAEA,KAAK,CAAC,SAAS,aAAa,EAAE,SAAS,SAAS,EAAE,GAAG,SAAS,SAAS,QAAQ,UAAU,QAAQ,EAAE;AAAA,EACrG;AAAA,EACA,oBAAoB;AACtB;AAEO,SAAS,iBAAiB,MAAsB;AACrD,SAAO,aAAa,QAAQ,IAAI,OAAO;AACzC;","names":[]}
package/dist/styles.css CHANGED
@@ -323,6 +323,21 @@
323
323
  background: #fff;
324
324
  padding: 1rem;
325
325
  }
326
+ /* Blijft zichtbaar bovenaan het (op mobiel begrensde, scrollbare) zijpaneel terwijl je door de
327
+ velden van een lang blok (bv. FAQ met meerdere items) scrollt — zonder terug omhoog te scrollen
328
+ moet je altijd bij Opslaan/Sluiten kunnen. */
329
+ .xicms-ve__panel-head {
330
+ position: sticky;
331
+ top: -1rem;
332
+ z-index: 1;
333
+ display: flex;
334
+ justify-content: space-between;
335
+ gap: 0.5rem;
336
+ margin: -1rem -1rem 0;
337
+ padding: 0.6rem 1rem;
338
+ background: #fff;
339
+ border-bottom: 1px solid #e2e8f0;
340
+ }
326
341
  .xicms-ve__hint {
327
342
  color: #64748b;
328
343
  font-size: 0.9rem;
@@ -377,16 +392,6 @@
377
392
  min-height: 40px;
378
393
  font-size: 1rem;
379
394
  }
380
- /* Op mobiel is er geen hover — de toolbar toont alleen bij selectie, en de brede knoppenrij
381
- (5 knoppen × 40px raakdoel) besloeg dan bijna de volledige breedte en viel absoluut-gepositioneerd
382
- over de eerste regel van de bloktekst heen (moeilijk te lezen, en per ongeluk 🗑 raken lag voor
383
- de hand). In-flow boven het blok i.p.v. eroverheen. */
384
- .xicms-ve-block__toolbar {
385
- position: static;
386
- flex-wrap: wrap;
387
- border-radius: 8px;
388
- margin-bottom: 0.35rem;
389
- }
390
395
  .xicms-ve__inserter-select {
391
396
  width: 100%;
392
397
  }
@@ -575,6 +580,19 @@
575
580
  opacity: 0.35;
576
581
  cursor: default;
577
582
  }
583
+ @media (max-width: 720px) {
584
+ /* Op mobiel is er geen hover — de toolbar toont alleen bij selectie, en de brede knoppenrij
585
+ (5 knoppen × 40px raakdoel) besloeg dan bijna de volledige breedte en viel absoluut-gepositioneerd
586
+ over de eerste regel van de bloktekst heen (moeilijk te lezen, en per ongeluk 🗑 raken lag voor
587
+ de hand). In-flow boven het blok i.p.v. eroverheen. Ná de losstaande regel hierboven gezet zodat
588
+ hij in de cascade wint (media queries geven zelf geen extra specificiteit). */
589
+ .xicms-ve-block__toolbar {
590
+ position: static;
591
+ flex-wrap: wrap;
592
+ border-radius: 8px;
593
+ margin-bottom: 0.35rem;
594
+ }
595
+ }
578
596
 
579
597
  /* Foutkaart voor een block dat niet kon renderen (alleen zichtbaar in de editor-preview). */
580
598
  .xicms-block-error {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiwebdesign/xicms-core",
3
- "version": "6.4.2",
3
+ "version": "6.4.4",
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": {
@@ -105,16 +105,18 @@
105
105
  "@radix-ui/react-tooltip": "^1.2.12",
106
106
  "@tiptap/extension-image": "^2.11.0",
107
107
  "@tiptap/extension-link": "^2.11.0",
108
+ "@tiptap/extension-text-style": "^2.27.2",
109
+ "@tiptap/extension-underline": "^2.27.2",
108
110
  "@tiptap/pm": "^2.11.0",
109
111
  "@tiptap/react": "^2.11.0",
110
112
  "@tiptap/starter-kit": "^2.11.0",
111
113
  "clsx": "^2.1.1",
112
114
  "dompurify": "^3.2.3",
113
115
  "firebase-admin": "^13.0.0",
116
+ "html2canvas-pro": "^1.5.11",
114
117
  "sanitize-html": "^2.14.0",
115
118
  "tailwind-merge": "^2.6.0",
116
- "zod": "^3.24.1",
117
- "html2canvas-pro": "^1.5.11"
119
+ "zod": "^3.24.1"
118
120
  },
119
121
  "devDependencies": {
120
122
  "@astryxdesign/cli": "^0.1.1",