@xiwebdesign/xicms-core 7.10.0 → 7.11.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/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/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"]}
|
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":";AAgBA,MAAM,eAAe;AACrB,MAAM,eAAe;AACrB,MAAM,cAAc;AACb,MAAM,aAAa;AAE1B,IAAI,SAAS;AACb,MAAM,QAAQ,oBAAI,IAAkB;AACpC,MAAM,YAAY,oBAAI,IAA2B;AACjD,MAAM,YAAY,oBAAI,IAA4B;AAClD,MAAM,aAAa,oBAAI,IAAyB;AAGzC,SAAS,cAAc,IAAa;AACzC,WAAS;AACX;AACO,SAAS,eAAwB;AACtC,SAAO;AACT;AAGO,SAAS,cAAc,MAAsB;AAClD,SAAO,QAAQ,QAAQ,GAAG;AAC5B;AAGO,SAAS,cAAc,IAA2B;AACvD,SAAO,GAAG,WAAW,OAAO,IAAI,GAAG,MAAM,QAAQ,MAAM,IAAI;AAC7D;AAEA,SAAS,OAAO;AACd,MAAI,OAAO,WAAW,YAAa,QAAO,cAAc,IAAI,MAAM,UAAU,CAAC;AAC/E;AAIO,SAAS,iBAA0B;AACxC,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,MAAI;AACF,WAAO,OAAO,eAAe,QAAQ,WAAW,MAAM;AAAA,EACxD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AACO,SAAS,gBAAgB,IAAa;AAC3C,MAAI,OAAO,WAAW,YAAa;AACnC,MAAI;AACF,QAAI,GAAI,QAAO,eAAe,QAAQ,aAAa,GAAG;AAAA,QACjD,QAAO,eAAe,WAAW,WAAW;AAAA,EACnD,QAAQ;AAAA,EAER;AACA,OAAK;AACP;AAGO,SAAS,aAAa,QAAgB,MAAc,MAAY;AACrE,QAAM,IAAI,GAAG,MAAM,KAAK,IAAI,IAAI,IAAI;AACtC;AACO,SAAS,QAAQ,QAAgB,MAA2B;AACjE,SAAO,MAAM,IAAI,GAAG,MAAM,KAAK,IAAI,EAAE,KAAK;AAC5C;AAGO,SAAS,iBAAiB,QAAgB,UAAyB;AACxE,YAAU,IAAI,QAAQ,QAAQ;AAChC;AACO,SAAS,YAAY,QAA+B;AACzD,SAAO,UAAU,IAAI,MAAM,KAAK,CAAC;AACnC;AAGO,SAAS,iBAAiB,QAAgB,QAAgB,KAAqB;AACpF,YAAU,IAAI,GAAG,MAAM,KAAK,MAAM,IAAI,GAAG;AAC3C;AACO,SAAS,YAAY,QAAgB,QAAuC;AACjF,SAAO,UAAU,IAAI,GAAG,MAAM,KAAK,MAAM,EAAE,KAAK;AAClD;AAOO,SAAS,kBAAkB,QAAgB,OAAuD;AACvG,aAAW;AAAA,IACT;AAAA,IACA,MAAM,IAAI,CAAC,GAAG,OAAO,EAAE,IAAI,aAAa,CAAC,IAAI,KAAK,EAAE,KAAK,MAAM,QAAQ,MAAM,EAAE,MAAM,KAAK,EAAE,OAAO,GAAG,EAAE;AAAA,EAC1G;AACF;AAEA,SAAS,SAAS,QAAgB;AAChC,SAAO,GAAG,YAAY,IAAI,MAAM;AAClC;AAEA,SAAS,mBAAmB,QAA6B;AACvD,MAAI,OAAO,WAAW,YAAa,QAAO,CAAC;AAC3C,MAAI;AACF,UAAM,MAAM,OAAO,aAAa,QAAQ,SAAS,MAAM,CAAC;AACxD,WAAO,MAAO,KAAK,MAAM,GAAG,IAAoB,CAAC;AAAA,EACnD,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAGO,SAAS,cAAc,QAA6B;AACzD,SAAO,CAAC,GAAG,mBAAmB,MAAM,GAAG,GAAI,WAAW,IAAI,MAAM,KAAK,CAAC,CAAE;AAC1E;AAOA,MAAM,kBAAkB,oBAAI,IAAyB;AAC9C,SAAS,aAAa,QAAgB,MAAgD;AAC3F,QAAM,QAAmB;AAAA,IACvB,IAAI,QAAQ,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC;AAAA,IAChE,KAAK,KAAK;AAAA,IACV,MAAM;AAAA,IACN,MAAM,KAAK;AAAA,IACX,KAAK;AAAA,EACP;AACA,QAAM,OAAO,CAAC,OAAO,GAAG,mBAAmB,MAAM,CAAC,EAAE,MAAM,GAAG,EAAE;AAC/D,MAAI;AACF,WAAO,aAAa,QAAQ,SAAS,MAAM,GAAG,KAAK,UAAU,IAAI,CAAC;AAAA,EACpE,QAAQ;AACN,oBAAgB,IAAI,QAAQ,CAAC,OAAO,GAAI,gBAAgB,IAAI,MAAM,KAAK,CAAC,CAAE,CAAC;AAAA,EAC7E;AACA,OAAK;AACL,SAAO;AACT;AAGO,SAAS,iBAAiB,QAA6B;AAC5D,SAAO,CAAC,GAAI,gBAAgB,IAAI,MAAM,KAAK,CAAC,GAAI,GAAG,cAAc,MAAM,CAAC;AAC1E;AAGA,SAAS,SAAS,QAAgB,IAAY;AAC5C,SAAO,GAAG,YAAY,IAAI,MAAM,IAAI,EAAE;AACxC;AAEO,SAAS,cAAc,QAAgB,IAAkC;AAC9E,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,MAAI;AACF,UAAM,MAAM,OAAO,aAAa,QAAQ,SAAS,QAAQ,EAAE,CAAC;AAC5D,WAAO,MAAO,KAAK,MAAM,GAAG,IAAsB;AAAA,EACpD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,cAAc,QAAgB,IAAY,OAAsB;AAC9E,MAAI,OAAO,WAAW,YAAa;AACnC,MAAI;AACF,UAAM,UAAU,cAAc,QAAQ,EAAE,KAAK,CAAC;AAC9C,WAAO,aAAa,QAAQ,SAAS,QAAQ,EAAE,GAAG,KAAK,UAAU,EAAE,GAAG,SAAS,GAAG,MAAM,CAAC,CAAC;AAAA,EAC5F,QAAQ;AAAA,EAER;AACA,OAAK;AACP;AAGO,SAAS,UAAU,QAAgB;AACxC,MAAI,OAAO,WAAW,YAAa;AACnC,MAAI;AACF,UAAM,WAAqB,CAAC;AAC5B,aAAS,IAAI,GAAG,IAAI,OAAO,aAAa,QAAQ,KAAK;AACnD,YAAM,IAAI,OAAO,aAAa,IAAI,CAAC;AACnC,UAAI,KAAK,EAAE,WAAW,GAAG,YAAY,IAAI,MAAM,GAAG,EAAG,UAAS,KAAK,CAAC;AAAA,IACtE;AACA,aAAS,QAAQ,CAAC,MAAM,OAAO,aAAa,WAAW,CAAC,CAAC;AACzD,WAAO,aAAa,WAAW,SAAS,MAAM,CAAC;AAC/C,oBAAgB,OAAO,MAAM;AAC7B,WAAO,eAAe,WAAW,WAAW;AAAA,EAC9C,QAAQ;AAAA,EAER;AACA,OAAK;AACP;AAMO,SAAS,gBAAgB,QAAgB,MAA2B;AACzE,QAAM,OAAO,QAAQ,QAAQ,IAAI;AACjC,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,QAAQ,cAAc,QAAQ,cAAc,IAAI,CAAC;AACvD,SAAO,QAAS,EAAE,GAAG,MAAM,GAAG,MAAM,IAAa;AACnD;","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() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/editor/EditorProvider.tsx"],"sourcesContent":["\"use client\";\n\nimport { createContext, useCallback, useContext, useEffect, useMemo, useState, lazy, Suspense, type ReactNode } from \"react\";\n// Het bloktype van de PAGINA zelf, zodat wat hier langskomt per definitie past in wat\n// `PublishedPage` rendert. Een eigen definitie zou hier stil uit de pas kunnen lopen.\nimport type { Page } from \"../schema/pages\";\n\ntype PaginaBlokken = Page[\"blocks\"];\n\n/**\n * Lichte runtime-context voor het inline (visuele) bewerken op de live pagina. De echte editor\n * (TipTap/Radix, zwaar) wordt PAS geladen als een editor hem opent — publieke bezoekers downloaden\n * hem nooit. De editor leeft in de admin-entry; we laden 'm via een dynamische import (eigen chunk).\n *\n * ── WAAROM DE BLOKKEN HIER LANGSKOMEN ─────────────────────────────────────────────────────\n *\n * De editor tekende de pagina vroeger zélf, in een eigen canvas binnen een schermvullende overlay.\n * Je klikte \"Pagina bewerken\" en je site verdween achter een donker paneel met een nabootsing erin.\n * Dat voelde niet als je site bewerken maar als een ander programma openen.\n *\n * Nu blijft de PAGINA de pagina en wordt alleen de DATA vervangen: de editor laadt de werkversie en\n * zet die hier neer, waarna `PublishedPage` de werkversie rendert in plaats van de gepubliceerde\n * blokken. Dezelfde componenten, dezelfde huisstijl, dezelfde layout — alleen andere inhoud.\n *\n * Die omweg via de data is wezenlijk, geen implementatiedetail. Alleen potloodjes over de live\n * pagina leggen zou betekenen dat je de WERKVERSIE bewerkt terwijl je de GEPUBLICEERDE tekst ziet.\n * Zodra je iets wijzigt en nog niet publiceert, lopen die twee uit elkaar en bewerk je iets anders\n * dan er staat. Wat je ziet moet zijn wat je bewerkt.\n *\n * `editPath` hoort erbij: een site kan meerdere gerenderde pagina's in beeld hebben (een\n * ingekaderde pagina, een preview). Alleen het pad dat de editor open heeft mag zijn blokken\n * verwisselen.\n *\n * ── WAAROM DE MODUS EEN REFRESH MOET OVERLEVEN (7.8) ──────────────────────────────────────\n *\n * Bewerken was een toestand die alleen in het geheugen van één render-boom bestond. Een per ongeluk\n * ingedrukte F5 — of een klik op een link in je eigen site, want dat is een volledige navigatie —\n * zette je terug op de live site. Je wijzigingen waren niet weg (die staan in de werkversie in\n * Firestore), maar je zág ze niet meer, en dat is niet te onderscheiden van kwijt zijn.\n *\n * Daarom staat de modus in `sessionStorage`: hij overleeft een refresh en het navigeren door de\n * site, en verdwijnt vanzelf zodra het tabblad dichtgaat. Bewust niet `localStorage` — dan zit je\n * morgen in een nieuw venster nog steeds in de bewerkmodus zonder dat je erom vroeg.\n */\nconst VisualEditor = lazy(() => import(\"../admin/visual/VisualEditor\"));\n\n/** \"live\" = precies wat een bezoeker ziet. \"bewerken\" = dezelfde pagina, met werkversie en gereedschap. */\nexport type Bewerkmodus = \"live\" | \"bewerken\";\n\nconst OPSLAGSLEUTEL = \"xicms-bewerkmodus\";\n\ninterface EditorCtx {\n modus: Bewerkmodus;\n /** Blijft bestaan naast `modus` omdat het door de hele codebase heen gelezen wordt. */\n open: boolean;\n zetModus: (modus: Bewerkmodus) => void;\n openEditor: () => void;\n closeEditor: () => void;\n /** De blokken van de werkversie die de pagina moet tonen; `null` = toon gewoon het gepubliceerde. */\n draftBlocks: PaginaBlokken | null;\n /** Het pad waarvoor `draftBlocks` geldt. */\n editPath: string | null;\n /** Door de editor aangeroepen zodra de werkversie laadt of wijzigt. */\n publishDraft: (path: string, blocks: PaginaBlokken | null) => void;\n /**\n * \"Vervang de foto in veld X van blok Y\" — gezet door de VisualEditor zolang hij open is.\n *\n * De 📷-knopjes op de foto's zitten in de RENDER van de pagina (Img), de mediakiezer zit in de\n * EDITOR. Die twee delen geen boom meer sinds het canvas weg is; dit is de brug. `null` buiten\n * de bewerkmodus, en dan tekent Img geen knopjes.\n */\n fotoVervanger: ((field: string, blockKey?: string) => void) | null;\n zetFotoVervanger: (fn: ((field: string, blockKey?: string) => void) | null) => void;\n}\n\nconst Ctx = createContext<EditorCtx>({\n modus: \"live\",\n open: false,\n zetModus: () => {},\n openEditor: () => {},\n closeEditor: () => {},\n draftBlocks: null,\n editPath: null,\n publishDraft: () => {},\n fotoVervanger: null,\n zetFotoVervanger: () => {},\n});\n\nexport function EditorProvider({ children }: { children: ReactNode }) {\n /**\n * Start altijd op \"live\", ook als de opslag iets anders zegt.\n *\n * De server rendert deze boom zonder toegang tot `sessionStorage`. Zou de eerste client-render\n * meteen \"bewerken\" teruggeven, dan wijkt hij af van de HTML die er al staat en gooit React de\n * boom weg (hydration mismatch). Vandaar: eerst hetzelfde als de server, daarna in een effect\n * bijstellen. Eén frame \"live\" is niet zichtbaar; een kapotte hydration wel.\n */\n const [modus, setModusState] = useState<Bewerkmodus>(\"live\");\n const [draft, setDraft] = useState<{ path: string; blocks: PaginaBlokken } | null>(null);\n const [fotoVervanger, setFotoVervanger] = useState<((field: string, blockKey?: string) => void) | null>(null);\n // In state gewikkeld: een functie rechtstreeks aan een setState geven zou hem AANROEPEN\n // (functionele updater) in plaats van opslaan.\n const zetFotoVervanger = useCallback((fn: ((field: string, blockKey?: string) => void) | null) => {\n setFotoVervanger(() => fn);\n }, []);\n\n useEffect(() => {\n try {\n if (sessionStorage.getItem(OPSLAGSLEUTEL) === \"bewerken\") setModusState(\"bewerken\");\n } catch {\n /* privémodus of geblokkeerde opslag: dan gewoon op de live site beginnen */\n }\n }, []);\n\n const zetModus = useCallback((next: Bewerkmodus) => {\n setModusState(next);\n try {\n if (next === \"bewerken\") sessionStorage.setItem(OPSLAGSLEUTEL, \"bewerken\");\n else sessionStorage.removeItem(OPSLAGSLEUTEL);\n } catch {\n /* de modus werkt ook zonder opslag; alleen een refresh valt dan terug op live */\n }\n // De werkversie hoort bij de bewerkmodus. Blijft hij staan na het sluiten, dan ziet de bezoeker\n // ongepubliceerde tekst terwijl het gereedschap weg is — een pagina die iets toont wat niet\n // live is. De werkversie zelf blijft in Firestore staan; alleen deze weergave stopt.\n if (next === \"live\") setDraft(null);\n }, []);\n\n const publishDraft = useCallback((path: string, blocks: PaginaBlokken | null) => {\n setDraft(blocks ? { path, blocks } : null);\n }, []);\n\n const value = useMemo<EditorCtx>(() => ({\n modus,\n open: modus === \"bewerken\",\n zetModus,\n openEditor: () => zetModus(\"bewerken\"),\n closeEditor: () => zetModus(\"live\"),\n draftBlocks: draft?.blocks ?? null,\n editPath: draft?.path ?? null,\n publishDraft,\n fotoVervanger,\n zetFotoVervanger,\n }), [modus, draft, zetModus, publishDraft, fotoVervanger, zetFotoVervanger]);\n\n return (\n <Ctx.Provider value={value}>\n {children}\n {modus === \"bewerken\" && (\n <Suspense fallback={null}>\n <VisualEditor onClose={() => zetModus(\"live\")} />\n </Suspense>\n )}\n </Ctx.Provider>\n );\n}\n\n/** Open/sluit de visuele pagina-editor. */\nexport function useEditor(): EditorCtx {\n return useContext(Ctx);\n}\n"],"mappings":";AAkJI,SAIM,KAJN;AAhJJ,SAAS,eAAe,aAAa,YAAY,WAAW,SAAS,UAAU,MAAM,gBAAgC;AA0CrH,MAAM,eAAe,KAAK,MAAM,OAAO,8BAA8B,CAAC;AAKtE,MAAM,gBAAgB;AA0BtB,MAAM,MAAM,cAAyB;AAAA,EACnC,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,MAAM;AAAA,EAAC;AAAA,EACjB,YAAY,MAAM;AAAA,EAAC;AAAA,EACnB,aAAa,MAAM;AAAA,EAAC;AAAA,EACpB,aAAa;AAAA,EACb,UAAU;AAAA,EACV,cAAc,MAAM;AAAA,EAAC;AAAA,EACrB,eAAe;AAAA,EACf,kBAAkB,MAAM;AAAA,EAAC;AAC3B,CAAC;AAEM,SAAS,eAAe,EAAE,SAAS,GAA4B;AASpE,QAAM,CAAC,OAAO,aAAa,IAAI,SAAsB,MAAM;AAC3D,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAyD,IAAI;AACvF,QAAM,CAAC,eAAe,gBAAgB,IAAI,SAA8D,IAAI;AAG5G,QAAM,mBAAmB,YAAY,CAAC,OAA4D;AAChG,qBAAiB,MAAM,EAAE;AAAA,EAC3B,GAAG,CAAC,CAAC;AAEL,YAAU,MAAM;AACd,QAAI;AACF,UAAI,eAAe,QAAQ,aAAa,MAAM,WAAY,eAAc,UAAU;AAAA,IACpF,QAAQ;AAAA,IAER;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,WAAW,YAAY,CAAC,SAAsB;AAClD,kBAAc,IAAI;AAClB,QAAI;AACF,UAAI,SAAS,WAAY,gBAAe,QAAQ,eAAe,UAAU;AAAA,UACpE,gBAAe,WAAW,aAAa;AAAA,IAC9C,QAAQ;AAAA,IAER;AAIA,QAAI,SAAS,OAAQ,UAAS,IAAI;AAAA,EACpC,GAAG,CAAC,CAAC;AAEL,QAAM,eAAe,YAAY,CAAC,MAAc,WAAiC;AAC/E,aAAS,SAAS,EAAE,MAAM,OAAO,IAAI,IAAI;AAAA,EAC3C,GAAG,CAAC,CAAC;AAEL,QAAM,QAAQ,QAAmB,OAAO;AAAA,IACtC;AAAA,IACA,MAAM,UAAU;AAAA,IAChB;AAAA,IACA,YAAY,MAAM,SAAS,UAAU;AAAA,IACrC,aAAa,MAAM,SAAS,MAAM;AAAA,IAClC,aAAa,OAAO,UAAU;AAAA,IAC9B,UAAU,OAAO,QAAQ;AAAA,IACzB;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,CAAC,OAAO,OAAO,UAAU,cAAc,eAAe,gBAAgB,CAAC;AAE3E,SACE,qBAAC,IAAI,UAAJ,EAAa,OACX;AAAA;AAAA,IACA,UAAU,cACT,oBAAC,YAAS,UAAU,MAClB,8BAAC,gBAAa,SAAS,MAAM,SAAS,MAAM,GAAG,GACjD;AAAA,KAEJ;AAEJ;AAGO,SAAS,YAAuB;AACrC,SAAO,WAAW,GAAG;AACvB;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/editor/EditorProvider.tsx"],"sourcesContent":["\"use client\";\n\nimport { createContext, useCallback, useContext, useEffect, useMemo, useState, type ComponentType, type ReactNode } from \"react\";\n// Het bloktype van de PAGINA zelf, zodat wat hier langskomt per definitie past in wat\n// `PublishedPage` rendert. Een eigen definitie zou hier stil uit de pas kunnen lopen.\nimport type { Page } from \"../schema/pages\";\n\ntype PaginaBlokken = Page[\"blocks\"];\n\n/**\n * Lichte runtime-context voor het inline (visuele) bewerken op de live pagina. De echte editor\n * (TipTap/Radix, zwaar) wordt PAS geladen als een editor hem opent — publieke bezoekers downloaden\n * hem nooit. De editor leeft in de admin-entry; we laden 'm via een dynamische import (eigen chunk).\n *\n * ── WAAROM DE BLOKKEN HIER LANGSKOMEN ─────────────────────────────────────────────────────\n *\n * De editor tekende de pagina vroeger zélf, in een eigen canvas binnen een schermvullende overlay.\n * Je klikte \"Pagina bewerken\" en je site verdween achter een donker paneel met een nabootsing erin.\n * Dat voelde niet als je site bewerken maar als een ander programma openen.\n *\n * Nu blijft de PAGINA de pagina en wordt alleen de DATA vervangen: de editor laadt de werkversie en\n * zet die hier neer, waarna `PublishedPage` de werkversie rendert in plaats van de gepubliceerde\n * blokken. Dezelfde componenten, dezelfde huisstijl, dezelfde layout — alleen andere inhoud.\n *\n * Die omweg via de data is wezenlijk, geen implementatiedetail. Alleen potloodjes over de live\n * pagina leggen zou betekenen dat je de WERKVERSIE bewerkt terwijl je de GEPUBLICEERDE tekst ziet.\n * Zodra je iets wijzigt en nog niet publiceert, lopen die twee uit elkaar en bewerk je iets anders\n * dan er staat. Wat je ziet moet zijn wat je bewerkt.\n *\n * `editPath` hoort erbij: een site kan meerdere gerenderde pagina's in beeld hebben (een\n * ingekaderde pagina, een preview). Alleen het pad dat de editor open heeft mag zijn blokken\n * verwisselen.\n *\n * ── WAAROM DE MODUS EEN REFRESH MOET OVERLEVEN (7.8) ──────────────────────────────────────\n *\n * Bewerken was een toestand die alleen in het geheugen van één render-boom bestond. Een per ongeluk\n * ingedrukte F5 — of een klik op een link in je eigen site, want dat is een volledige navigatie —\n * zette je terug op de live site. Je wijzigingen waren niet weg (die staan in de werkversie in\n * Firestore), maar je zág ze niet meer, en dat is niet te onderscheiden van kwijt zijn.\n *\n * Daarom staat de modus in `sessionStorage`: hij overleeft een refresh en het navigeren door de\n * site, en verdwijnt vanzelf zodra het tabblad dichtgaat. Bewust niet `localStorage` — dan zit je\n * morgen in een nieuw venster nog steeds in de bewerkmodus zonder dat je erom vroeg.\n */\n/**\n * BEWUST GEEN `lazy()` + `<Suspense>`. De chunk-splitsing is dezelfde (de dynamische import\n * hieronder maakt zijn eigen chunk), maar Suspense heeft een gedraging die ons hier de kop kostte:\n * als de boom suspendt, koppelt React de al-gemonteerde children tijdelijk los en weer vast\n * (disconnect/reconnect van de passive effects). In dat her-aansluitpad draaide Astryx'\n * `useScrollLock`-effect dubbel zonder tussenliggende cleanup, waardoor zijn module-brede teller\n * nooit meer op nul kwam — en een teller boven nul betekent: `body { position: fixed }`. De hele\n * site stond stil: niet meer scrollen, dus ook geen bewerkknoppen bij blokken onder de vouw.\n * Zelf laden in een effect en pas renderen als de module er is, heeft dat Suspense-pad niet.\n */\ntype VisualEditorComponent = ComponentType<{ onClose: () => void }>;\nlet visualEditorModule: VisualEditorComponent | null = null;\n\n/** \"live\" = precies wat een bezoeker ziet. \"bewerken\" = dezelfde pagina, met werkversie en gereedschap. */\nexport type Bewerkmodus = \"live\" | \"bewerken\";\n\nconst OPSLAGSLEUTEL = \"xicms-bewerkmodus\";\n\ninterface EditorCtx {\n modus: Bewerkmodus;\n /** Blijft bestaan naast `modus` omdat het door de hele codebase heen gelezen wordt. */\n open: boolean;\n zetModus: (modus: Bewerkmodus) => void;\n openEditor: () => void;\n closeEditor: () => void;\n /** De blokken van de werkversie die de pagina moet tonen; `null` = toon gewoon het gepubliceerde. */\n draftBlocks: PaginaBlokken | null;\n /** Het pad waarvoor `draftBlocks` geldt. */\n editPath: string | null;\n /** Door de editor aangeroepen zodra de werkversie laadt of wijzigt. */\n publishDraft: (path: string, blocks: PaginaBlokken | null) => void;\n /**\n * \"Vervang de foto in veld X van blok Y\" — gezet door de VisualEditor zolang hij open is.\n *\n * De 📷-knopjes op de foto's zitten in de RENDER van de pagina (Img), de mediakiezer zit in de\n * EDITOR. Die twee delen geen boom meer sinds het canvas weg is; dit is de brug. `null` buiten\n * de bewerkmodus, en dan tekent Img geen knopjes.\n */\n fotoVervanger: ((field: string, blockKey?: string) => void) | null;\n zetFotoVervanger: (fn: ((field: string, blockKey?: string) => void) | null) => void;\n}\n\nconst Ctx = createContext<EditorCtx>({\n modus: \"live\",\n open: false,\n zetModus: () => {},\n openEditor: () => {},\n closeEditor: () => {},\n draftBlocks: null,\n editPath: null,\n publishDraft: () => {},\n fotoVervanger: null,\n zetFotoVervanger: () => {},\n});\n\nexport function EditorProvider({ children }: { children: ReactNode }) {\n /**\n * Start altijd op \"live\", ook als de opslag iets anders zegt.\n *\n * De server rendert deze boom zonder toegang tot `sessionStorage`. Zou de eerste client-render\n * meteen \"bewerken\" teruggeven, dan wijkt hij af van de HTML die er al staat en gooit React de\n * boom weg (hydration mismatch). Vandaar: eerst hetzelfde als de server, daarna in een effect\n * bijstellen. Eén frame \"live\" is niet zichtbaar; een kapotte hydration wel.\n */\n const [modus, setModusState] = useState<Bewerkmodus>(\"live\");\n // De editor-component zodra zijn chunk binnen is; module-cache zodat een tweede keer openen\n // (of een andere pagina in dezelfde sessie) niet opnieuw op het netwerk wacht.\n const [VisualEditor, setVisualEditor] = useState<VisualEditorComponent | null>(() => visualEditorModule);\n const [draft, setDraft] = useState<{ path: string; blocks: PaginaBlokken } | null>(null);\n const [fotoVervanger, setFotoVervanger] = useState<((field: string, blockKey?: string) => void) | null>(null);\n // In state gewikkeld: een functie rechtstreeks aan een setState geven zou hem AANROEPEN\n // (functionele updater) in plaats van opslaan.\n const zetFotoVervanger = useCallback((fn: ((field: string, blockKey?: string) => void) | null) => {\n setFotoVervanger(() => fn);\n }, []);\n\n useEffect(() => {\n try {\n if (sessionStorage.getItem(OPSLAGSLEUTEL) === \"bewerken\") setModusState(\"bewerken\");\n } catch {\n /* privémodus of geblokkeerde opslag: dan gewoon op de live site beginnen */\n }\n }, []);\n\n // De editor-chunk laden zodra de bewerkmodus aangaat (zie het commentaar bij\n // `visualEditorModule` waarom dit met de hand gebeurt en niet via lazy/Suspense).\n useEffect(() => {\n if (modus !== \"bewerken\" || VisualEditor) return;\n let losgekoppeld = false;\n void import(\"../admin/visual/VisualEditor\").then((m) => {\n visualEditorModule = m.default;\n if (!losgekoppeld) setVisualEditor(() => m.default);\n });\n return () => {\n losgekoppeld = true;\n };\n }, [modus, VisualEditor]);\n\n const zetModus = useCallback((next: Bewerkmodus) => {\n setModusState(next);\n try {\n if (next === \"bewerken\") sessionStorage.setItem(OPSLAGSLEUTEL, \"bewerken\");\n else sessionStorage.removeItem(OPSLAGSLEUTEL);\n } catch {\n /* de modus werkt ook zonder opslag; alleen een refresh valt dan terug op live */\n }\n // De werkversie hoort bij de bewerkmodus. Blijft hij staan na het sluiten, dan ziet de bezoeker\n // ongepubliceerde tekst terwijl het gereedschap weg is — een pagina die iets toont wat niet\n // live is. De werkversie zelf blijft in Firestore staan; alleen deze weergave stopt.\n if (next === \"live\") setDraft(null);\n }, []);\n\n const publishDraft = useCallback((path: string, blocks: PaginaBlokken | null) => {\n setDraft(blocks ? { path, blocks } : null);\n }, []);\n\n const value = useMemo<EditorCtx>(() => ({\n modus,\n open: modus === \"bewerken\",\n zetModus,\n openEditor: () => zetModus(\"bewerken\"),\n closeEditor: () => zetModus(\"live\"),\n draftBlocks: draft?.blocks ?? null,\n editPath: draft?.path ?? null,\n publishDraft,\n fotoVervanger,\n zetFotoVervanger,\n }), [modus, draft, zetModus, publishDraft, fotoVervanger, zetFotoVervanger]);\n\n return (\n <Ctx.Provider value={value}>\n {children}\n {modus === \"bewerken\" && VisualEditor && <VisualEditor onClose={() => zetModus(\"live\")} />}\n </Ctx.Provider>\n );\n}\n\n/** Open/sluit de visuele pagina-editor. */\nexport function useEditor(): EditorCtx {\n return useContext(Ctx);\n}\n"],"mappings":";AA8KI,SAE2C,KAF3C;AA5KJ,SAAS,eAAe,aAAa,YAAY,WAAW,SAAS,gBAAoD;AAqDzH,IAAI,qBAAmD;AAKvD,MAAM,gBAAgB;AA0BtB,MAAM,MAAM,cAAyB;AAAA,EACnC,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,MAAM;AAAA,EAAC;AAAA,EACjB,YAAY,MAAM;AAAA,EAAC;AAAA,EACnB,aAAa,MAAM;AAAA,EAAC;AAAA,EACpB,aAAa;AAAA,EACb,UAAU;AAAA,EACV,cAAc,MAAM;AAAA,EAAC;AAAA,EACrB,eAAe;AAAA,EACf,kBAAkB,MAAM;AAAA,EAAC;AAC3B,CAAC;AAEM,SAAS,eAAe,EAAE,SAAS,GAA4B;AASpE,QAAM,CAAC,OAAO,aAAa,IAAI,SAAsB,MAAM;AAG3D,QAAM,CAAC,cAAc,eAAe,IAAI,SAAuC,MAAM,kBAAkB;AACvG,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAyD,IAAI;AACvF,QAAM,CAAC,eAAe,gBAAgB,IAAI,SAA8D,IAAI;AAG5G,QAAM,mBAAmB,YAAY,CAAC,OAA4D;AAChG,qBAAiB,MAAM,EAAE;AAAA,EAC3B,GAAG,CAAC,CAAC;AAEL,YAAU,MAAM;AACd,QAAI;AACF,UAAI,eAAe,QAAQ,aAAa,MAAM,WAAY,eAAc,UAAU;AAAA,IACpF,QAAQ;AAAA,IAER;AAAA,EACF,GAAG,CAAC,CAAC;AAIL,YAAU,MAAM;AACd,QAAI,UAAU,cAAc,aAAc;AAC1C,QAAI,eAAe;AACnB,SAAK,OAAO,8BAA8B,EAAE,KAAK,CAAC,MAAM;AACtD,2BAAqB,EAAE;AACvB,UAAI,CAAC,aAAc,iBAAgB,MAAM,EAAE,OAAO;AAAA,IACpD,CAAC;AACD,WAAO,MAAM;AACX,qBAAe;AAAA,IACjB;AAAA,EACF,GAAG,CAAC,OAAO,YAAY,CAAC;AAExB,QAAM,WAAW,YAAY,CAAC,SAAsB;AAClD,kBAAc,IAAI;AAClB,QAAI;AACF,UAAI,SAAS,WAAY,gBAAe,QAAQ,eAAe,UAAU;AAAA,UACpE,gBAAe,WAAW,aAAa;AAAA,IAC9C,QAAQ;AAAA,IAER;AAIA,QAAI,SAAS,OAAQ,UAAS,IAAI;AAAA,EACpC,GAAG,CAAC,CAAC;AAEL,QAAM,eAAe,YAAY,CAAC,MAAc,WAAiC;AAC/E,aAAS,SAAS,EAAE,MAAM,OAAO,IAAI,IAAI;AAAA,EAC3C,GAAG,CAAC,CAAC;AAEL,QAAM,QAAQ,QAAmB,OAAO;AAAA,IACtC;AAAA,IACA,MAAM,UAAU;AAAA,IAChB;AAAA,IACA,YAAY,MAAM,SAAS,UAAU;AAAA,IACrC,aAAa,MAAM,SAAS,MAAM;AAAA,IAClC,aAAa,OAAO,UAAU;AAAA,IAC9B,UAAU,OAAO,QAAQ;AAAA,IACzB;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,CAAC,OAAO,OAAO,UAAU,cAAc,eAAe,gBAAgB,CAAC;AAE3E,SACE,qBAAC,IAAI,UAAJ,EAAa,OACX;AAAA;AAAA,IACA,UAAU,cAAc,gBAAgB,oBAAC,gBAAa,SAAS,MAAM,SAAS,MAAM,GAAG;AAAA,KAC1F;AAEJ;AAGO,SAAS,YAAuB;AACrC,SAAO,WAAW,GAAG;AACvB;","names":[]}
|
package/dist/styles.css
CHANGED
|
@@ -324,20 +324,11 @@
|
|
|
324
324
|
user-select: none;
|
|
325
325
|
}
|
|
326
326
|
.xicms-ve__body {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
grid-template-columns: 1fr;
|
|
333
|
-
min-height: 0;
|
|
334
|
-
}
|
|
335
|
-
.xicms-ve__body.is-panel-open {
|
|
336
|
-
/* 360px was gekozen voor blok-instellingen (een handvol velden). Sinds het productformulier in
|
|
337
|
-
ditzelfde paneel opent (7.8) is dat te smal: twee kolommen velden, varianten-tabellen en
|
|
338
|
-
foto-rijen vouwden zich dubbel. clamp: nooit smaller dan de oude 360, nooit meer dan 480, en
|
|
339
|
-
op een gewoon scherm ± een kwart — de pagina blijft de hoofdzaak. */
|
|
340
|
-
grid-template-columns: 1fr clamp(360px, 28vw, 480px);
|
|
327
|
+
/* Net als de shell: geen eigen doos. Dit was een schermvullend grid met [canvas | paneel],
|
|
328
|
+
maar het canvas bestaat niet meer — de live pagina eronder ís het werkblad. Als grid-in-de-
|
|
329
|
+
flow belandde het paneel ONDER de volledige pagina: technisch aanwezig, feitelijk
|
|
330
|
+
onvindbaar. Het paneel positioneert zichzelf nu als vaste lade (hieronder). */
|
|
331
|
+
display: contents;
|
|
341
332
|
}
|
|
342
333
|
.xicms-ve__canvas {
|
|
343
334
|
overflow: auto;
|
|
@@ -349,6 +340,20 @@
|
|
|
349
340
|
background-size: 24px 24px;
|
|
350
341
|
}
|
|
351
342
|
.xicms-ve__panel {
|
|
343
|
+
/* Vaste lade rechts, van onder de editorbalk tot aan de demo-balk (of de schermrand).
|
|
344
|
+
360–480 breed: nooit smaller dan blok-instellingen nodig hebben, nooit meer dan het
|
|
345
|
+
productformulier vraagt — de pagina blijft de hoofdzaak. z-index boven de publiceerknop
|
|
346
|
+
zou de knop verstoppen; erónder blijven (10001) en niet de volle breedte pakken is genoeg. */
|
|
347
|
+
position: fixed;
|
|
348
|
+
top: 2.6rem;
|
|
349
|
+
right: 0;
|
|
350
|
+
bottom: var(--xicms-bottombar, 0px);
|
|
351
|
+
width: clamp(360px, 28vw, 480px);
|
|
352
|
+
max-width: 100vw;
|
|
353
|
+
/* Boven de blokknoppen en potloodjes (10004-10005): die horen bij de pagina en moeten onder
|
|
354
|
+
de lade doorlopen, niet er bovenop zweven. */
|
|
355
|
+
z-index: 10007;
|
|
356
|
+
box-shadow: -12px 0 32px rgba(15, 23, 42, 0.18);
|
|
352
357
|
overflow: auto;
|
|
353
358
|
border-left: 1px solid var(--color-border, #e2e8f0);
|
|
354
359
|
background: var(--color-background-card, #fff);
|
|
@@ -377,6 +382,7 @@
|
|
|
377
382
|
}
|
|
378
383
|
.xicms-ve__inserter {
|
|
379
384
|
display: flex;
|
|
385
|
+
flex-wrap: wrap;
|
|
380
386
|
align-items: center;
|
|
381
387
|
gap: 0.75rem;
|
|
382
388
|
margin-top: 1rem;
|
|
@@ -402,17 +408,19 @@
|
|
|
402
408
|
order: 3;
|
|
403
409
|
flex-basis: 100%;
|
|
404
410
|
}
|
|
405
|
-
.xicms-ve__body {
|
|
406
|
-
grid-template-columns: 1fr;
|
|
407
|
-
grid-template-rows: 1fr auto;
|
|
408
|
-
}
|
|
409
411
|
.xicms-ve__canvas {
|
|
410
412
|
padding: 1rem;
|
|
411
413
|
}
|
|
414
|
+
/* Telefoon: de lade komt van onderen (een zijlade van 360px zou het hele scherm zijn). */
|
|
412
415
|
.xicms-ve__panel {
|
|
416
|
+
top: auto;
|
|
417
|
+
left: 0;
|
|
418
|
+
right: 0;
|
|
419
|
+
width: auto;
|
|
420
|
+
max-height: 60vh;
|
|
413
421
|
border-left: 0;
|
|
414
422
|
border-top: 2px solid #cbd5e1;
|
|
415
|
-
|
|
423
|
+
box-shadow: 0 -12px 32px rgba(15, 23, 42, 0.18);
|
|
416
424
|
}
|
|
417
425
|
.xicms-ve__inserter {
|
|
418
426
|
flex-wrap: wrap;
|
|
@@ -3170,6 +3178,12 @@
|
|
|
3170
3178
|
.xicms-ve-blokknop--gevaar:hover {
|
|
3171
3179
|
background: rgba(220, 38, 38, 0.85);
|
|
3172
3180
|
}
|
|
3181
|
+
.xicms-ve-blokknop--klaar {
|
|
3182
|
+
background: rgba(22, 163, 74, 0.5);
|
|
3183
|
+
}
|
|
3184
|
+
.xicms-ve-blokknop--klaar:hover {
|
|
3185
|
+
background: rgba(22, 163, 74, 0.9);
|
|
3186
|
+
}
|
|
3173
3187
|
|
|
3174
3188
|
/* Producten dragen een iets andere tint zodat je ziet dat je iets anders aanwijst dan een blok:
|
|
3175
3189
|
wijzigingen aan een product gaan meteen live en horen niet met paginatekst verward te worden. */
|
|
@@ -3214,6 +3228,17 @@
|
|
|
3214
3228
|
right: 20px;
|
|
3215
3229
|
bottom: calc(20px + var(--xicms-bottombar, 0px));
|
|
3216
3230
|
z-index: 10006;
|
|
3231
|
+
transition: right 0.2s ease;
|
|
3232
|
+
}
|
|
3233
|
+
/* Lade open → knop schuift mee naar links, anders ligt hij óver het formulier waarin je net
|
|
3234
|
+
aan het typen bent. Alleen op schermen waar de lade een zijlade is (op mobiel komt hij van
|
|
3235
|
+
onderen en blijft de knop gewoon rechts). */
|
|
3236
|
+
@media (min-width: 721px) {
|
|
3237
|
+
:root:has(.xicms-ve__panel) .xicms-ve-publiceer {
|
|
3238
|
+
right: calc(clamp(360px, 28vw, 480px) + 20px);
|
|
3239
|
+
}
|
|
3240
|
+
}
|
|
3241
|
+
.xicms-ve-publiceer {
|
|
3217
3242
|
display: grid;
|
|
3218
3243
|
gap: 2px;
|
|
3219
3244
|
justify-items: center;
|
package/dist/version.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const CORE_VERSION = true ? "7.
|
|
1
|
+
const CORE_VERSION = true ? "7.11.0" : "0.0.0-dev";
|
|
2
2
|
const CORE_PACKAGE = "@xiwebdesign/xicms-core";
|
|
3
3
|
function isNieuwer(beschikbaar, huidig) {
|
|
4
4
|
const delen = (v) => v.split("-")[0].split(".").map((n) => Number.parseInt(n, 10) || 0);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xiwebdesign/xicms-core",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.11.0",
|
|
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": {
|