@xiwebdesign/xicms-core 7.10.0 → 7.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/media/MediaPicker.d.ts +1 -1
- package/dist/admin/media/MediaPicker.js +1 -0
- package/dist/admin/media/MediaPicker.js.map +1 -1
- package/dist/admin/patterns/PatternInserter.js +1 -1
- package/dist/admin/patterns/PatternInserter.js.map +1 -1
- package/dist/admin/visual/VisualEditor.js +92 -15
- package/dist/admin/visual/VisualEditor.js.map +1 -1
- package/dist/admin/visual/wijzigingen.js +18 -2
- package/dist/admin/visual/wijzigingen.js.map +1 -1
- package/dist/blocks/builtins/FormBlock.js +6 -2
- package/dist/blocks/builtins/FormBlock.js.map +1 -1
- package/dist/blocks/builtins/Search.js +6 -24
- package/dist/blocks/builtins/Search.js.map +1 -1
- package/dist/blocks/builtins/SymbolRef.js +23 -9
- package/dist/blocks/builtins/SymbolRef.js.map +1 -1
- package/dist/blocks/searchText.d.ts +19 -0
- package/dist/blocks/searchText.js +27 -0
- package/dist/blocks/searchText.js.map +1 -0
- package/dist/demo/demoStore.d.ts +8 -0
- package/dist/demo/demoStore.js +2 -1
- package/dist/demo/demoStore.js.map +1 -1
- package/dist/editor/EditorProvider.js +15 -3
- package/dist/editor/EditorProvider.js.map +1 -1
- package/dist/server/blockData.d.ts +17 -0
- package/dist/server/blockData.js +86 -0
- package/dist/server/blockData.js.map +1 -0
- package/dist/server/cached.d.ts +2 -0
- package/dist/server/cached.js +10 -0
- package/dist/server/cached.js.map +1 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +4 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/pages.d.ts +14 -0
- package/dist/server/pages.js +20 -10
- package/dist/server/pages.js.map +1 -1
- package/dist/server/shop.d.ts +8 -6
- package/dist/server/shop.js +10 -3
- package/dist/server/shop.js.map +1 -1
- package/dist/styles.css +44 -19
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -42,7 +42,23 @@ function bepaalWijzigingen(werk, live) {
|
|
|
42
42
|
const opKey = new Map(live.map((b, i) => [String(b._key ?? ""), { blok: b, index: i }]));
|
|
43
43
|
const blokken = /* @__PURE__ */ new Set();
|
|
44
44
|
const velden = /* @__PURE__ */ new Map();
|
|
45
|
-
w.
|
|
45
|
+
const gemeenschappelijk = w.map((b) => String(b._key ?? "")).filter((key) => key && opKey.has(key));
|
|
46
|
+
const liveVolgorde = gemeenschappelijk.map((key) => opKey.get(key).index);
|
|
47
|
+
const lengteTot = liveVolgorde.map(() => 1);
|
|
48
|
+
const vorige = liveVolgorde.map(() => -1);
|
|
49
|
+
let besteEind = -1;
|
|
50
|
+
for (let i = 0; i < liveVolgorde.length; i++) {
|
|
51
|
+
for (let j = 0; j < i; j++) {
|
|
52
|
+
if (liveVolgorde[j] < liveVolgorde[i] && lengteTot[j] + 1 > lengteTot[i]) {
|
|
53
|
+
lengteTot[i] = lengteTot[j] + 1;
|
|
54
|
+
vorige[i] = j;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (besteEind < 0 || lengteTot[i] > lengteTot[besteEind]) besteEind = i;
|
|
58
|
+
}
|
|
59
|
+
const opZijnPlek = /* @__PURE__ */ new Set();
|
|
60
|
+
for (let i = besteEind; i >= 0; i = vorige[i]) opZijnPlek.add(gemeenschappelijk[i]);
|
|
61
|
+
w.forEach((blok) => {
|
|
46
62
|
const key = String(blok._key ?? "");
|
|
47
63
|
if (!key) return;
|
|
48
64
|
const eerder = opKey.get(key);
|
|
@@ -50,7 +66,7 @@ function bepaalWijzigingen(werk, live) {
|
|
|
50
66
|
blokken.add(key);
|
|
51
67
|
return;
|
|
52
68
|
}
|
|
53
|
-
if (
|
|
69
|
+
if (!opZijnPlek.has(key)) blokken.add(key);
|
|
54
70
|
const verschillen = /* @__PURE__ */ new Set();
|
|
55
71
|
loop(blok, eerder.blok, "", verschillen);
|
|
56
72
|
if (verschillen.size > 0) velden.set(key, verschillen);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/visual/wijzigingen.ts"],"sourcesContent":["/**\n * Wat is er veranderd sinds de laatste publicatie?\n *\n * De editor wist tot 7.8 alleen of de WERKVERSIE nog opgeslagen moest worden. Dat is een andere\n * vraag dan wat een bezoeker ziet: je kunt iets wijzigen, het wordt netjes automatisch opgeslagen,\n * en er staat nog steeds niets live. \"Opgeslagen\" en \"gepubliceerd\" zijn twee toestanden en die\n * werden op één hoop gegooid — vandaar een publiceerknop die altijd even hard riep en een\n * waarschuwing die afging op het verkeerde moment.\n *\n * Dit bestand vergelijkt de werkversie met de gepubliceerde kopie en levert twee dingen:\n * welke BLOKKEN nieuw of verplaatst zijn, en welke VELDEN een andere waarde hebben. Daarmee kan\n * de pagina laten zien wat er nog niet live staat (rood/onderstreept) en kan de knop rechtsonder\n * eerlijk zeggen of er iets te publiceren valt.\n *\n * De veldpaden zijn dezelfde puntnotatie die de blokken in `data-xicms-edit` zetten\n * (`title`, `items.2.aHtml`), zodat de markering rechtstreeks op de DOM-haken past en er geen\n * tweede, uit de pas lopende vertaalslag nodig is.\n */\n\ntype Blok = Record<string, unknown>;\n\n/** Sleutels die over de administratie gaan, niet over de inhoud — die maken een pagina niet \"gewijzigd\". */\nconst NEGEER = new Set([\"_key\", \"_type\"]);\n\nexport interface Wijzigingen {\n /** Blok-keys die nieuw zijn of van plek zijn veranderd. */\n blokken: Set<string>;\n /** Blok-key → veldpaden met een andere waarde dan wat er live staat. */\n velden: Map<string, Set<string>>;\n /** Blok-keys die in de werkversie zijn VERWIJDERD (alleen voor de telling). */\n verwijderd: number;\n /** Is er hoe dan ook iets te publiceren? */\n iets: boolean;\n}\n\nexport const GEEN_WIJZIGINGEN: Wijzigingen = {\n blokken: new Set(),\n velden: new Map(),\n verwijderd: 0,\n iets: false,\n};\n\n/**\n * Loop een waarde af en noteer elk pad waarvan de inhoud verschilt.\n *\n * Arrays worden op index vergeleken, want dat is precies hoe de blokken hun paden opbouwen\n * (`items.2.q`). Een ingevoegd item in het midden markeert daardoor alles erna als gewijzigd —\n * technisch waar (die posities tonen nu andere tekst) en in de praktijk het eerlijkste signaal:\n * er is aan die lijst gesleuteld en dat staat nog niet live.\n */\nfunction loop(nieuw: unknown, oud: unknown, pad: string, uit: Set<string>): void {\n if (Array.isArray(nieuw) || Array.isArray(oud)) {\n const a = Array.isArray(nieuw) ? nieuw : [];\n const b = Array.isArray(oud) ? oud : [];\n for (let i = 0; i < Math.max(a.length, b.length); i++) {\n loop(a[i], b[i], pad ? `${pad}.${i}` : String(i), uit);\n }\n return;\n }\n const nObj = nieuw && typeof nieuw === \"object\";\n const oObj = oud && typeof oud === \"object\";\n if (nObj || oObj) {\n const sleutels = new Set([\n ...Object.keys(nObj ? (nieuw as object) : {}),\n ...Object.keys(oObj ? (oud as object) : {}),\n ]);\n for (const k of sleutels) {\n if (!pad && NEGEER.has(k)) continue;\n const n = nObj ? (nieuw as Record<string, unknown>)[k] : undefined;\n const o = oObj ? (oud as Record<string, unknown>)[k] : undefined;\n loop(n, o, pad ? `${pad}.${k}` : k, uit);\n }\n return;\n }\n // Bladwaarde. `undefined` en een lege string zijn hier hetzelfde: een veld dat nooit is ingevuld\n // en een veld dat je leegmaakte en weer invulde mogen niet als wijziging blijven hangen.\n const links = nieuw === undefined || nieuw === null ? \"\" : String(nieuw);\n const rechts = oud === undefined || oud === null ? \"\" : String(oud);\n if (links !== rechts && pad) uit.add(pad);\n}\n\n/**\n * Vergelijk de blokken van de werkversie met die van de gepubliceerde pagina.\n *\n * Is er nog géén gepubliceerde versie (`live === null`), dan is de hele pagina nieuw. Dat markeren\n * we bewust NIET veld voor veld: een compleet rode pagina is geen informatie, alleen lawaai. De\n * blokken krijgen de markering, de knop rechtsonder wordt blauw, en dat is de boodschap.\n */\nexport function bepaalWijzigingen(werk: Blok[] | null | undefined, live: Blok[] | null | undefined): Wijzigingen {\n const w = werk ?? [];\n if (!live) {\n const blokken = new Set(w.map((b) => String(b._key ?? \"\")).filter(Boolean));\n return { blokken, velden: new Map(), verwijderd: 0, iets: blokken.size > 0 };\n }\n const opKey = new Map(live.map((b, i) => [String(b._key ?? \"\"), { blok: b, index: i }]));\n const blokken = new Set<string>();\n const velden = new Map<string, Set<string>>();\n\n w.forEach((blok, index) => {\n const key = String(blok._key ?? \"\");\n if (!key) return;\n const eerder = opKey.get(key);\n if (!eerder) {\n // Nieuw blok: markeer het blok, niet elk veld apart (zie hierboven).\n blokken.add(key);\n return;\n }\n if (eerder.index !== index) blokken.add(key);\n const verschillen = new Set<string>();\n loop(blok, eerder.blok, \"\", verschillen);\n if (verschillen.size > 0) velden.set(key, verschillen);\n });\n\n const inWerk = new Set(w.map((b) => String(b._key ?? \"\")));\n const verwijderd = live.filter((b) => !inWerk.has(String(b._key ?? \"\"))).length;\n\n return {\n blokken,\n velden,\n verwijderd,\n iets: blokken.size > 0 || velden.size > 0 || verwijderd > 0,\n };\n}\n\n/**\n * Hoeveel staat er klaar — in gewone woorden, want een getal zonder eenheid zegt niets.\n *\n * Bewust grof: \"3 wijzigingen\" is genoeg om te weten dát er iets ligt. Wie precies wil weten wát,\n * ziet dat op de pagina zelf staan (rood/onderstreept); daar hoort deze knop niet mee te wedijveren.\n */\nexport function aantalWijzigingen(w: Wijzigingen): string {\n let n = w.blokken.size + w.verwijderd;\n for (const velden of w.velden.values()) n += velden.size;\n if (n === 0) return \"niets gewijzigd\";\n return n === 1 ? \"1 wijziging\" : `${n} wijzigingen`;\n}\n"],"mappings":"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","names":["blokken"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/visual/wijzigingen.ts"],"sourcesContent":["/**\n * Wat is er veranderd sinds de laatste publicatie?\n *\n * De editor wist tot 7.8 alleen of de WERKVERSIE nog opgeslagen moest worden. Dat is een andere\n * vraag dan wat een bezoeker ziet: je kunt iets wijzigen, het wordt netjes automatisch opgeslagen,\n * en er staat nog steeds niets live. \"Opgeslagen\" en \"gepubliceerd\" zijn twee toestanden en die\n * werden op één hoop gegooid — vandaar een publiceerknop die altijd even hard riep en een\n * waarschuwing die afging op het verkeerde moment.\n *\n * Dit bestand vergelijkt de werkversie met de gepubliceerde kopie en levert twee dingen:\n * welke BLOKKEN nieuw of verplaatst zijn, en welke VELDEN een andere waarde hebben. Daarmee kan\n * de pagina laten zien wat er nog niet live staat (rood/onderstreept) en kan de knop rechtsonder\n * eerlijk zeggen of er iets te publiceren valt.\n *\n * De veldpaden zijn dezelfde puntnotatie die de blokken in `data-xicms-edit` zetten\n * (`title`, `items.2.aHtml`), zodat de markering rechtstreeks op de DOM-haken past en er geen\n * tweede, uit de pas lopende vertaalslag nodig is.\n */\n\ntype Blok = Record<string, unknown>;\n\n/** Sleutels die over de administratie gaan, niet over de inhoud — die maken een pagina niet \"gewijzigd\". */\nconst NEGEER = new Set([\"_key\", \"_type\"]);\n\nexport interface Wijzigingen {\n /** Blok-keys die nieuw zijn of van plek zijn veranderd. */\n blokken: Set<string>;\n /** Blok-key → veldpaden met een andere waarde dan wat er live staat. */\n velden: Map<string, Set<string>>;\n /** Blok-keys die in de werkversie zijn VERWIJDERD (alleen voor de telling). */\n verwijderd: number;\n /** Is er hoe dan ook iets te publiceren? */\n iets: boolean;\n}\n\nexport const GEEN_WIJZIGINGEN: Wijzigingen = {\n blokken: new Set(),\n velden: new Map(),\n verwijderd: 0,\n iets: false,\n};\n\n/**\n * Loop een waarde af en noteer elk pad waarvan de inhoud verschilt.\n *\n * Arrays worden op index vergeleken, want dat is precies hoe de blokken hun paden opbouwen\n * (`items.2.q`). Een ingevoegd item in het midden markeert daardoor alles erna als gewijzigd —\n * technisch waar (die posities tonen nu andere tekst) en in de praktijk het eerlijkste signaal:\n * er is aan die lijst gesleuteld en dat staat nog niet live.\n */\nfunction loop(nieuw: unknown, oud: unknown, pad: string, uit: Set<string>): void {\n if (Array.isArray(nieuw) || Array.isArray(oud)) {\n const a = Array.isArray(nieuw) ? nieuw : [];\n const b = Array.isArray(oud) ? oud : [];\n for (let i = 0; i < Math.max(a.length, b.length); i++) {\n loop(a[i], b[i], pad ? `${pad}.${i}` : String(i), uit);\n }\n return;\n }\n const nObj = nieuw && typeof nieuw === \"object\";\n const oObj = oud && typeof oud === \"object\";\n if (nObj || oObj) {\n const sleutels = new Set([\n ...Object.keys(nObj ? (nieuw as object) : {}),\n ...Object.keys(oObj ? (oud as object) : {}),\n ]);\n for (const k of sleutels) {\n if (!pad && NEGEER.has(k)) continue;\n const n = nObj ? (nieuw as Record<string, unknown>)[k] : undefined;\n const o = oObj ? (oud as Record<string, unknown>)[k] : undefined;\n loop(n, o, pad ? `${pad}.${k}` : k, uit);\n }\n return;\n }\n // Bladwaarde. `undefined` en een lege string zijn hier hetzelfde: een veld dat nooit is ingevuld\n // en een veld dat je leegmaakte en weer invulde mogen niet als wijziging blijven hangen.\n const links = nieuw === undefined || nieuw === null ? \"\" : String(nieuw);\n const rechts = oud === undefined || oud === null ? \"\" : String(oud);\n if (links !== rechts && pad) uit.add(pad);\n}\n\n/**\n * Vergelijk de blokken van de werkversie met die van de gepubliceerde pagina.\n *\n * Is er nog géén gepubliceerde versie (`live === null`), dan is de hele pagina nieuw. Dat markeren\n * we bewust NIET veld voor veld: een compleet rode pagina is geen informatie, alleen lawaai. De\n * blokken krijgen de markering, de knop rechtsonder wordt blauw, en dat is de boodschap.\n */\nexport function bepaalWijzigingen(werk: Blok[] | null | undefined, live: Blok[] | null | undefined): Wijzigingen {\n const w = werk ?? [];\n if (!live) {\n const blokken = new Set(w.map((b) => String(b._key ?? \"\")).filter(Boolean));\n return { blokken, velden: new Map(), verwijderd: 0, iets: blokken.size > 0 };\n }\n const opKey = new Map(live.map((b, i) => [String(b._key ?? \"\"), { blok: b, index: i }]));\n const blokken = new Set<string>();\n const velden = new Map<string, Set<string>>();\n\n /**\n * Verplaatst = uit de RELATIEVE volgorde, niet \"op een andere index\". Dat verschil is groot:\n * één blok invoegen schuift elk blok eronder een index op, en op index vergeleken telde één\n * handeling als veertien wijzigingen — dertien blokken rood die de klant niet heeft aangeraakt.\n * We zoeken daarom de langste deelrij van gemeenschappelijke blokken die onderling in dezelfde\n * volgorde zijn gebleven; wat daarbuiten valt is werkelijk versleept. Bij \"blok omlaag\" is dat\n * precies dat ene blok. (O(n²), en dat is prima: een pagina heeft tientallen blokken, geen\n * duizenden.)\n */\n const gemeenschappelijk = w\n .map((b) => String(b._key ?? \"\"))\n .filter((key) => key && opKey.has(key));\n const liveVolgorde = gemeenschappelijk.map((key) => opKey.get(key)!.index);\n const lengteTot = liveVolgorde.map(() => 1);\n const vorige = liveVolgorde.map(() => -1);\n let besteEind = -1;\n for (let i = 0; i < liveVolgorde.length; i++) {\n for (let j = 0; j < i; j++) {\n if (liveVolgorde[j] < liveVolgorde[i] && lengteTot[j] + 1 > lengteTot[i]) {\n lengteTot[i] = lengteTot[j] + 1;\n vorige[i] = j;\n }\n }\n if (besteEind < 0 || lengteTot[i] > lengteTot[besteEind]) besteEind = i;\n }\n const opZijnPlek = new Set<string>();\n for (let i = besteEind; i >= 0; i = vorige[i]) opZijnPlek.add(gemeenschappelijk[i]);\n\n w.forEach((blok) => {\n const key = String(blok._key ?? \"\");\n if (!key) return;\n const eerder = opKey.get(key);\n if (!eerder) {\n // Nieuw blok: markeer het blok, niet elk veld apart (zie hierboven).\n blokken.add(key);\n return;\n }\n if (!opZijnPlek.has(key)) blokken.add(key);\n const verschillen = new Set<string>();\n loop(blok, eerder.blok, \"\", verschillen);\n if (verschillen.size > 0) velden.set(key, verschillen);\n });\n\n const inWerk = new Set(w.map((b) => String(b._key ?? \"\")));\n const verwijderd = live.filter((b) => !inWerk.has(String(b._key ?? \"\"))).length;\n\n return {\n blokken,\n velden,\n verwijderd,\n iets: blokken.size > 0 || velden.size > 0 || verwijderd > 0,\n };\n}\n\n/**\n * Hoeveel staat er klaar — in gewone woorden, want een getal zonder eenheid zegt niets.\n *\n * Bewust grof: \"3 wijzigingen\" is genoeg om te weten dát er iets ligt. Wie precies wil weten wát,\n * ziet dat op de pagina zelf staan (rood/onderstreept); daar hoort deze knop niet mee te wedijveren.\n */\nexport function aantalWijzigingen(w: Wijzigingen): string {\n let n = w.blokken.size + w.verwijderd;\n for (const velden of w.velden.values()) n += velden.size;\n if (n === 0) return \"niets gewijzigd\";\n return n === 1 ? \"1 wijziging\" : `${n} wijzigingen`;\n}\n"],"mappings":"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","names":["blokken"]}
|
|
@@ -3,6 +3,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { useEffect, useState } from "react";
|
|
4
4
|
import { FormBlockSchema } from "../../schema/blocks.js";
|
|
5
5
|
import { defineBlock } from "../registry.js";
|
|
6
|
+
import { useBlockData } from "../context.js";
|
|
6
7
|
import { submitFormValues } from "./lib/submitForm.js";
|
|
7
8
|
import { isDemoActive, getDemoForm } from "../../demo/demoStore.js";
|
|
8
9
|
import { useSiteConfig } from "../../config.js";
|
|
@@ -25,7 +26,9 @@ const DEMO_FALLBACK_FORM = {
|
|
|
25
26
|
};
|
|
26
27
|
function FormRender({ block, siteId, mode }) {
|
|
27
28
|
const siteMode = useSiteConfig().mode;
|
|
28
|
-
const
|
|
29
|
+
const serverForms = useBlockData("forms");
|
|
30
|
+
const serverDef = serverForms?.[block.formId] ?? null;
|
|
31
|
+
const [def, setDef] = useState(serverDef);
|
|
29
32
|
const [unavailable, setUnavailable] = useState(false);
|
|
30
33
|
const [values, setValues] = useState({});
|
|
31
34
|
const [hp, setHp] = useState("");
|
|
@@ -39,6 +42,7 @@ function FormRender({ block, siteId, mode }) {
|
|
|
39
42
|
setLocalOnly(true);
|
|
40
43
|
return;
|
|
41
44
|
}
|
|
45
|
+
if (serverDef) return;
|
|
42
46
|
void (async () => {
|
|
43
47
|
const [{ getDb }, { doc, getDoc }] = await Promise.all([
|
|
44
48
|
import("../../firebase/client.js"),
|
|
@@ -63,7 +67,7 @@ function FormRender({ block, siteId, mode }) {
|
|
|
63
67
|
setUnavailable(true);
|
|
64
68
|
}
|
|
65
69
|
})();
|
|
66
|
-
}, [demo, siteId, block.formId, siteMode]);
|
|
70
|
+
}, [demo, siteId, block.formId, siteMode, serverDef]);
|
|
67
71
|
if (unavailable) {
|
|
68
72
|
return /* @__PURE__ */ jsx(Card, { padding: 4, variant: "muted", children: /* @__PURE__ */ jsxs(Text, { children: [
|
|
69
73
|
block.heading ? `${block.heading}: ` : "",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/blocks/builtins/FormBlock.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState, type FormEvent } from \"react\";\nimport type { z } from \"zod\";\nimport { FormBlockSchema } from \"../../schema/blocks\";\nimport type { FormDefinition } from \"../../schema/forms\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { submitFormValues } from \"./lib/submitForm\";\nimport { isDemoActive, getDemoForm } from \"../../demo/demoStore\";\nimport { useSiteConfig } from \"../../config\";\nimport { TextInput } from \"@astryxdesign/core/TextInput\";\nimport { TextArea } from \"@astryxdesign/core/TextArea\";\nimport { Selector } from \"@astryxdesign/core/Selector\";\nimport { CheckboxInput } from \"@astryxdesign/core/CheckboxInput\";\nimport { Spinner } from \"@astryxdesign/core/Spinner\";\nimport { Button } from \"@astryxdesign/core/Button\";\nimport { VStack, Card, Heading, Text } from \"../ui\";\n\ntype FormBlk = z.infer<typeof FormBlockSchema>;\n\n/** Voorbeeldformulier voor de demo wanneer er geen specifieke definitie is geregistreerd. */\nconst DEMO_FALLBACK_FORM: FormDefinition = {\n name: \"Contact\",\n submitLabel: \"Versturen\",\n successMessage: \"Bedankt! Dit is een demo — je bericht is niet echt verstuurd.\",\n fields: [\n { name: \"naam\", label: \"Naam\", type: \"text\", required: true },\n { name: \"email\", label: \"E-mail\", type: \"email\", required: true },\n { name: \"bericht\", label: \"Bericht\", type: \"textarea\", required: false, placeholder: \"Waarmee kunnen we je helpen?\" },\n ],\n};\n\nfunction FormRender({ block, siteId, mode }: BlockRenderProps<FormBlk>) {\n const siteMode = useSiteConfig().mode;\n const [def, setDef] = useState<FormDefinition | null>(null);\n const [unavailable, setUnavailable] = useState(false);\n const [values, setValues] = useState<Record<string, string | boolean>>({});\n const [hp, setHp] = useState(\"\");\n const [status, setStatus] = useState<\"idle\" | \"busy\" | \"ok\" | \"error\">(\"idle\");\n const [error, setError] = useState<string | null>(null);\n\n const demo = isDemoActive();\n // \"Lokaal\" inzenden (geen Firebase-call) bij demo OF wanneer er geen Firebase is maar wél een\n // geregistreerd voorbeeldformulier — zo werkt een statische referentiesite z'n formulier ook.\n const [localOnly, setLocalOnly] = useState(demo);\n\n useEffect(() => {\n if (demo) {\n setDef(getDemoForm(siteId, block.formId) ?? DEMO_FALLBACK_FORM);\n setLocalOnly(true);\n return;\n }\n // Firestore dynamisch: alleen een pagina die écht een formulier toont haalt de SDK binnen.\n void (async () => {\n const [{ getDb }, { doc, getDoc }] = await Promise.all([\n import(\"../../firebase/client\"),\n import(\"firebase/firestore\"),\n ]);\n const db = getDb();\n if (!db) {\n // Geen Firebase: val terug op een geregistreerd formulier, of (op een mode:\"demo\"-site) op\n // het ingebouwde voorbeeld; beide zenden lokaal in. Anders een nette \"niet beschikbaar\".\n const reg = getDemoForm(siteId, block.formId) ?? (siteMode === \"demo\" ? DEMO_FALLBACK_FORM : null);\n if (reg) { setDef(reg); setLocalOnly(true); } else { setUnavailable(true); }\n return;\n }\n try {\n const snap = await getDoc(doc(db, \"sites\", siteId, \"forms\", block.formId));\n if (snap.exists()) setDef(snap.data() as FormDefinition);\n else setUnavailable(true);\n } catch {\n setUnavailable(true);\n }\n })();\n }, [demo, siteId, block.formId, siteMode]);\n\n if (unavailable) {\n return (\n <Card padding={4} variant=\"muted\">\n <Text>{block.heading ? `${block.heading}: ` : \"\"}neem contact met ons op — we reageren snel.</Text>\n </Card>\n );\n }\n if (!def) {\n return (\n <div style={{ display: \"grid\", placeItems: \"center\", minHeight: 120 }}>\n <Spinner label=\"Formulier laden…\" />\n </div>\n );\n }\n\n async function submit(e: FormEvent) {\n e.preventDefault();\n if (mode === \"preview\") return;\n if (localOnly) {\n // Lokaal (demo / geen backend): niets versturen, alleen de successtaat tonen.\n setStatus(\"ok\");\n setValues({});\n return;\n }\n setStatus(\"busy\");\n setError(null);\n try {\n await submitFormValues(siteId, block.formId, values, hp);\n // Optioneel doorsturen naar een bedankt-pagina (conversiemeting/advertenties).\n if (def?.successRedirect) {\n window.location.assign(def.successRedirect);\n return;\n }\n setStatus(\"ok\");\n setValues({});\n } catch {\n setStatus(\"error\");\n setError(\"Versturen mislukt. Probeer het later opnieuw.\");\n }\n }\n\n if (status === \"ok\") {\n return (\n // role=\"status\": screenreaders melden de succesboodschap wanneer het formulier verdwijnt.\n <Card padding={4} variant=\"muted\" role=\"status\">\n <Text>{def.successMessage}</Text>\n </Card>\n );\n }\n\n const setVal = (name: string, v: string | boolean) => setValues((prev) => ({ ...prev, [name]: v }));\n\n return (\n <form className=\"xicms-form\" onSubmit={submit}>\n <VStack gap={3}>\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n {def.fields.map((f) => {\n const sv = (values[f.name] as string) ?? \"\";\n if (f.type === \"textarea\") {\n return (\n <TextArea key={f.name} label={f.label} isRequired={f.required} placeholder={f.placeholder} rows={4}\n value={sv} onChange={(v) => setVal(f.name, v)} />\n );\n }\n if (f.type === \"select\") {\n return (\n <Selector key={f.name} label={f.label} isRequired={f.required} placeholder=\"Kies…\"\n options={f.options ?? []} value={sv} onChange={(v) => setVal(f.name, (v as string) ?? \"\")} />\n );\n }\n if (f.type === \"checkbox\") {\n return (\n <CheckboxInput key={f.name} label={f.label} isRequired={f.required}\n value={Boolean(values[f.name])} onChange={(checked) => setVal(f.name, checked)} />\n );\n }\n if (f.type === \"radio\") {\n return (\n <fieldset key={f.name} className=\"xicms-form__radios\">\n <legend>{f.label}{f.required && \" *\"}</legend>\n {(f.options ?? []).map((opt) => (\n <label key={opt} className=\"xicms-form__radio\">\n <input\n type=\"radio\"\n name={f.name}\n value={opt}\n required={f.required}\n checked={sv === opt}\n onChange={() => setVal(f.name, opt)}\n />\n {opt}\n </label>\n ))}\n </fieldset>\n );\n }\n if (f.type === \"date\" || f.type === \"number\") {\n // Astryx TextInput kent geen date/number → native input in dezelfde stijl.\n return (\n <label key={f.name} className=\"xicms-form__native\">\n <span className=\"xicms-form__native-label\">{f.label}{f.required && \" *\"}</span>\n <input\n className=\"xicms-input\"\n type={f.type}\n required={f.required}\n placeholder={f.placeholder}\n value={sv}\n onChange={(e) => setVal(f.name, e.target.value)}\n />\n </label>\n );\n }\n return (\n <TextInput key={f.name} label={f.label} type={f.type === \"email\" ? \"email\" : \"text\"}\n isRequired={f.required} placeholder={f.placeholder}\n value={sv} onChange={(v) => setVal(f.name, v)} />\n );\n })}\n {/* Honeypot — verborgen voor mensen, bots vullen 'm vaak wel in. */}\n <input\n type=\"text\"\n tabIndex={-1}\n autoComplete=\"off\"\n value={hp}\n onChange={(e) => setHp(e.target.value)}\n style={{ position: \"absolute\", left: \"-9999px\", width: 1, height: 1 }}\n aria-hidden\n />\n {error && <Text role=\"alert\" style={{ color: \"var(--color-error, #c0392b)\" }}>{error}</Text>}\n <div>\n <Button label={status === \"busy\" ? \"Versturen…\" : def.submitLabel} variant=\"primary\" size=\"lg\" type=\"submit\" isLoading={status === \"busy\"} />\n </div>\n </VStack>\n </form>\n );\n}\n\nexport const formBlock = defineBlock({\n type: \"form\",\n schema: FormBlockSchema,\n Render: FormRender,\n label: \"Formulier\",\n icon: \"✉️\",\n defaults: () => ({ formId: \"\" }),\n});\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/blocks/builtins/FormBlock.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState, type FormEvent } from \"react\";\nimport type { z } from \"zod\";\nimport { FormBlockSchema } from \"../../schema/blocks\";\nimport type { FormDefinition } from \"../../schema/forms\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { useBlockData } from \"../context\";\nimport { submitFormValues } from \"./lib/submitForm\";\nimport { isDemoActive, getDemoForm } from \"../../demo/demoStore\";\nimport { useSiteConfig } from \"../../config\";\nimport { TextInput } from \"@astryxdesign/core/TextInput\";\nimport { TextArea } from \"@astryxdesign/core/TextArea\";\nimport { Selector } from \"@astryxdesign/core/Selector\";\nimport { CheckboxInput } from \"@astryxdesign/core/CheckboxInput\";\nimport { Spinner } from \"@astryxdesign/core/Spinner\";\nimport { Button } from \"@astryxdesign/core/Button\";\nimport { VStack, Card, Heading, Text } from \"../ui\";\n\ntype FormBlk = z.infer<typeof FormBlockSchema>;\n\n/** Voorbeeldformulier voor de demo wanneer er geen specifieke definitie is geregistreerd. */\nconst DEMO_FALLBACK_FORM: FormDefinition = {\n name: \"Contact\",\n submitLabel: \"Versturen\",\n successMessage: \"Bedankt! Dit is een demo — je bericht is niet echt verstuurd.\",\n fields: [\n { name: \"naam\", label: \"Naam\", type: \"text\", required: true },\n { name: \"email\", label: \"E-mail\", type: \"email\", required: true },\n { name: \"bericht\", label: \"Bericht\", type: \"textarea\", required: false, placeholder: \"Waarmee kunnen we je helpen?\" },\n ],\n};\n\nfunction FormRender({ block, siteId, mode }: BlockRenderProps<FormBlk>) {\n const siteMode = useSiteConfig().mode;\n // Server-seed (7.12): de route leest formulierdefinities server-side (getPageBlockData) en\n // geeft ze mee als blockData. Het formulier staat dan al in de SSR-HTML en de per-bezoeker\n // `getDoc` hieronder vervalt. Zonder seed (oudere site, admin-preview) werkt het oude pad.\n const serverForms = useBlockData<Record<string, FormDefinition>>(\"forms\");\n const serverDef = serverForms?.[block.formId] ?? null;\n const [def, setDef] = useState<FormDefinition | null>(serverDef);\n const [unavailable, setUnavailable] = useState(false);\n const [values, setValues] = useState<Record<string, string | boolean>>({});\n const [hp, setHp] = useState(\"\");\n const [status, setStatus] = useState<\"idle\" | \"busy\" | \"ok\" | \"error\">(\"idle\");\n const [error, setError] = useState<string | null>(null);\n\n const demo = isDemoActive();\n // \"Lokaal\" inzenden (geen Firebase-call) bij demo OF wanneer er geen Firebase is maar wél een\n // geregistreerd voorbeeldformulier — zo werkt een statische referentiesite z'n formulier ook.\n const [localOnly, setLocalOnly] = useState(demo);\n\n useEffect(() => {\n if (demo) {\n setDef(getDemoForm(siteId, block.formId) ?? DEMO_FALLBACK_FORM);\n setLocalOnly(true);\n return;\n }\n if (serverDef) return; // definitie kwam al server-side mee\n // Firestore dynamisch: alleen een pagina die écht een formulier toont haalt de SDK binnen.\n void (async () => {\n const [{ getDb }, { doc, getDoc }] = await Promise.all([\n import(\"../../firebase/client\"),\n import(\"firebase/firestore\"),\n ]);\n const db = getDb();\n if (!db) {\n // Geen Firebase: val terug op een geregistreerd formulier, of (op een mode:\"demo\"-site) op\n // het ingebouwde voorbeeld; beide zenden lokaal in. Anders een nette \"niet beschikbaar\".\n const reg = getDemoForm(siteId, block.formId) ?? (siteMode === \"demo\" ? DEMO_FALLBACK_FORM : null);\n if (reg) { setDef(reg); setLocalOnly(true); } else { setUnavailable(true); }\n return;\n }\n try {\n const snap = await getDoc(doc(db, \"sites\", siteId, \"forms\", block.formId));\n if (snap.exists()) setDef(snap.data() as FormDefinition);\n else setUnavailable(true);\n } catch {\n setUnavailable(true);\n }\n })();\n }, [demo, siteId, block.formId, siteMode, serverDef]);\n\n if (unavailable) {\n return (\n <Card padding={4} variant=\"muted\">\n <Text>{block.heading ? `${block.heading}: ` : \"\"}neem contact met ons op — we reageren snel.</Text>\n </Card>\n );\n }\n if (!def) {\n return (\n <div style={{ display: \"grid\", placeItems: \"center\", minHeight: 120 }}>\n <Spinner label=\"Formulier laden…\" />\n </div>\n );\n }\n\n async function submit(e: FormEvent) {\n e.preventDefault();\n if (mode === \"preview\") return;\n if (localOnly) {\n // Lokaal (demo / geen backend): niets versturen, alleen de successtaat tonen.\n setStatus(\"ok\");\n setValues({});\n return;\n }\n setStatus(\"busy\");\n setError(null);\n try {\n await submitFormValues(siteId, block.formId, values, hp);\n // Optioneel doorsturen naar een bedankt-pagina (conversiemeting/advertenties).\n if (def?.successRedirect) {\n window.location.assign(def.successRedirect);\n return;\n }\n setStatus(\"ok\");\n setValues({});\n } catch {\n setStatus(\"error\");\n setError(\"Versturen mislukt. Probeer het later opnieuw.\");\n }\n }\n\n if (status === \"ok\") {\n return (\n // role=\"status\": screenreaders melden de succesboodschap wanneer het formulier verdwijnt.\n <Card padding={4} variant=\"muted\" role=\"status\">\n <Text>{def.successMessage}</Text>\n </Card>\n );\n }\n\n const setVal = (name: string, v: string | boolean) => setValues((prev) => ({ ...prev, [name]: v }));\n\n return (\n <form className=\"xicms-form\" onSubmit={submit}>\n <VStack gap={3}>\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n {def.fields.map((f) => {\n const sv = (values[f.name] as string) ?? \"\";\n if (f.type === \"textarea\") {\n return (\n <TextArea key={f.name} label={f.label} isRequired={f.required} placeholder={f.placeholder} rows={4}\n value={sv} onChange={(v) => setVal(f.name, v)} />\n );\n }\n if (f.type === \"select\") {\n return (\n <Selector key={f.name} label={f.label} isRequired={f.required} placeholder=\"Kies…\"\n options={f.options ?? []} value={sv} onChange={(v) => setVal(f.name, (v as string) ?? \"\")} />\n );\n }\n if (f.type === \"checkbox\") {\n return (\n <CheckboxInput key={f.name} label={f.label} isRequired={f.required}\n value={Boolean(values[f.name])} onChange={(checked) => setVal(f.name, checked)} />\n );\n }\n if (f.type === \"radio\") {\n return (\n <fieldset key={f.name} className=\"xicms-form__radios\">\n <legend>{f.label}{f.required && \" *\"}</legend>\n {(f.options ?? []).map((opt) => (\n <label key={opt} className=\"xicms-form__radio\">\n <input\n type=\"radio\"\n name={f.name}\n value={opt}\n required={f.required}\n checked={sv === opt}\n onChange={() => setVal(f.name, opt)}\n />\n {opt}\n </label>\n ))}\n </fieldset>\n );\n }\n if (f.type === \"date\" || f.type === \"number\") {\n // Astryx TextInput kent geen date/number → native input in dezelfde stijl.\n return (\n <label key={f.name} className=\"xicms-form__native\">\n <span className=\"xicms-form__native-label\">{f.label}{f.required && \" *\"}</span>\n <input\n className=\"xicms-input\"\n type={f.type}\n required={f.required}\n placeholder={f.placeholder}\n value={sv}\n onChange={(e) => setVal(f.name, e.target.value)}\n />\n </label>\n );\n }\n return (\n <TextInput key={f.name} label={f.label} type={f.type === \"email\" ? \"email\" : \"text\"}\n isRequired={f.required} placeholder={f.placeholder}\n value={sv} onChange={(v) => setVal(f.name, v)} />\n );\n })}\n {/* Honeypot — verborgen voor mensen, bots vullen 'm vaak wel in. */}\n <input\n type=\"text\"\n tabIndex={-1}\n autoComplete=\"off\"\n value={hp}\n onChange={(e) => setHp(e.target.value)}\n style={{ position: \"absolute\", left: \"-9999px\", width: 1, height: 1 }}\n aria-hidden\n />\n {error && <Text role=\"alert\" style={{ color: \"var(--color-error, #c0392b)\" }}>{error}</Text>}\n <div>\n <Button label={status === \"busy\" ? \"Versturen…\" : def.submitLabel} variant=\"primary\" size=\"lg\" type=\"submit\" isLoading={status === \"busy\"} />\n </div>\n </VStack>\n </form>\n );\n}\n\nexport const formBlock = defineBlock({\n type: \"form\",\n schema: FormBlockSchema,\n Render: FormRender,\n label: \"Formulier\",\n icon: \"✉️\",\n defaults: () => ({ formId: \"\" }),\n});\n"],"mappings":"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","names":[]}
|
|
@@ -5,32 +5,14 @@ import { SearchBlockSchema } from "../../schema/blocks.js";
|
|
|
5
5
|
import { defineBlock } from "../registry.js";
|
|
6
6
|
import { TextInput } from "@astryxdesign/core/TextInput";
|
|
7
7
|
import { rankResults, highlightParts } from "./lib/searchRank.js";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
if (typeof v === "string") {
|
|
11
|
-
parts.push(v);
|
|
12
|
-
return;
|
|
13
|
-
}
|
|
14
|
-
if (Array.isArray(v)) {
|
|
15
|
-
for (const item of v) collectStrings(item, parts, depth + 1);
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
if (typeof v === "object") {
|
|
19
|
-
for (const [k, val] of Object.entries(v)) {
|
|
20
|
-
if (k.startsWith("_") || k === "url" || k === "imageUrl" || k === "href") continue;
|
|
21
|
-
collectStrings(val, parts, depth + 1);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
function blocksToText(blocks) {
|
|
26
|
-
const parts = [];
|
|
27
|
-
collectStrings(blocks, parts, 0);
|
|
28
|
-
return parts.join(" ").replace(/<[^>]*>/g, " ");
|
|
29
|
-
}
|
|
8
|
+
import { useBlockData } from "../context.js";
|
|
9
|
+
import { blocksToText } from "../searchText.js";
|
|
30
10
|
function SearchRender({ block, siteId }) {
|
|
31
|
-
const
|
|
11
|
+
const serverIndex = useBlockData("searchIndex");
|
|
12
|
+
const [entries, setEntries] = useState(serverIndex ?? []);
|
|
32
13
|
const [q, setQ] = useState("");
|
|
33
14
|
useEffect(() => {
|
|
15
|
+
if (serverIndex) return;
|
|
34
16
|
let cancelled = false;
|
|
35
17
|
(async () => {
|
|
36
18
|
const [{ getDb }, { collection, getDocs, query, where }] = await Promise.all([
|
|
@@ -72,7 +54,7 @@ function SearchRender({ block, siteId }) {
|
|
|
72
54
|
return () => {
|
|
73
55
|
cancelled = true;
|
|
74
56
|
};
|
|
75
|
-
}, [siteId, block.includeNews]);
|
|
57
|
+
}, [siteId, block.includeNews, serverIndex]);
|
|
76
58
|
const [debouncedQ, setDebouncedQ] = useState("");
|
|
77
59
|
useEffect(() => {
|
|
78
60
|
const t = setTimeout(() => setDebouncedQ(q), 250);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/blocks/builtins/Search.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useMemo, useState } from \"react\";\nimport type { z } from \"zod\";\nimport { SearchBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { TextInput } from \"@astryxdesign/core/TextInput\";\nimport { rankResults, highlightParts } from \"./lib/searchRank\";\n\ntype SearchBlock = z.infer<typeof SearchBlockSchema>;\n\
|
|
1
|
+
{"version":3,"sources":["../../../src/blocks/builtins/Search.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useMemo, useState } from \"react\";\nimport type { z } from \"zod\";\nimport { SearchBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { TextInput } from \"@astryxdesign/core/TextInput\";\nimport { rankResults, highlightParts } from \"./lib/searchRank\";\nimport { useBlockData } from \"../context\";\nimport { blocksToText, type SearchEntry } from \"../searchText\";\n\ntype SearchBlock = z.infer<typeof SearchBlockSchema>;\n\ntype Entry = SearchEntry;\n\n// De tekst-vlakkers (collectStrings/blocksToText) zijn gedeeld met de server-zoekindex:\n// zie ../searchText.ts — client en server moeten exact dezelfde tekst zien.\n\n/** Site-zoek — leest gepubliceerde pagina's (en optioneel nieuws) en filtert client-side. */\nfunction SearchRender({ block, siteId }: BlockRenderProps<SearchBlock>) {\n // Server-seed (7.12): de route levert de complete zoekindex als blockData mee. Dan is zoeken\n // instant én gratis — de client-fetch hieronder las anders de HELE pagina-collectie per\n // bezoeker, veruit de duurste read van het hele systeem.\n const serverIndex = useBlockData<Entry[]>(\"searchIndex\");\n const [entries, setEntries] = useState<Entry[]>(serverIndex ?? []);\n const [q, setQ] = useState(\"\");\n\n useEffect(() => {\n if (serverIndex) return;\n let cancelled = false;\n (async () => {\n // Firestore dynamisch: de zoekindex wordt pas opgebouwd wanneer het zoekblok in beeld is,\n // dus een gewone pagina downloadt de SDK niet.\n const [{ getDb }, { collection, getDocs, query, where }] = await Promise.all([\n import(\"../../firebase/client\"),\n import(\"firebase/firestore\"),\n ]);\n const db = getDb();\n if (!db || cancelled) return;\n const out: Entry[] = [];\n try {\n const pagesSnap = await getDocs(\n query(collection(db, `sites/${siteId}/pages`), where(\"status\", \"==\", \"published\")),\n );\n pagesSnap.forEach((d) => {\n const data = d.data() as Record<string, unknown>;\n out.push({\n title: String(data.title ?? \"\"),\n path: String(data.path ?? \"/\"),\n text: blocksToText(data.blocks),\n });\n });\n if (block.includeNews) {\n const newsSnap = await getDocs(\n query(collection(db, `sites/${siteId}/news`), where(\"published\", \"==\", true)),\n );\n newsSnap.forEach((d) => {\n const data = d.data() as Record<string, unknown>;\n out.push({\n title: String(data.title ?? \"\"),\n path: `/nieuws/${String(data.slug ?? d.id)}`,\n text: String(data.excerpt ?? \"\") + \" \" + String(data.body ?? \"\").replace(/<[^>]*>/g, \" \"),\n });\n });\n }\n } catch {\n /* stil: zonder leesrechten/Firebase blijft het zoekveld leeg */\n }\n if (!cancelled) setEntries(out);\n })();\n return () => {\n cancelled = true;\n };\n }, [siteId, block.includeNews, serverIndex]);\n\n // Debounce (250ms): niet bij elke toetsaanslag opnieuw ranken/renderen.\n const [debouncedQ, setDebouncedQ] = useState(\"\");\n useEffect(() => {\n const t = setTimeout(() => setDebouncedQ(q), 250);\n return () => clearTimeout(t);\n }, [q]);\n\n const results = useMemo(() => rankResults(debouncedQ, entries), [entries, debouncedQ]);\n\n const mark = (text: string) =>\n highlightParts(text, debouncedQ).map((p, i) => (p.hit ? <mark key={i}>{p.text}</mark> : <span key={i}>{p.text}</span>));\n\n return (\n <div className=\"xicms-search\">\n <TextInput\n label={block.placeholder}\n isLabelHidden\n placeholder={block.placeholder}\n value={q}\n onChange={setQ}\n />\n {debouncedQ.trim() && (\n <ul className=\"xicms-search__results\">\n {results.length === 0 ? (\n <li className=\"xicms-search__empty\">Niets gevonden.</li>\n ) : (\n results.map((r, i) => (\n <li key={i} className=\"xicms-search__result\">\n <a href={r.path}>{mark(r.title || r.path)}</a>\n {r.snippet && <p className=\"xicms-search__snippet\">{mark(r.snippet)}</p>}\n </li>\n ))\n )}\n </ul>\n )}\n </div>\n );\n}\n\nexport const searchBlock = defineBlock({\n type: \"search\",\n schema: SearchBlockSchema,\n Render: SearchRender,\n label: \"Zoeken\",\n icon: \"🔍\",\n defaults: () => ({ placeholder: \"Zoeken…\", includeNews: true }),\n});\n"],"mappings":";AAqF4D,cAiB9C,YAjB8C;AAnF5D,SAAS,WAAW,SAAS,gBAAgB;AAE7C,SAAS,yBAAyB;AAClC,SAAS,mBAA0C;AACnD,SAAS,iBAAiB;AAC1B,SAAS,aAAa,sBAAsB;AAC5C,SAAS,oBAAoB;AAC7B,SAAS,oBAAsC;AAU/C,SAAS,aAAa,EAAE,OAAO,OAAO,GAAkC;AAItE,QAAM,cAAc,aAAsB,aAAa;AACvD,QAAM,CAAC,SAAS,UAAU,IAAI,SAAkB,eAAe,CAAC,CAAC;AACjE,QAAM,CAAC,GAAG,IAAI,IAAI,SAAS,EAAE;AAE7B,YAAU,MAAM;AACd,QAAI,YAAa;AACjB,QAAI,YAAY;AAChB,KAAC,YAAY;AAGX,YAAM,CAAC,EAAE,MAAM,GAAG,EAAE,YAAY,SAAS,OAAO,MAAM,CAAC,IAAI,MAAM,QAAQ,IAAI;AAAA,QAC3E,OAAO,uBAAuB;AAAA,QAC9B,OAAO,oBAAoB;AAAA,MAC7B,CAAC;AACD,YAAM,KAAK,MAAM;AACjB,UAAI,CAAC,MAAM,UAAW;AACtB,YAAM,MAAe,CAAC;AACtB,UAAI;AACF,cAAM,YAAY,MAAM;AAAA,UACtB,MAAM,WAAW,IAAI,SAAS,MAAM,QAAQ,GAAG,MAAM,UAAU,MAAM,WAAW,CAAC;AAAA,QACnF;AACA,kBAAU,QAAQ,CAAC,MAAM;AACvB,gBAAM,OAAO,EAAE,KAAK;AACpB,cAAI,KAAK;AAAA,YACP,OAAO,OAAO,KAAK,SAAS,EAAE;AAAA,YAC9B,MAAM,OAAO,KAAK,QAAQ,GAAG;AAAA,YAC7B,MAAM,aAAa,KAAK,MAAM;AAAA,UAChC,CAAC;AAAA,QACH,CAAC;AACD,YAAI,MAAM,aAAa;AACrB,gBAAM,WAAW,MAAM;AAAA,YACrB,MAAM,WAAW,IAAI,SAAS,MAAM,OAAO,GAAG,MAAM,aAAa,MAAM,IAAI,CAAC;AAAA,UAC9E;AACA,mBAAS,QAAQ,CAAC,MAAM;AACtB,kBAAM,OAAO,EAAE,KAAK;AACpB,gBAAI,KAAK;AAAA,cACP,OAAO,OAAO,KAAK,SAAS,EAAE;AAAA,cAC9B,MAAM,WAAW,OAAO,KAAK,QAAQ,EAAE,EAAE,CAAC;AAAA,cAC1C,MAAM,OAAO,KAAK,WAAW,EAAE,IAAI,MAAM,OAAO,KAAK,QAAQ,EAAE,EAAE,QAAQ,YAAY,GAAG;AAAA,YAC1F,CAAC;AAAA,UACH,CAAC;AAAA,QACH;AAAA,MACF,QAAQ;AAAA,MAER;AACA,UAAI,CAAC,UAAW,YAAW,GAAG;AAAA,IAChC,GAAG;AACH,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,QAAQ,MAAM,aAAa,WAAW,CAAC;AAG3C,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,EAAE;AAC/C,YAAU,MAAM;AACd,UAAM,IAAI,WAAW,MAAM,cAAc,CAAC,GAAG,GAAG;AAChD,WAAO,MAAM,aAAa,CAAC;AAAA,EAC7B,GAAG,CAAC,CAAC,CAAC;AAEN,QAAM,UAAU,QAAQ,MAAM,YAAY,YAAY,OAAO,GAAG,CAAC,SAAS,UAAU,CAAC;AAErF,QAAM,OAAO,CAAC,SACZ,eAAe,MAAM,UAAU,EAAE,IAAI,CAAC,GAAG,MAAO,EAAE,MAAM,oBAAC,UAAc,YAAE,QAAN,CAAW,IAAU,oBAAC,UAAc,YAAE,QAAN,CAAW,CAAQ;AAExH,SACE,qBAAC,SAAI,WAAU,gBACb;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,MAAM;AAAA,QACb,eAAa;AAAA,QACb,aAAa,MAAM;AAAA,QACnB,OAAO;AAAA,QACP,UAAU;AAAA;AAAA,IACZ;AAAA,IACC,WAAW,KAAK,KACf,oBAAC,QAAG,WAAU,yBACX,kBAAQ,WAAW,IAClB,oBAAC,QAAG,WAAU,uBAAsB,6BAAe,IAEnD,QAAQ,IAAI,CAAC,GAAG,MACd,qBAAC,QAAW,WAAU,wBACpB;AAAA,0BAAC,OAAE,MAAM,EAAE,MAAO,eAAK,EAAE,SAAS,EAAE,IAAI,GAAE;AAAA,MACzC,EAAE,WAAW,oBAAC,OAAE,WAAU,yBAAyB,eAAK,EAAE,OAAO,GAAE;AAAA,SAF7D,CAGT,CACD,GAEL;AAAA,KAEJ;AAEJ;AAEO,MAAM,cAAc,YAAY;AAAA,EACrC,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,OAAO,EAAE,aAAa,gBAAW,aAAa,KAAK;AAC/D,CAAC;","names":[]}
|
|
@@ -6,8 +6,10 @@ import { SymbolSchema } from "../../schema/symbols.js";
|
|
|
6
6
|
import { defineBlock } from "../registry.js";
|
|
7
7
|
import { BlockRenderer } from "../BlockRenderer.js";
|
|
8
8
|
import { useRegistry } from "../context.js";
|
|
9
|
+
import { useEditor } from "../../editor/EditorProvider.js";
|
|
9
10
|
function SymbolRefRender({ block, siteId, mode }) {
|
|
10
11
|
const registry = useRegistry();
|
|
12
|
+
const bewerken = useEditor().open || mode === "preview";
|
|
11
13
|
const [symbol, setSymbol] = useState(void 0);
|
|
12
14
|
useEffect(() => {
|
|
13
15
|
if (!block.symbolId) {
|
|
@@ -26,20 +28,32 @@ function SymbolRefRender({ block, siteId, mode }) {
|
|
|
26
28
|
setSymbol(null);
|
|
27
29
|
return;
|
|
28
30
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
const ref = doc(db, "sites", siteId, "symbols", block.symbolId);
|
|
32
|
+
const zet = (data, bestaat) => {
|
|
33
|
+
const parsed = bestaat ? SymbolSchema.safeParse(data) : null;
|
|
34
|
+
setSymbol(parsed?.success ? parsed.data : null);
|
|
35
|
+
};
|
|
36
|
+
if (bewerken) {
|
|
37
|
+
unsub = onSnapshot(
|
|
38
|
+
ref,
|
|
39
|
+
(snap) => zet(snap.data(), snap.exists()),
|
|
40
|
+
() => setSymbol(null)
|
|
41
|
+
);
|
|
42
|
+
} else {
|
|
43
|
+
const { getDoc } = await import("firebase/firestore");
|
|
44
|
+
try {
|
|
45
|
+
const snap = await getDoc(ref);
|
|
46
|
+
if (!afgebroken) zet(snap.data(), snap.exists());
|
|
47
|
+
} catch {
|
|
48
|
+
if (!afgebroken) setSymbol(null);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
37
51
|
})();
|
|
38
52
|
return () => {
|
|
39
53
|
afgebroken = true;
|
|
40
54
|
unsub?.();
|
|
41
55
|
};
|
|
42
|
-
}, [siteId, block.symbolId]);
|
|
56
|
+
}, [siteId, block.symbolId, bewerken]);
|
|
43
57
|
if (symbol === void 0) return null;
|
|
44
58
|
if (symbol === null) {
|
|
45
59
|
return mode === "preview" ? /* @__PURE__ */ jsxs("p", { className: "xicms-ve__hint", children: [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/blocks/builtins/SymbolRef.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport type { z } from \"zod\";\nimport { SymbolRefBlockSchema } from \"../../schema/blocks\";\nimport { SymbolSchema, type SymbolDefinition } from \"../../schema/symbols\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { BlockRenderer } from \"../BlockRenderer\";\nimport { useRegistry } from \"../context\";\n\ntype SymbolRef = z.infer<typeof SymbolRefBlockSchema>;\n\n/**\n * Verwijzing naar een gedeeld symbool. Op de LIVE site wordt een symbolRef normaliter al\n * server-side vervangen door de symbool-blokken (server/pages.ts `expandSymbols`) — dan komt\n * deze Render niet eens aan bod. In de editor/preview (en als client-fallback) rendert hij het\n * symbool live vanuit Firestore, met een subtiel kader zodat de editor ziet dat dit gedeelde\n * inhoud is die op meerdere pagina's meebeweegt.\n */\nfunction SymbolRefRender({ block, siteId, mode }: BlockRenderProps<SymbolRef>) {\n const registry = useRegistry();\n const [symbol, setSymbol] = useState<SymbolDefinition | null | undefined>(undefined);\n useEffect(() => {\n if (!block.symbolId) {\n setSymbol(null);\n return;\n }\n // Firestore dynamisch laden: een pagina zonder symbolen haalt de SDK zo nooit binnen, en een\n // pagina mét symbolen pas op het moment dat er echt iets op te halen valt.\n let unsub: (() => void) | undefined;\n let afgebroken = false;\n void (async () => {\n const [{ getDb }, { doc, onSnapshot }] = await Promise.all([\n import(\"../../firebase/client\"),\n import(\"firebase/firestore\"),\n ]);\n const db = getDb();\n if (!db || afgebroken) {\n setSymbol(null);\n return;\n }\n
|
|
1
|
+
{"version":3,"sources":["../../../src/blocks/builtins/SymbolRef.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport type { z } from \"zod\";\nimport { SymbolRefBlockSchema } from \"../../schema/blocks\";\nimport { SymbolSchema, type SymbolDefinition } from \"../../schema/symbols\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { BlockRenderer } from \"../BlockRenderer\";\nimport { useRegistry } from \"../context\";\nimport { useEditor } from \"../../editor/EditorProvider\";\n\ntype SymbolRef = z.infer<typeof SymbolRefBlockSchema>;\n\n/**\n * Verwijzing naar een gedeeld symbool. Op de LIVE site wordt een symbolRef normaliter al\n * server-side vervangen door de symbool-blokken (server/pages.ts `expandSymbols`) — dan komt\n * deze Render niet eens aan bod. In de editor/preview (en als client-fallback) rendert hij het\n * symbool live vanuit Firestore, met een subtiel kader zodat de editor ziet dat dit gedeelde\n * inhoud is die op meerdere pagina's meebeweegt.\n */\nfunction SymbolRefRender({ block, siteId, mode }: BlockRenderProps<SymbolRef>) {\n const registry = useRegistry();\n // Live meebewegen is alleen nuttig terwijl er BEWERKT wordt. Voor een gewone bezoeker was de\n // onSnapshot hieronder het duurste patroon van het systeem: een read plus een blijvend open\n // verbinding, per bezoeker — voor content die toch pas verandert als iemand publiceert.\n const bewerken = useEditor().open || mode === \"preview\";\n const [symbol, setSymbol] = useState<SymbolDefinition | null | undefined>(undefined);\n useEffect(() => {\n if (!block.symbolId) {\n setSymbol(null);\n return;\n }\n // Firestore dynamisch laden: een pagina zonder symbolen haalt de SDK zo nooit binnen, en een\n // pagina mét symbolen pas op het moment dat er echt iets op te halen valt.\n let unsub: (() => void) | undefined;\n let afgebroken = false;\n void (async () => {\n const [{ getDb }, { doc, onSnapshot }] = await Promise.all([\n import(\"../../firebase/client\"),\n import(\"firebase/firestore\"),\n ]);\n const db = getDb();\n if (!db || afgebroken) {\n setSymbol(null);\n return;\n }\n const ref = doc(db, \"sites\", siteId, \"symbols\", block.symbolId!);\n const zet = (data: unknown, bestaat: boolean) => {\n const parsed = bestaat ? SymbolSchema.safeParse(data) : null;\n setSymbol(parsed?.success ? parsed.data : null);\n };\n if (bewerken) {\n unsub = onSnapshot(\n ref,\n (snap) => zet(snap.data(), snap.exists()),\n () => setSymbol(null),\n );\n } else {\n // Publieke fallback (zeldzaam: normaal is de ref al server-side geëxpandeerd): één read,\n // geen listener.\n const { getDoc } = await import(\"firebase/firestore\");\n try {\n const snap = await getDoc(ref);\n if (!afgebroken) zet(snap.data(), snap.exists());\n } catch {\n if (!afgebroken) setSymbol(null);\n }\n }\n })();\n return () => {\n afgebroken = true;\n unsub?.();\n };\n }, [siteId, block.symbolId, bewerken]);\n\n if (symbol === undefined) return null; // laden — geen flits\n if (symbol === null) {\n // Verweesde referentie: nooit de pagina breken; in preview uitleggen, live niets tonen.\n return mode === \"preview\" ? (\n <p className=\"xicms-ve__hint\">🧩 Symbool niet gevonden ({block.symbolId}) — kies een ander symbool of verwijder dit blok.</p>\n ) : null;\n }\n const inner = <BlockRenderer blocks={symbol.blocks} registry={registry} siteId={siteId} mode={mode} />;\n if (mode === \"preview\") {\n return (\n <div className=\"xicms-symbol\" data-symbol={block.symbolId}>\n <div className=\"xicms-symbol__badge\">🧩 {symbol.name} — gedeeld symbool (wijzigingen gelden op élke pagina)</div>\n {inner}\n </div>\n );\n }\n return inner;\n}\n\nexport const symbolRefBlock = defineBlock({\n type: \"symbolRef\",\n schema: SymbolRefBlockSchema,\n Render: SymbolRefRender,\n label: \"Symbool (gedeelde sectie)\",\n icon: \"🧩\",\n defaults: () => ({ symbolId: \"\" }),\n});\n"],"mappings":";AA+EM,SAGU,KAHV;AA7EN,SAAS,WAAW,gBAAgB;AAEpC,SAAS,4BAA4B;AACrC,SAAS,oBAA2C;AACpD,SAAS,mBAA0C;AACnD,SAAS,qBAAqB;AAC9B,SAAS,mBAAmB;AAC5B,SAAS,iBAAiB;AAW1B,SAAS,gBAAgB,EAAE,OAAO,QAAQ,KAAK,GAAgC;AAC7E,QAAM,WAAW,YAAY;AAI7B,QAAM,WAAW,UAAU,EAAE,QAAQ,SAAS;AAC9C,QAAM,CAAC,QAAQ,SAAS,IAAI,SAA8C,MAAS;AACnF,YAAU,MAAM;AACd,QAAI,CAAC,MAAM,UAAU;AACnB,gBAAU,IAAI;AACd;AAAA,IACF;AAGA,QAAI;AACJ,QAAI,aAAa;AACjB,UAAM,YAAY;AAChB,YAAM,CAAC,EAAE,MAAM,GAAG,EAAE,KAAK,WAAW,CAAC,IAAI,MAAM,QAAQ,IAAI;AAAA,QACzD,OAAO,uBAAuB;AAAA,QAC9B,OAAO,oBAAoB;AAAA,MAC7B,CAAC;AACD,YAAM,KAAK,MAAM;AACjB,UAAI,CAAC,MAAM,YAAY;AACrB,kBAAU,IAAI;AACd;AAAA,MACF;AACA,YAAM,MAAM,IAAI,IAAI,SAAS,QAAQ,WAAW,MAAM,QAAS;AAC/D,YAAM,MAAM,CAAC,MAAe,YAAqB;AAC/C,cAAM,SAAS,UAAU,aAAa,UAAU,IAAI,IAAI;AACxD,kBAAU,QAAQ,UAAU,OAAO,OAAO,IAAI;AAAA,MAChD;AACA,UAAI,UAAU;AACZ,gBAAQ;AAAA,UACN;AAAA,UACA,CAAC,SAAS,IAAI,KAAK,KAAK,GAAG,KAAK,OAAO,CAAC;AAAA,UACxC,MAAM,UAAU,IAAI;AAAA,QACtB;AAAA,MACF,OAAO;AAGL,cAAM,EAAE,OAAO,IAAI,MAAM,OAAO,oBAAoB;AACpD,YAAI;AACF,gBAAM,OAAO,MAAM,OAAO,GAAG;AAC7B,cAAI,CAAC,WAAY,KAAI,KAAK,KAAK,GAAG,KAAK,OAAO,CAAC;AAAA,QACjD,QAAQ;AACN,cAAI,CAAC,WAAY,WAAU,IAAI;AAAA,QACjC;AAAA,MACF;AAAA,IACF,GAAG;AACH,WAAO,MAAM;AACX,mBAAa;AACb,cAAQ;AAAA,IACV;AAAA,EACF,GAAG,CAAC,QAAQ,MAAM,UAAU,QAAQ,CAAC;AAErC,MAAI,WAAW,OAAW,QAAO;AACjC,MAAI,WAAW,MAAM;AAEnB,WAAO,SAAS,YACd,qBAAC,OAAE,WAAU,kBAAiB;AAAA;AAAA,MAA2B,MAAM;AAAA,MAAS;AAAA,OAAiD,IACvH;AAAA,EACN;AACA,QAAM,QAAQ,oBAAC,iBAAc,QAAQ,OAAO,QAAQ,UAAoB,QAAgB,MAAY;AACpG,MAAI,SAAS,WAAW;AACtB,WACE,qBAAC,SAAI,WAAU,gBAAe,eAAa,MAAM,UAC/C;AAAA,2BAAC,SAAI,WAAU,uBAAsB;AAAA;AAAA,QAAI,OAAO;AAAA,QAAK;AAAA,SAAsD;AAAA,MAC1G;AAAA,OACH;AAAA,EAEJ;AACA,SAAO;AACT;AAEO,MAAM,iBAAiB,YAAY;AAAA,EACxC,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,OAAO,EAAE,UAAU,GAAG;AAClC,CAAC;","names":[]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pagina-blokken → doorzoekbare platte tekst. Pure functies, bewust zonder "use client" of
|
|
3
|
+
* "server-only": het zoekblok (client) en de server-zoekindex (server/blockData.ts) moeten
|
|
4
|
+
* EXACT dezelfde tekst zien, anders vindt de een wat de ander mist.
|
|
5
|
+
*/
|
|
6
|
+
/** Eén regel in de zoekindex — de vorm die het zoekblok rankt en toont. */
|
|
7
|
+
export interface SearchEntry {
|
|
8
|
+
title: string;
|
|
9
|
+
path: string;
|
|
10
|
+
text: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Verzamel alle mens-leesbare strings, recursief: `cards.items[].title/text`, `faq.items[].q/a`,
|
|
14
|
+
* en alles binnen columns/withSidebar/tabs. Alleen top-level strings pakken zou zoeken vrijwel
|
|
15
|
+
* leeg maken op content-rijke pagina's.
|
|
16
|
+
*/
|
|
17
|
+
export declare function collectStrings(v: unknown, parts: string[], depth: number): void;
|
|
18
|
+
/** Blokken → één platte tekst (HTML-tags gestript). */
|
|
19
|
+
export declare function blocksToText(blocks: unknown): string;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
function collectStrings(v, parts, depth) {
|
|
2
|
+
if (depth > 6 || v == null) return;
|
|
3
|
+
if (typeof v === "string") {
|
|
4
|
+
parts.push(v);
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
if (Array.isArray(v)) {
|
|
8
|
+
for (const item of v) collectStrings(item, parts, depth + 1);
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
if (typeof v === "object") {
|
|
12
|
+
for (const [k, val] of Object.entries(v)) {
|
|
13
|
+
if (k.startsWith("_") || k === "url" || k === "imageUrl" || k === "href") continue;
|
|
14
|
+
collectStrings(val, parts, depth + 1);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function blocksToText(blocks) {
|
|
19
|
+
const parts = [];
|
|
20
|
+
collectStrings(blocks, parts, 0);
|
|
21
|
+
return parts.join(" ").replace(/<[^>]*>/g, " ");
|
|
22
|
+
}
|
|
23
|
+
export {
|
|
24
|
+
blocksToText,
|
|
25
|
+
collectStrings
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=searchText.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/blocks/searchText.ts"],"sourcesContent":["/**\n * Pagina-blokken → doorzoekbare platte tekst. Pure functies, bewust zonder \"use client\" of\n * \"server-only\": het zoekblok (client) en de server-zoekindex (server/blockData.ts) moeten\n * EXACT dezelfde tekst zien, anders vindt de een wat de ander mist.\n */\n\n/** Eén regel in de zoekindex — de vorm die het zoekblok rankt en toont. */\nexport interface SearchEntry {\n title: string;\n path: string;\n text: string;\n}\n\n/**\n * Verzamel alle mens-leesbare strings, recursief: `cards.items[].title/text`, `faq.items[].q/a`,\n * en alles binnen columns/withSidebar/tabs. Alleen top-level strings pakken zou zoeken vrijwel\n * leeg maken op content-rijke pagina's.\n */\nexport function collectStrings(v: unknown, parts: string[], depth: number): void {\n if (depth > 6 || v == null) return; // diepte-cap tegen pathologische structuren\n if (typeof v === \"string\") {\n parts.push(v);\n return;\n }\n if (Array.isArray(v)) {\n for (const item of v) collectStrings(item, parts, depth + 1);\n return;\n }\n if (typeof v === \"object\") {\n for (const [k, val] of Object.entries(v as Record<string, unknown>)) {\n if (k.startsWith(\"_\") || k === \"url\" || k === \"imageUrl\" || k === \"href\") continue; // meta/urls niet doorzoekbaar\n collectStrings(val, parts, depth + 1);\n }\n }\n}\n\n/** Blokken → één platte tekst (HTML-tags gestript). */\nexport function blocksToText(blocks: unknown): string {\n const parts: string[] = [];\n collectStrings(blocks, parts, 0);\n return parts.join(\" \").replace(/<[^>]*>/g, \" \");\n}\n"],"mappings":"AAkBO,SAAS,eAAe,GAAY,OAAiB,OAAqB;AAC/E,MAAI,QAAQ,KAAK,KAAK,KAAM;AAC5B,MAAI,OAAO,MAAM,UAAU;AACzB,UAAM,KAAK,CAAC;AACZ;AAAA,EACF;AACA,MAAI,MAAM,QAAQ,CAAC,GAAG;AACpB,eAAW,QAAQ,EAAG,gBAAe,MAAM,OAAO,QAAQ,CAAC;AAC3D;AAAA,EACF;AACA,MAAI,OAAO,MAAM,UAAU;AACzB,eAAW,CAAC,GAAG,GAAG,KAAK,OAAO,QAAQ,CAA4B,GAAG;AACnE,UAAI,EAAE,WAAW,GAAG,KAAK,MAAM,SAAS,MAAM,cAAc,MAAM,OAAQ;AAC1E,qBAAe,KAAK,OAAO,QAAQ,CAAC;AAAA,IACtC;AAAA,EACF;AACF;AAGO,SAAS,aAAa,QAAyB;AACpD,QAAM,QAAkB,CAAC;AACzB,iBAAe,QAAQ,OAAO,CAAC;AAC/B,SAAO,MAAM,KAAK,GAAG,EAAE,QAAQ,YAAY,GAAG;AAChD;","names":[]}
|
package/dist/demo/demoStore.d.ts
CHANGED
|
@@ -11,6 +11,14 @@ export declare function demoIdForPath(path: string): string;
|
|
|
11
11
|
export declare function demoPathForId(id: string): string | null;
|
|
12
12
|
export declare function isDemoLoggedIn(): boolean;
|
|
13
13
|
export declare function setDemoLoggedIn(on: boolean): void;
|
|
14
|
+
/**
|
|
15
|
+
* De EERSTE registratie wint. Een seed is per definitie het origineel, en het origineel verandert
|
|
16
|
+
* niet tijdens een sessie. Zonder deze regel vervuilde de seed zichzelf: een demo-route registreert
|
|
17
|
+
* bij module-load de zuivere pagina, maar `PublishedPage` registreert in een effect de pagina die
|
|
18
|
+
* hij TOONT — en dat is in de demo seed+draft. De bewerkte tekst werd zo het "origineel", waarmee
|
|
19
|
+
* de wijzigingen-teller schoon leek en "blok verwerpen" terugzette naar precies de tekst die je
|
|
20
|
+
* wilde verwerpen. (De seeds leven in het geheugen van één pageload, dus "eerste" is per bezoek.)
|
|
21
|
+
*/
|
|
14
22
|
export declare function registerSeed(siteId: string, path: string, page: Page): void;
|
|
15
23
|
export declare function getSeed(siteId: string, path: string): Page | null;
|
|
16
24
|
export declare function registerDemoNews(siteId: string, articles: NewsArticle[]): void;
|
package/dist/demo/demoStore.js
CHANGED
|
@@ -41,7 +41,8 @@ function setDemoLoggedIn(on) {
|
|
|
41
41
|
emit();
|
|
42
42
|
}
|
|
43
43
|
function registerSeed(siteId, path, page) {
|
|
44
|
-
|
|
44
|
+
const key = `${siteId}::${path}`;
|
|
45
|
+
if (!seeds.has(key)) seeds.set(key, page);
|
|
45
46
|
}
|
|
46
47
|
function getSeed(siteId, path) {
|
|
47
48
|
return seeds.get(`${siteId}::${path}`) ?? null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/demo/demoStore.ts"],"sourcesContent":["\"use client\";\n\nimport type { Page } from \"../schema/pages\";\nimport type { MediaItem, NewsArticle } from \"../content/types\";\nimport type { FormDefinition } from \"../schema/forms\";\n\n/**\n * Lokale opslag voor de DEMO-bewerkmodus (`config.demoEditing`). Alle \"bewerkingen\" van een\n * bezoeker (teksten, blokken, publiceren) blijven UITSLUITEND in de browser van die bezoeker\n * (localStorage) — er gaat niets naar Firebase. Zo kunnen eindgebruikers het beheer veilig\n * uitproberen zonder dat er echt iets verandert. Een \"Reset\" wist alles weer.\n *\n * Bewust GEEN onderdeel van de echte (Firebase-)opslag: pagesClient en AuthProvider takken hier\n * alleen naartoe als de demo-modus actief is, en die staat alleen aan op een demo-site.\n */\n\nconst DRAFT_PREFIX = \"xicms-demo-draft\";\nconst MEDIA_PREFIX = \"xicms-demo-media\";\nconst SESSION_KEY = \"xicms-demo-session\";\nexport const DEMO_EVENT = \"xicms:demo\";\n\nlet active = false;\nconst seeds = new Map<string, Page>();\nconst newsSeeds = new Map<string, NewsArticle[]>();\nconst formSeeds = new Map<string, FormDefinition>();\nconst mediaSeeds = new Map<string, MediaItem[]>();\n\n/** Zet de demo-bewerkmodus aan/uit (door de provider, op basis van config.demoEditing). */\nexport function setDemoActive(on: boolean) {\n active = on;\n}\nexport function isDemoActive(): boolean {\n return active;\n}\n\n/** Stabiel, deterministisch draft-id per pad, zodat pagina + editor naar dezelfde opslag wijzen. */\nexport function demoIdForPath(path: string): string {\n return `demo:${path || \"/\"}`;\n}\n\n/** De omkering: uit een demo-draft-id het pad terug. `null` voor een niet-demo-id. */\nexport function demoPathForId(id: string): string | null {\n return id.startsWith(\"demo:\") ? id.slice(\"demo:\".length) : null;\n}\n\nfunction emit() {\n if (typeof window !== \"undefined\") window.dispatchEvent(new Event(DEMO_EVENT));\n}\n\n// ── Pseudo-login (sessie) ──────────────────────────────────────────────────\n// In sessionStorage: verdwijnt als het tabblad sluit — benadrukt het tijdelijke karakter.\nexport function isDemoLoggedIn(): boolean {\n if (typeof window === \"undefined\") return false;\n try {\n return window.sessionStorage.getItem(SESSION_KEY) === \"1\";\n } catch {\n return false;\n }\n}\nexport function setDemoLoggedIn(on: boolean) {\n if (typeof window === \"undefined\") return;\n try {\n if (on) window.sessionStorage.setItem(SESSION_KEY, \"1\");\n else window.sessionStorage.removeItem(SESSION_KEY);\n } catch {\n /* opslag geweigerd — login geldt dan alleen voor deze render */\n }\n emit();\n}\n\n// ── Seed-pagina's (de oorspronkelijke inhoud waar een bezoeker op verder bewerkt) ──\nexport function registerSeed(siteId: string, path: string, page: Page) {\n seeds.set(`${siteId}::${path}`, page);\n}\nexport function getSeed(siteId: string, path: string): Page | null {\n return seeds.get(`${siteId}::${path}`) ?? null;\n}\n\n// ── Demo-nieuws (zodat nieuws-blokken/widgets ook in de demo gevuld zijn, zonder Firebase) ──\nexport function registerDemoNews(siteId: string, articles: NewsArticle[]) {\n newsSeeds.set(siteId, articles);\n}\nexport function getDemoNews(siteId: string): NewsArticle[] {\n return newsSeeds.get(siteId) ?? [];\n}\n\n// ── Demo-formulieren (zodat het form-blok werkt zonder Firebase; inzenden doet lokaal niets) ──\nexport function registerDemoForm(siteId: string, formId: string, def: FormDefinition) {\n formSeeds.set(`${siteId}::${formId}`, def);\n}\nexport function getDemoForm(siteId: string, formId: string): FormDefinition | null {\n return formSeeds.get(`${siteId}::${formId}`) ?? null;\n}\n\n// ── Demo-media (bibliotheek + upload zonder Firebase) ─────────────────────\n// Geseede items (bv. de foto's van de demo-site zelf) + lokale \"uploads\" als data-URL in\n// localStorage. Zo werken kiezen, uploaden en vervangen in de demo écht — alles blijft lokaal.\n\n/** Seed de mediabibliotheek van de demo (curated set; alt = zoektekst in de kiezer). */\nexport function registerDemoMedia(siteId: string, items: Array<Pick<MediaItem, \"url\" | \"name\" | \"alt\">>) {\n mediaSeeds.set(\n siteId,\n items.map((m, i) => ({ id: `demo-seed-${i}`, url: m.url, path: \"demo\", name: m.name, alt: m.alt ?? \"\" })),\n );\n}\n\nfunction mediaKey(siteId: string) {\n return `${MEDIA_PREFIX}:${siteId}`;\n}\n\nfunction loadLocalDemoMedia(siteId: string): MediaItem[] {\n if (typeof window === \"undefined\") return [];\n try {\n const raw = window.localStorage.getItem(mediaKey(siteId));\n return raw ? (JSON.parse(raw) as MediaItem[]) : [];\n } catch {\n return [];\n }\n}\n\n/** Bibliotheek in demo-modus: lokale uploads (nieuwste eerst) + de geseede set. */\nexport function listDemoMedia(siteId: string): MediaItem[] {\n return [...loadLocalDemoMedia(siteId), ...(mediaSeeds.get(siteId) ?? [])];\n}\n\n/**\n * \"Upload\" in demo-modus: bewaar de (gecomprimeerde) afbeelding als data-URL in localStorage.\n * localStorage is beperkt (~5 MB) — we houden maximaal 10 lokale uploads aan en laten bij een\n * volle opslag het item alleen voor deze sessie in het geheugen bestaan (zelfde vangnet als drafts).\n */\nconst memoryOnlyMedia = new Map<string, MediaItem[]>();\nexport function addDemoMedia(siteId: string, item: { url: string; name: string }): MediaItem {\n const media: MediaItem = {\n id: `demo-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,\n url: item.url,\n path: \"demo\",\n name: item.name,\n alt: \"\",\n };\n const next = [media, ...loadLocalDemoMedia(siteId)].slice(0, 10);\n try {\n window.localStorage.setItem(mediaKey(siteId), JSON.stringify(next));\n } catch {\n memoryOnlyMedia.set(siteId, [media, ...(memoryOnlyMedia.get(siteId) ?? [])]);\n }\n emit();\n return media;\n}\n\n/** Inclusief geheugen-only items (opslag-vol-vangnet) — gebruikt door de kiezer. */\nexport function listDemoMediaAll(siteId: string): MediaItem[] {\n return [...(memoryOnlyMedia.get(siteId) ?? []), ...listDemoMedia(siteId)];\n}\n\n// ── Lokale drafts (de bewerkingen) ─────────────────────────────────────────\nfunction draftKey(siteId: string, id: string) {\n return `${DRAFT_PREFIX}:${siteId}:${id}`;\n}\n\nexport function loadDemoDraft(siteId: string, id: string): Partial<Page> | null {\n if (typeof window === \"undefined\") return null;\n try {\n const raw = window.localStorage.getItem(draftKey(siteId, id));\n return raw ? (JSON.parse(raw) as Partial<Page>) : null;\n } catch {\n return null;\n }\n}\n\nexport function saveDemoDraft(siteId: string, id: string, patch: Partial<Page>) {\n if (typeof window === \"undefined\") return;\n try {\n const current = loadDemoDraft(siteId, id) ?? {};\n window.localStorage.setItem(draftKey(siteId, id), JSON.stringify({ ...current, ...patch }));\n } catch {\n /* opslag vol/geweigerd — bewerking geldt dan alleen voor deze sessie in het geheugen */\n }\n emit();\n}\n\n/** Verwijdert alle lokale demo-bewerkingen + login voor deze site (de \"Reset\"-knop). */\nexport function resetDemo(siteId: string) {\n if (typeof window === \"undefined\") return;\n try {\n const toRemove: string[] = [];\n for (let i = 0; i < window.localStorage.length; i++) {\n const k = window.localStorage.key(i);\n if (k && k.startsWith(`${DRAFT_PREFIX}:${siteId}:`)) toRemove.push(k);\n }\n toRemove.forEach((k) => window.localStorage.removeItem(k));\n window.localStorage.removeItem(mediaKey(siteId));\n memoryOnlyMedia.delete(siteId);\n window.sessionStorage.removeItem(SESSION_KEY);\n } catch {\n /* negeren */\n }\n emit();\n}\n\n/**\n * De effectieve demo-pagina = seed met lokale bewerkingen eroverheen. Gebruikt door de demo-pagina\n * om de wijzigingen van de bezoeker direct te tonen (ook na sluiten van de editor).\n */\nexport function resolveDemoPage(siteId: string, path: string): Page | null {\n const seed = getSeed(siteId, path);\n if (!seed) return null;\n const draft = loadDemoDraft(siteId, demoIdForPath(path));\n return draft ? ({ ...seed, ...draft } as Page) : seed;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/demo/demoStore.ts"],"sourcesContent":["\"use client\";\n\nimport type { Page } from \"../schema/pages\";\nimport type { MediaItem, NewsArticle } from \"../content/types\";\nimport type { FormDefinition } from \"../schema/forms\";\n\n/**\n * Lokale opslag voor de DEMO-bewerkmodus (`config.demoEditing`). Alle \"bewerkingen\" van een\n * bezoeker (teksten, blokken, publiceren) blijven UITSLUITEND in de browser van die bezoeker\n * (localStorage) — er gaat niets naar Firebase. Zo kunnen eindgebruikers het beheer veilig\n * uitproberen zonder dat er echt iets verandert. Een \"Reset\" wist alles weer.\n *\n * Bewust GEEN onderdeel van de echte (Firebase-)opslag: pagesClient en AuthProvider takken hier\n * alleen naartoe als de demo-modus actief is, en die staat alleen aan op een demo-site.\n */\n\nconst DRAFT_PREFIX = \"xicms-demo-draft\";\nconst MEDIA_PREFIX = \"xicms-demo-media\";\nconst SESSION_KEY = \"xicms-demo-session\";\nexport const DEMO_EVENT = \"xicms:demo\";\n\nlet active = false;\nconst seeds = new Map<string, Page>();\nconst newsSeeds = new Map<string, NewsArticle[]>();\nconst formSeeds = new Map<string, FormDefinition>();\nconst mediaSeeds = new Map<string, MediaItem[]>();\n\n/** Zet de demo-bewerkmodus aan/uit (door de provider, op basis van config.demoEditing). */\nexport function setDemoActive(on: boolean) {\n active = on;\n}\nexport function isDemoActive(): boolean {\n return active;\n}\n\n/** Stabiel, deterministisch draft-id per pad, zodat pagina + editor naar dezelfde opslag wijzen. */\nexport function demoIdForPath(path: string): string {\n return `demo:${path || \"/\"}`;\n}\n\n/** De omkering: uit een demo-draft-id het pad terug. `null` voor een niet-demo-id. */\nexport function demoPathForId(id: string): string | null {\n return id.startsWith(\"demo:\") ? id.slice(\"demo:\".length) : null;\n}\n\nfunction emit() {\n if (typeof window !== \"undefined\") window.dispatchEvent(new Event(DEMO_EVENT));\n}\n\n// ── Pseudo-login (sessie) ──────────────────────────────────────────────────\n// In sessionStorage: verdwijnt als het tabblad sluit — benadrukt het tijdelijke karakter.\nexport function isDemoLoggedIn(): boolean {\n if (typeof window === \"undefined\") return false;\n try {\n return window.sessionStorage.getItem(SESSION_KEY) === \"1\";\n } catch {\n return false;\n }\n}\nexport function setDemoLoggedIn(on: boolean) {\n if (typeof window === \"undefined\") return;\n try {\n if (on) window.sessionStorage.setItem(SESSION_KEY, \"1\");\n else window.sessionStorage.removeItem(SESSION_KEY);\n } catch {\n /* opslag geweigerd — login geldt dan alleen voor deze render */\n }\n emit();\n}\n\n// ── Seed-pagina's (de oorspronkelijke inhoud waar een bezoeker op verder bewerkt) ──\n/**\n * De EERSTE registratie wint. Een seed is per definitie het origineel, en het origineel verandert\n * niet tijdens een sessie. Zonder deze regel vervuilde de seed zichzelf: een demo-route registreert\n * bij module-load de zuivere pagina, maar `PublishedPage` registreert in een effect de pagina die\n * hij TOONT — en dat is in de demo seed+draft. De bewerkte tekst werd zo het \"origineel\", waarmee\n * de wijzigingen-teller schoon leek en \"blok verwerpen\" terugzette naar precies de tekst die je\n * wilde verwerpen. (De seeds leven in het geheugen van één pageload, dus \"eerste\" is per bezoek.)\n */\nexport function registerSeed(siteId: string, path: string, page: Page) {\n const key = `${siteId}::${path}`;\n if (!seeds.has(key)) seeds.set(key, page);\n}\nexport function getSeed(siteId: string, path: string): Page | null {\n return seeds.get(`${siteId}::${path}`) ?? null;\n}\n\n// ── Demo-nieuws (zodat nieuws-blokken/widgets ook in de demo gevuld zijn, zonder Firebase) ──\nexport function registerDemoNews(siteId: string, articles: NewsArticle[]) {\n newsSeeds.set(siteId, articles);\n}\nexport function getDemoNews(siteId: string): NewsArticle[] {\n return newsSeeds.get(siteId) ?? [];\n}\n\n// ── Demo-formulieren (zodat het form-blok werkt zonder Firebase; inzenden doet lokaal niets) ──\nexport function registerDemoForm(siteId: string, formId: string, def: FormDefinition) {\n formSeeds.set(`${siteId}::${formId}`, def);\n}\nexport function getDemoForm(siteId: string, formId: string): FormDefinition | null {\n return formSeeds.get(`${siteId}::${formId}`) ?? null;\n}\n\n// ── Demo-media (bibliotheek + upload zonder Firebase) ─────────────────────\n// Geseede items (bv. de foto's van de demo-site zelf) + lokale \"uploads\" als data-URL in\n// localStorage. Zo werken kiezen, uploaden en vervangen in de demo écht — alles blijft lokaal.\n\n/** Seed de mediabibliotheek van de demo (curated set; alt = zoektekst in de kiezer). */\nexport function registerDemoMedia(siteId: string, items: Array<Pick<MediaItem, \"url\" | \"name\" | \"alt\">>) {\n mediaSeeds.set(\n siteId,\n items.map((m, i) => ({ id: `demo-seed-${i}`, url: m.url, path: \"demo\", name: m.name, alt: m.alt ?? \"\" })),\n );\n}\n\nfunction mediaKey(siteId: string) {\n return `${MEDIA_PREFIX}:${siteId}`;\n}\n\nfunction loadLocalDemoMedia(siteId: string): MediaItem[] {\n if (typeof window === \"undefined\") return [];\n try {\n const raw = window.localStorage.getItem(mediaKey(siteId));\n return raw ? (JSON.parse(raw) as MediaItem[]) : [];\n } catch {\n return [];\n }\n}\n\n/** Bibliotheek in demo-modus: lokale uploads (nieuwste eerst) + de geseede set. */\nexport function listDemoMedia(siteId: string): MediaItem[] {\n return [...loadLocalDemoMedia(siteId), ...(mediaSeeds.get(siteId) ?? [])];\n}\n\n/**\n * \"Upload\" in demo-modus: bewaar de (gecomprimeerde) afbeelding als data-URL in localStorage.\n * localStorage is beperkt (~5 MB) — we houden maximaal 10 lokale uploads aan en laten bij een\n * volle opslag het item alleen voor deze sessie in het geheugen bestaan (zelfde vangnet als drafts).\n */\nconst memoryOnlyMedia = new Map<string, MediaItem[]>();\nexport function addDemoMedia(siteId: string, item: { url: string; name: string }): MediaItem {\n const media: MediaItem = {\n id: `demo-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,\n url: item.url,\n path: \"demo\",\n name: item.name,\n alt: \"\",\n };\n const next = [media, ...loadLocalDemoMedia(siteId)].slice(0, 10);\n try {\n window.localStorage.setItem(mediaKey(siteId), JSON.stringify(next));\n } catch {\n memoryOnlyMedia.set(siteId, [media, ...(memoryOnlyMedia.get(siteId) ?? [])]);\n }\n emit();\n return media;\n}\n\n/** Inclusief geheugen-only items (opslag-vol-vangnet) — gebruikt door de kiezer. */\nexport function listDemoMediaAll(siteId: string): MediaItem[] {\n return [...(memoryOnlyMedia.get(siteId) ?? []), ...listDemoMedia(siteId)];\n}\n\n// ── Lokale drafts (de bewerkingen) ─────────────────────────────────────────\nfunction draftKey(siteId: string, id: string) {\n return `${DRAFT_PREFIX}:${siteId}:${id}`;\n}\n\nexport function loadDemoDraft(siteId: string, id: string): Partial<Page> | null {\n if (typeof window === \"undefined\") return null;\n try {\n const raw = window.localStorage.getItem(draftKey(siteId, id));\n return raw ? (JSON.parse(raw) as Partial<Page>) : null;\n } catch {\n return null;\n }\n}\n\nexport function saveDemoDraft(siteId: string, id: string, patch: Partial<Page>) {\n if (typeof window === \"undefined\") return;\n try {\n const current = loadDemoDraft(siteId, id) ?? {};\n window.localStorage.setItem(draftKey(siteId, id), JSON.stringify({ ...current, ...patch }));\n } catch {\n /* opslag vol/geweigerd — bewerking geldt dan alleen voor deze sessie in het geheugen */\n }\n emit();\n}\n\n/** Verwijdert alle lokale demo-bewerkingen + login voor deze site (de \"Reset\"-knop). */\nexport function resetDemo(siteId: string) {\n if (typeof window === \"undefined\") return;\n try {\n const toRemove: string[] = [];\n for (let i = 0; i < window.localStorage.length; i++) {\n const k = window.localStorage.key(i);\n if (k && k.startsWith(`${DRAFT_PREFIX}:${siteId}:`)) toRemove.push(k);\n }\n toRemove.forEach((k) => window.localStorage.removeItem(k));\n window.localStorage.removeItem(mediaKey(siteId));\n memoryOnlyMedia.delete(siteId);\n window.sessionStorage.removeItem(SESSION_KEY);\n } catch {\n /* negeren */\n }\n emit();\n}\n\n/**\n * De effectieve demo-pagina = seed met lokale bewerkingen eroverheen. Gebruikt door de demo-pagina\n * om de wijzigingen van de bezoeker direct te tonen (ook na sluiten van de editor).\n */\nexport function resolveDemoPage(siteId: string, path: string): Page | null {\n const seed = getSeed(siteId, path);\n if (!seed) return null;\n const draft = loadDemoDraft(siteId, demoIdForPath(path));\n return draft ? ({ ...seed, ...draft } as Page) : seed;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { createContext, useCallback, useContext, useEffect, useMemo, useState
|
|
4
|
-
|
|
3
|
+
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
|
|
4
|
+
let visualEditorModule = null;
|
|
5
5
|
const OPSLAGSLEUTEL = "xicms-bewerkmodus";
|
|
6
6
|
const Ctx = createContext({
|
|
7
7
|
modus: "live",
|
|
@@ -22,6 +22,7 @@ const Ctx = createContext({
|
|
|
22
22
|
});
|
|
23
23
|
function EditorProvider({ children }) {
|
|
24
24
|
const [modus, setModusState] = useState("live");
|
|
25
|
+
const [VisualEditor, setVisualEditor] = useState(() => visualEditorModule);
|
|
25
26
|
const [draft, setDraft] = useState(null);
|
|
26
27
|
const [fotoVervanger, setFotoVervanger] = useState(null);
|
|
27
28
|
const zetFotoVervanger = useCallback((fn) => {
|
|
@@ -33,6 +34,17 @@ function EditorProvider({ children }) {
|
|
|
33
34
|
} catch {
|
|
34
35
|
}
|
|
35
36
|
}, []);
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
if (modus !== "bewerken" || VisualEditor) return;
|
|
39
|
+
let losgekoppeld = false;
|
|
40
|
+
void import("../admin/visual/VisualEditor.js").then((m) => {
|
|
41
|
+
visualEditorModule = m.default;
|
|
42
|
+
if (!losgekoppeld) setVisualEditor(() => m.default);
|
|
43
|
+
});
|
|
44
|
+
return () => {
|
|
45
|
+
losgekoppeld = true;
|
|
46
|
+
};
|
|
47
|
+
}, [modus, VisualEditor]);
|
|
36
48
|
const zetModus = useCallback((next) => {
|
|
37
49
|
setModusState(next);
|
|
38
50
|
try {
|
|
@@ -59,7 +71,7 @@ function EditorProvider({ children }) {
|
|
|
59
71
|
}), [modus, draft, zetModus, publishDraft, fotoVervanger, zetFotoVervanger]);
|
|
60
72
|
return /* @__PURE__ */ jsxs(Ctx.Provider, { value, children: [
|
|
61
73
|
children,
|
|
62
|
-
modus === "bewerken" &&
|
|
74
|
+
modus === "bewerken" && VisualEditor && /* @__PURE__ */ jsx(VisualEditor, { onClose: () => zetModus("live") })
|
|
63
75
|
] });
|
|
64
76
|
}
|
|
65
77
|
function useEditor() {
|