@xiwebdesign/xicms-core 7.2.0 → 7.3.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.
Files changed (107) hide show
  1. package/dist/XiCMSProvider.d.ts +2 -8
  2. package/dist/XiCMSProvider.js +1 -8
  3. package/dist/XiCMSProvider.js.map +1 -1
  4. package/dist/admin/AdminApp.js +7 -12
  5. package/dist/admin/AdminApp.js.map +1 -1
  6. package/dist/admin/NewsEditor.d.ts +8 -1
  7. package/dist/admin/NewsEditor.js +19 -18
  8. package/dist/admin/NewsEditor.js.map +1 -1
  9. package/dist/admin/SuperadminPanel.js +6 -7
  10. package/dist/admin/SuperadminPanel.js.map +1 -1
  11. package/dist/admin/customBlocks/CustomBlocksPanel.js +3 -4
  12. package/dist/admin/customBlocks/CustomBlocksPanel.js.map +1 -1
  13. package/dist/admin/lib/pagesClient.js +3 -4
  14. package/dist/admin/lib/pagesClient.js.map +1 -1
  15. package/dist/admin/lib/symbolsClient.js +3 -4
  16. package/dist/admin/lib/symbolsClient.js.map +1 -1
  17. package/dist/admin/pages/PagesPanel.js +5 -2
  18. package/dist/admin/pages/PagesPanel.js.map +1 -1
  19. package/dist/admin/settings/MailSettings.js +2 -3
  20. package/dist/admin/settings/MailSettings.js.map +1 -1
  21. package/dist/admin/settings/MailTemplates.js +2 -3
  22. package/dist/admin/settings/MailTemplates.js.map +1 -1
  23. package/dist/admin/settings/SettingsPanel.js +250 -225
  24. package/dist/admin/settings/SettingsPanel.js.map +1 -1
  25. package/dist/admin/settings/SnapshotsCard.js +3 -4
  26. package/dist/admin/settings/SnapshotsCard.js.map +1 -1
  27. package/dist/admin/shop/lib.js +2 -3
  28. package/dist/admin/shop/lib.js.map +1 -1
  29. package/dist/admin/visual/VisualEditor.js +239 -103
  30. package/dist/admin/visual/VisualEditor.js.map +1 -1
  31. package/dist/admin.css +1 -1
  32. package/dist/blocks/builtins/Agenda.js +6 -6
  33. package/dist/blocks/builtins/Agenda.js.map +1 -1
  34. package/dist/blocks/builtins/Banner.js +1 -1
  35. package/dist/blocks/builtins/Banner.js.map +1 -1
  36. package/dist/blocks/builtins/Breadcrumbs.js +5 -2
  37. package/dist/blocks/builtins/Breadcrumbs.js.map +1 -1
  38. package/dist/blocks/builtins/Cards.js +2 -2
  39. package/dist/blocks/builtins/Cards.js.map +1 -1
  40. package/dist/blocks/builtins/Contact.js +3 -3
  41. package/dist/blocks/builtins/Contact.js.map +1 -1
  42. package/dist/blocks/builtins/Cta.js +3 -3
  43. package/dist/blocks/builtins/Cta.js.map +1 -1
  44. package/dist/blocks/builtins/Downloads.js +5 -5
  45. package/dist/blocks/builtins/Downloads.js.map +1 -1
  46. package/dist/blocks/builtins/Elements.js +8 -5
  47. package/dist/blocks/builtins/Elements.js.map +1 -1
  48. package/dist/blocks/builtins/Hero.js +1 -1
  49. package/dist/blocks/builtins/Hero.js.map +1 -1
  50. package/dist/blocks/builtins/Jobs.js +4 -1
  51. package/dist/blocks/builtins/Jobs.js.map +1 -1
  52. package/dist/blocks/builtins/MapBlock.js +11 -11
  53. package/dist/blocks/builtins/MapBlock.js.map +1 -1
  54. package/dist/blocks/builtins/Newsletter.js +3 -2
  55. package/dist/blocks/builtins/Newsletter.js.map +1 -1
  56. package/dist/blocks/builtins/OpeningHours.js +2 -2
  57. package/dist/blocks/builtins/OpeningHours.js.map +1 -1
  58. package/dist/blocks/builtins/PdfBlock.js +2 -2
  59. package/dist/blocks/builtins/PdfBlock.js.map +1 -1
  60. package/dist/blocks/builtins/Portfolio.js +4 -4
  61. package/dist/blocks/builtins/Portfolio.js.map +1 -1
  62. package/dist/blocks/builtins/Pricing.js +3 -3
  63. package/dist/blocks/builtins/Pricing.js.map +1 -1
  64. package/dist/blocks/builtins/Sections.js +7 -7
  65. package/dist/blocks/builtins/Sections.js.map +1 -1
  66. package/dist/blocks/builtins/Team.js +3 -3
  67. package/dist/blocks/builtins/Team.js.map +1 -1
  68. package/dist/blocks/builtins/Widgets.js +28 -28
  69. package/dist/blocks/builtins/Widgets.js.map +1 -1
  70. package/dist/blocks/builtins/lib/submitForm.js +3 -4
  71. package/dist/blocks/builtins/lib/submitForm.js.map +1 -1
  72. package/dist/blocks/ui/index.d.ts +8 -1
  73. package/dist/blocks/ui/index.js +3 -2
  74. package/dist/blocks/ui/index.js.map +1 -1
  75. package/dist/components/EditBar.d.ts +9 -1
  76. package/dist/components/EditBar.js +6 -5
  77. package/dist/components/EditBar.js.map +1 -1
  78. package/dist/components/PageView.js +3 -5
  79. package/dist/components/PageView.js.map +1 -1
  80. package/dist/firebase/admin.js +4 -0
  81. package/dist/firebase/admin.js.map +1 -1
  82. package/dist/firebase/client.js +23 -5
  83. package/dist/firebase/client.js.map +1 -1
  84. package/dist/firebase/emulators.d.ts +9 -0
  85. package/dist/firebase/emulators.js +24 -0
  86. package/dist/firebase/emulators.js.map +1 -0
  87. package/dist/firebase/functionsClient.d.ts +18 -0
  88. package/dist/firebase/functionsClient.js +17 -0
  89. package/dist/firebase/functionsClient.js.map +1 -0
  90. package/dist/lock/DemoLock.js +19 -2
  91. package/dist/lock/DemoLock.js.map +1 -1
  92. package/dist/modules/shop/cartStore.js +2 -1
  93. package/dist/modules/shop/cartStore.js.map +1 -1
  94. package/dist/modules/shop/lib.js +9 -10
  95. package/dist/modules/shop/lib.js.map +1 -1
  96. package/dist/server/index.d.ts +1 -1
  97. package/dist/server/index.js +2 -0
  98. package/dist/server/index.js.map +1 -1
  99. package/dist/server/pages.d.ts +11 -0
  100. package/dist/server/pages.js +47 -0
  101. package/dist/server/pages.js.map +1 -1
  102. package/dist/styles.css +130 -29
  103. package/dist/theme/color.d.ts +17 -0
  104. package/dist/theme/color.js +12 -0
  105. package/dist/theme/color.js.map +1 -0
  106. package/dist/version.js +1 -1
  107. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/server/pages.ts"],"sourcesContent":["import \"server-only\";\n\nimport { cache } from \"react\";\n\nimport { getAdminDb } from \"../firebase/admin\";\nimport { PageSchema, type Page } from \"../schema/pages\";\nimport { SymbolSchema, type StoredSymbol } from \"../schema/symbols\";\nimport { CustomBlockSchema, type CustomBlock } from \"../schema/customBlocks\";\nimport { PublicSiteSettingsSchema, type PublicSiteSettings } from \"../schema/settings\";\nimport { localizePage, localizeBlocks } from \"../i18n/overlay\";\nimport { getBundle, getBundlePage } from \"./bundle\";\n\n/**\n * BUNDLE-FIRST, Firestore-fallback (5.7).\n *\n * Elke publieke reader hieronder kijkt eerst of er een gecommit content-bundle is (server/bundle.ts,\n * `content/published.bundle.json`). Zo ja → serveer daaruit met exact dezelfde return-vormen als het\n * Firestore-pad (incl. schema-validatie, i18n-overlay en symbol-expansie). Zo nee (dev/lokaal of een\n * repo zonder gecommit bundle) → val ongewijzigd terug op de Admin-SDK / keyless-REST-implementatie.\n * De bundle-tak is puur additief: signatures en gedrag van de fallback blijven identiek.\n */\n\nexport type StoredPage = Page & { id: string };\n\n/**\n * Cache-tags voor gerichte invalidatie.\n *\n * Zonder tags kende de site maar één knop: `revalidatePath(\"/\", \"layout\")` bij élke publicatie,\n * wat de HELE site ongeldig verklaart. Eén tekstwijziging op een site van 20 pagina's kostte\n * daarmee tot 20 verse Firestore-reads in plaats van 1 — precies het tegenovergestelde van goedkoop.\n *\n * Met tags invalideert de backend alleen wat er werkelijk wijzigde: die ene pagina, of de\n * instellingen, of de symbolen. De rest blijft staan en kost niets.\n */\n/**\n * Tijdgebaseerd ISR-venster — nu het VANGNET, niet het normale pad.\n *\n * Verse content komt van de cache-tags: publiceren duwt de wijziging binnen seconden door. De\n * timer dekt alleen het geval dat die push níét aankomt (site even onbereikbaar, geheim verkeerd,\n * deploy ertussen). Op 300s betekende dat: élke pagina hertelt elke 5 minuten bij Firestore, ook\n * als er maanden niets wijzigt — bij ~50 sites duizenden reads per dag voor niets.\n *\n * Een uur houdt de kosten laag en laat een gemiste push zichzelf herstellen. Per site bij te\n * stellen met `XICMS_ISR_FALLBACK` (seconden).\n */\nconst ISR_FALLBACK = Number(process.env.XICMS_ISR_FALLBACK) || 3600;\n\n/**\n * Kortere terugval voor bronnen ZONDER push, mocht die er ooit weer zijn.\n *\n * Sinds 6.6 heeft élke publieke bron een push: pagina's via `publishPage`, symbolen en bouwstenen\n * via hun callables, en instellingen via `saveSiteSettings` — die laatste bestond niet, waardoor\n * het beheer rechtstreeks naar Firestore schreef en er niets viel door te duwen. Deze constante\n * blijft staan als vangnet voor toekomstige bronnen die datzelfde gat zouden hebben.\n */\nconst ISR_NO_PUSH = Number(process.env.XICMS_ISR_FALLBACK_NOPUSH) || 300;\n\nexport const cacheTags = {\n page: (siteId: string, path: string) => `xicms:${siteId}:page:${path}`,\n pages: (siteId: string) => `xicms:${siteId}:pages`,\n collection: (siteId: string, name: string) => `xicms:${siteId}:${name}`,\n};\n\n/**\n * Tags voor een pagina-query. Filtert de query op één pad, dan krijgt de read ALLEEN de PAD-tag;\n * een query zonder padfilter (de lijst) krijgt de lijst-tag.\n *\n * WAAROM NIET ALLEBEI (zoals hiervoor): de lijst-tag stond óók op een read van één enkele pagina,\n * en `publishPage` stuurt die lijst-tag altijd mee. Daarmee verklaarde élke publicatie de cache van\n * élke pagina ongeldig — precies wat de gerichte tags moesten voorkomen. Gemeten op de staging-\n * deploy: `/diensten` stond op `x-vercel-cache: HIT, age: 28` en sprong na het publiceren van een\n * tekst op een ándere pagina terug naar `REVALIDATED, age: 0`. Kosten: een verse Firestore-read per\n * pagina per publicatie.\n *\n * Veilig omdat geen enkele pagina-render van de lijst afhangt: `listPublishedPages` wordt alleen\n * gebruikt in `generateStaticParams` en de sitemap, niet in de nav. De keerzijde is dat een\n * pad-WIJZIGING nu expliciet moet worden opgeruimd — daarom stuurt `publishPage` ook de tag van het\n * oude pad mee wanneer dat verandert (functions/src/index.ts).\n */\nfunction pageQueryTags(siteId: string, filters: Filter[]): string[] {\n const padFilter = filters.find((f) => JSON.stringify(f).includes('\"path\"'));\n const pad = padFilter ? (JSON.stringify(padFilter).match(/\"stringValue\":\"([^\"]*)\"/)?.[1] ?? null) : null;\n return pad ? [cacheTags.page(siteId, pad)] : [cacheTags.pages(siteId)];\n}\n\nfunction toPath(slugs: string[] | undefined): string {\n const clean = (slugs ?? []).filter(Boolean);\n return \"/\" + clean.join(\"/\");\n}\n\n/** Pad uit Next catch-all params (`[[...slug]]`). \"/\" voor de root. */\nexport function pathFromSlugs(slugs: string[] | undefined): string {\n const p = toPath(slugs);\n return p === \"/\" ? \"/\" : p.replace(/\\/$/, \"\");\n}\n\nfunction parse(id: string, data: unknown): StoredPage | null {\n const res = PageSchema.safeParse(data);\n if (!res.success) {\n // Niet stil 404'en: een gepubliceerde pagina die het schema niet haalt is een diagnose-signaal\n // (schema-evolutie zonder default, of corrupte write) — zelfde patroon als symbols/customBlocks.\n console.warn(`[xicms] pagina \"${id}\" faalt schema-validatie en wordt overgeslagen:`, res.error.issues[0]);\n return null;\n }\n return { id, ...res.data };\n}\n\n/**\n * Admin-SDK-reads mogen een PUBLIEKE pagina nooit laten 500'en — zelfde belofte als het REST-pad\n * (dat al overal try/catch → lege fallback heeft). Een Firestore-hikje wordt gelogd en levert de\n * fallback op; de route rendert dan 404/fallback i.p.v. een servererror.\n */\nasync function safeAdminRead<T>(fallback: T, what: string, fn: () => Promise<T>): Promise<T> {\n try {\n return await fn();\n } catch (e) {\n console.warn(`[xicms] Admin-read mislukt (${what}) — fallback gebruikt:`, (e as Error)?.message ?? e);\n return fallback;\n }\n}\n\n/* ---------------- keyless public reads via Firestore REST (alleen apiKey) ----------------\n * Gepubliceerde pagina's zijn publiek leesbaar (rules). De Firestore REST-API honoreert die\n * rules met enkel de web-apiKey (geen Admin-credentials, geen client-SDK), dus dit werkt\n * server-side op keyless deployments (org-policy zonder service-account-keys).\n */\ntype FsValue = Record<string, unknown>;\nfunction decode(v: FsValue): unknown {\n if (v == null) return null;\n if (\"stringValue\" in v) return v.stringValue;\n if (\"integerValue\" in v) return Number(v.integerValue);\n if (\"doubleValue\" in v) return v.doubleValue;\n if (\"booleanValue\" in v) return v.booleanValue;\n if (\"nullValue\" in v) return null;\n if (\"timestampValue\" in v) return v.timestampValue;\n if (\"referenceValue\" in v) return v.referenceValue;\n if (\"geoPointValue\" in v) return v.geoPointValue;\n if (\"arrayValue\" in v) return (((v.arrayValue as FsValue)?.values as FsValue[]) ?? []).map(decode);\n if (\"mapValue\" in v) return decodeFields(((v.mapValue as FsValue)?.fields as Record<string, FsValue>) ?? {});\n return null;\n}\nfunction decodeFields(fields: Record<string, FsValue>): Record<string, unknown> {\n return Object.fromEntries(Object.entries(fields).map(([k, x]) => [k, decode(x)]));\n}\n\ninterface Filter { fieldFilter: { field: { fieldPath: string }; op: \"EQUAL\"; value: { stringValue: string } } }\nfunction eq(fieldPath: string, value: string): Filter {\n return { fieldFilter: { field: { fieldPath }, op: \"EQUAL\", value: { stringValue: value } } };\n}\n\nasync function restQuery(siteId: string, filters: Filter[], limit?: number): Promise<StoredPage[]> {\n const apiKey = process.env.NEXT_PUBLIC_FIREBASE_API_KEY;\n const projectId = process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID;\n if (!apiKey || !projectId) return [];\n const url = `https://firestore.googleapis.com/v1/projects/${projectId}/databases/(default)/documents/sites/${siteId}:runQuery?key=${apiKey}`;\n const where = filters.length === 1 ? filters[0] : { compositeFilter: { op: \"AND\", filters } };\n const structuredQuery: Record<string, unknown> = { from: [{ collectionId: \"pages\" }], where };\n if (limit) structuredQuery.limit = limit;\n const tags = pageQueryTags(siteId, filters);\n // Netwerk-/parsefouten mogen een publieke pagina nooit laten crashen (500) — dan liever een\n // lege lijst (Next toont dan een 404 of de fallback, en de volgende revalidate probeert opnieuw).\n try {\n const res = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ structuredQuery }),\n // ISR + gerichte tags: de tijdslimiet is het vangnet, de tag is het normale pad.\n next: { revalidate: ISR_FALLBACK, tags },\n });\n if (!res.ok) return [];\n const rows = (await res.json()) as { document?: { name: string; fields: Record<string, FsValue> } }[];\n return rows\n .filter((r) => r.document)\n .map((r) => parse(r.document!.name.split(\"/\").pop()!, decodeFields(r.document!.fields)))\n .filter((p): p is StoredPage => p !== null);\n } catch (err) {\n console.error(\"[xicms] Firestore REST-query mislukt:\", err);\n return [];\n }\n}\n\n/* ================= LIVE-READS-modus (6.5): vers uit Firestore, bundle als STATISCHE FALLBACK =======\n *\n * Standaard is XiCMS **bundle-first** (5.7): een gecommitte `content/published.bundle.json` is de\n * bron → nul Firestore at-runtime, maar een wijziging verschijnt pas na de gedebouncede rebuild.\n *\n * Zet je per site de env-var `XICMS_LIVE_READS=1`, dan draait de reader **Firestore-first**:\n * - Primair lezen we vers uit Firestore (Admin of keyless REST). In combinatie met on-demand\n * revalidation (`/api/revalidate`, aangeroepen door de backend na een publicatie) staat een\n * wijziging binnen seconden live — zónder rebuild, dus zonder Vercel-bouwminuten.\n * - Is Firestore **onbereikbaar** (netwerk/permissie/ontbrekende config), dan valt de reader terug\n * op de gecommitte bundle: een STATISCHE last-known-good back-up. De site blijft dus staan als\n * Firebase plat ligt.\n * - Een BEREIKBARE Firestore is gezaghebbend: \"pagina bestaat niet\" → echte 404 (géén bundle-\n * fallback, anders zou een verwijderde pagina uit de back-up herrijzen).\n *\n * De preview-modus (`?preview=1`, `skipBundle`) leest sowieso rechtstreeks en negeert de bundle.\n */\n/**\n * Live-reads staan STANDAARD aan zodra Firestore bereikbaar is.\n *\n * Firestore is leidend voor de inhoud, dus een tekstwijziging hoort binnen seconden live te staan\n * zónder rebuild. Dat als opt-in-vlag laten betekende dat elke site die hem vergat alsnog een\n * Vercel-build per tekstwijziging kreeg — de trage én dure route, precies wat we niet willen.\n *\n * Daarom afgeleid uit CAPACITEIT in plaats van uit een handmatige schakelaar:\n * - server-credentials (Admin-SDK) of een publieke REST-config aanwezig → live-reads;\n * - geen van beide → bundle-only, want er valt niets vers te lezen;\n * - `XICMS_LIVE_READS=0` → expliciet uit (bewuste keuze voor een volledig statische site).\n *\n * De terugval blijft ongewijzigd: is Firestore onbereikbaar, dan serveert `liveOrBundle` de\n * gecommitte bundle als last-known-good. Standaard aanzetten vergroot dus niet de kans op een\n * kapotte site — alleen de kans dat content vers is.\n */\nfunction liveReadsEnabled(): boolean {\n const flag = process.env.XICMS_LIVE_READS?.toLowerCase();\n if (flag === \"0\" || flag === \"false\") return false;\n if (flag === \"1\" || flag === \"true\") return true;\n const heeftAdmin = Boolean(process.env.FIREBASE_PROJECT_ID && process.env.FIREBASE_CLIENT_EMAIL);\n const heeftRest = Boolean(process.env.NEXT_PUBLIC_FIREBASE_API_KEY && process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID);\n return heeftAdmin || heeftRest;\n}\n\nconst LIVE_READS = liveReadsEnabled();\n\n/** Leest of live-reads actief zijn — voor diagnose en tests. */\nexport function liveReadsActive(): boolean {\n return LIVE_READS;\n}\n\n/**\n * Live-modus-wrapper: probeer `fresh()` (Firestore). Slaagt die → verse data. Gooit die (Firestore\n * onbereikbaar) → `fromBundle()` (last-known-good back-up). Buiten live-modus/preview niet gebruikt.\n */\nasync function liveOrBundle<T>(what: string, fresh: () => Promise<T>, fromBundle: () => T): Promise<T> {\n try {\n return await fresh();\n } catch (e) {\n console.warn(`[xicms] Firestore onbereikbaar (${what}) — statische bundle-fallback gebruikt:`, (e as Error)?.message ?? e);\n return fromBundle();\n }\n}\n\n/** Firestore-config voor het keyless REST-pad, of GOOIT als die ontbreekt (→ bundle-fallback i.p.v.\n * stil een lege lijst serveren). */\nfunction restConfigOrThrow(): { apiKey: string; projectId: string } {\n const apiKey = process.env.NEXT_PUBLIC_FIREBASE_API_KEY;\n const projectId = process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID;\n if (!apiKey || !projectId) throw new Error(\"Firebase REST-config ontbreekt (apiKey/projectId)\");\n return { apiKey, projectId };\n}\n\n/** Als `restQuery`, maar GOOIT bij fouten (i.p.v. []) zodat live-modus de bundle-fallback kan kiezen. */\nasync function restQueryThrowing(siteId: string, filters: Filter[], limit?: number): Promise<StoredPage[]> {\n const { apiKey, projectId } = restConfigOrThrow();\n const tags = pageQueryTags(siteId, filters);\n const url = `https://firestore.googleapis.com/v1/projects/${projectId}/databases/(default)/documents/sites/${siteId}:runQuery?key=${apiKey}`;\n const where = filters.length === 1 ? filters[0] : { compositeFilter: { op: \"AND\", filters } };\n const structuredQuery: Record<string, unknown> = { from: [{ collectionId: \"pages\" }], where };\n if (limit) structuredQuery.limit = limit;\n const res = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ structuredQuery }),\n next: { revalidate: ISR_FALLBACK, tags },\n });\n if (!res.ok) throw new Error(`Firestore REST-query ${res.status}`);\n const rows = (await res.json()) as { document?: { name: string; fields: Record<string, FsValue> } }[];\n return rows\n .filter((r) => r.document)\n .map((r) => parse(r.document!.name.split(\"/\").pop()!, decodeFields(r.document!.fields)))\n .filter((p): p is StoredPage => p !== null);\n}\n\n/** Verse (throwing) collectie-read onder sites/{siteId}/ — Admin als beschikbaar, anders keyless REST. */\nasync function readCollectionThrowing(siteId: string, collectionId: string): Promise<{ id: string; data: Record<string, unknown> }[]> {\n const adb = getAdminDb();\n if (adb) {\n const snap = await adb.collection(`sites/${siteId}/${collectionId}`).get();\n return snap.docs.map((d) => ({ id: d.id, data: d.data() as Record<string, unknown> }));\n }\n const { apiKey, projectId } = restConfigOrThrow();\n const url = `https://firestore.googleapis.com/v1/projects/${projectId}/databases/(default)/documents/sites/${siteId}:runQuery?key=${apiKey}`;\n const res = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ structuredQuery: { from: [{ collectionId }] } }),\n // Collectie-tag: het opslaan van een symbool raakt alleen de symbolen-cache, niet de pagina's.\n next: { revalidate: ISR_FALLBACK, tags: [cacheTags.collection(siteId, collectionId)] },\n });\n if (!res.ok) throw new Error(`Firestore REST-read ${collectionId} ${res.status}`);\n const rows = (await res.json()) as { document?: { name: string; fields: Record<string, FsValue> } }[];\n return rows\n .filter((r) => r.document)\n .map((r) => ({ id: r.document!.name.split(\"/\").pop()!, data: decodeFields(r.document!.fields ?? {}) }));\n}\n\n/** Pagina's uit de gecommitte bundle (of [] als er voor deze site geen bundle is). */\nfunction bundlePages(siteId: string): StoredPage[] {\n const bundle = getBundle();\n if (!bundle || bundle.siteId !== siteId) return [];\n return bundle.pages.map((p) => parse(p.id, p)).filter((p): p is StoredPage => p !== null);\n}\n\n/** Alle gepubliceerde pagina's (sitemap / generateStaticParams). Live-first (bundle-fallback) of bundle-first. */\nexport async function listPublishedPages(siteId: string): Promise<StoredPage[]> {\n if (LIVE_READS) {\n return liveOrBundle(\n `listPublishedPages ${siteId}`,\n async () => {\n const adb = getAdminDb();\n if (adb) {\n const snap = await adb.collection(`sites/${siteId}/pages`).where(\"status\", \"==\", \"published\").get();\n return snap.docs.map((d) => parse(d.id, d.data())).filter((p): p is StoredPage => p !== null);\n }\n return restQueryThrowing(siteId, [eq(\"status\", \"published\")]);\n },\n () => bundlePages(siteId),\n );\n }\n const bundle = getBundle();\n if (bundle && bundle.siteId === siteId) {\n return bundle.pages.map((p) => parse(p.id, p)).filter((p): p is StoredPage => p !== null);\n }\n const adb = getAdminDb();\n if (adb) {\n return safeAdminRead([], `listPublishedPages ${siteId}`, async () => {\n const snap = await adb.collection(`sites/${siteId}/pages`).where(\"status\", \"==\", \"published\").get();\n return snap.docs.map((d) => parse(d.id, d.data())).filter((p): p is StoredPage => p !== null);\n });\n }\n return restQuery(siteId, [eq(\"status\", \"published\")]);\n}\n\n/**\n * Per-request-dedup via React cache(): metadata (generateMetadata) én de render lezen dezelfde\n * pagina — zonder cache() waren dat twee identieke Firestore-reads per request. NB: de\n * `next:{revalidate}`-hint op het REST-pad dekt dit niet (POST-fetches cachen niet); route-level\n * ISR (revalidate=300 in de consumer) blijft de echte kosten-demper tussen requests.\n */\nexport const getPublishedPageByPath = cache(getPublishedPageByPathUncached);\nexport const getSiteSymbols = cache(getSiteSymbolsUncached);\nexport const getSiteCustomBlocks = cache(getSiteCustomBlocksUncached);\nexport const getPublicSiteSettings = cache(getPublicSiteSettingsUncached);\n\n/**\n * Eén gepubliceerde pagina op pad. Bundle-first, anders keyless via REST/Admin.\n * `opts.skipBundle` (voorbeeld-modus, `?preview=1` in de visuele editor): negeert de gecommitte\n * bundel en leest rechtstreeks — de bundel komt pas met vertraging mee via de gedebouncede deploy-\n * worker, dus een beheerder die net gepubliceerd heeft ziet anders tot ~15 min lang de oude tekst.\n */\nasync function getPublishedPageByPathUncached(\n siteId: string,\n path: string,\n opts?: { skipBundle?: boolean },\n): Promise<StoredPage | null> {\n // Live-modus (niet in preview): vers uit Firestore, met de bundle als fallback bij een outage.\n if (LIVE_READS && !opts?.skipBundle) {\n return liveOrBundle(\n `getPublishedPageByPath ${siteId}${path}`,\n async () => {\n const adb = getAdminDb();\n if (adb) {\n const snap = await adb\n .collection(`sites/${siteId}/pages`)\n .where(\"status\", \"==\", \"published\")\n .where(\"path\", \"==\", path)\n .limit(1)\n .get();\n const d = snap.docs[0];\n return d ? parse(d.id, d.data()) : null;\n }\n const rows = await restQueryThrowing(siteId, [eq(\"status\", \"published\"), eq(\"path\", path)], 1);\n return rows[0] ?? null;\n },\n () => {\n const b = getBundle();\n if (!b || b.siteId !== siteId) return null;\n const hit = getBundlePage(b, path);\n return hit ? parse(hit.id, hit) : null;\n },\n );\n }\n const bundle = opts?.skipBundle ? null : getBundle();\n if (bundle && bundle.siteId === siteId) {\n const hit = getBundlePage(bundle, path);\n return hit ? parse(hit.id, hit) : null;\n }\n const adb = getAdminDb();\n if (adb) {\n return safeAdminRead(null, `getPublishedPageByPath ${siteId}${path}`, async () => {\n const snap = await adb\n .collection(`sites/${siteId}/pages`)\n .where(\"status\", \"==\", \"published\")\n .where(\"path\", \"==\", path)\n .limit(1)\n .get();\n const d = snap.docs[0];\n return d ? parse(d.id, d.data()) : null;\n });\n }\n const rows = await restQuery(siteId, [eq(\"status\", \"published\"), eq(\"path\", path)], 1);\n return rows[0] ?? null;\n}\n\n/** Draft-versie (voor preview achter auth). */\nexport async function getDraftPageById(siteId: string, pageId: string): Promise<StoredPage | null> {\n const db = getAdminDb();\n if (!db) return null;\n return safeAdminRead(null, `getDraftPageById ${siteId}/${pageId}`, async () => {\n const d = await db.doc(`sites/${siteId}/drafts/${pageId}`).get();\n return d.exists ? parse(d.id, d.data()) : null;\n });\n}\n\n/** Draft op pad (preview). */\nexport async function getDraftPageByPath(siteId: string, path: string): Promise<StoredPage | null> {\n const db = getAdminDb();\n if (!db) return null;\n return safeAdminRead(null, `getDraftPageByPath ${siteId}${path}`, async () => {\n const snap = await db.collection(`sites/${siteId}/drafts`).where(\"path\", \"==\", path).limit(1).get();\n const d = snap.docs[0];\n return d ? parse(d.id, d.data()) : null;\n });\n}\n\n/* ---------------- symbols & custom blocks (5.0) ---------------- */\n\n/** Generieke publieke collectie-read onder sites/{siteId}/ — Admin als beschikbaar, anders REST. */\nasync function readCollection(siteId: string, collectionId: string): Promise<{ id: string; data: Record<string, unknown> }[]> {\n const adb = getAdminDb();\n if (adb) {\n return safeAdminRead([], `readCollection ${siteId}/${collectionId}`, async () => {\n const snap = await adb.collection(`sites/${siteId}/${collectionId}`).get();\n return snap.docs.map((d) => ({ id: d.id, data: d.data() as Record<string, unknown> }));\n });\n }\n const apiKey = process.env.NEXT_PUBLIC_FIREBASE_API_KEY;\n const projectId = process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID;\n if (!apiKey || !projectId) return [];\n const url = `https://firestore.googleapis.com/v1/projects/${projectId}/databases/(default)/documents/sites/${siteId}:runQuery?key=${apiKey}`;\n try {\n const res = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ structuredQuery: { from: [{ collectionId }] } }),\n next: { revalidate: ISR_FALLBACK, tags: [cacheTags.collection(siteId, collectionId)] },\n });\n if (!res.ok) return [];\n const rows = (await res.json()) as { document?: { name: string; fields: Record<string, FsValue> } }[];\n return rows\n .filter((r) => r.document)\n .map((r) => ({ id: r.document!.name.split(\"/\").pop()!, data: decodeFields(r.document!.fields ?? {}) }));\n } catch (err) {\n console.error(`[xicms] Firestore REST-read van ${collectionId} mislukt:`, err);\n return [];\n }\n}\n\n/** Symbool-rijen uit de gecommitte bundle (of [] als er voor deze site geen bundle is). */\nfunction bundleSymbolRows(siteId: string): { id: string; data: Record<string, unknown> }[] {\n const bundle = getBundle();\n return bundle && bundle.siteId === siteId\n ? bundle.symbols.map((s) => ({ id: s.id, data: s as Record<string, unknown> }))\n : [];\n}\n\n/** Alle symbolen van een site (gevalideerd; ongeldige overgeslagen met waarschuwing). Live-first of bundle-first. */\nasync function getSiteSymbolsUncached(siteId: string): Promise<StoredSymbol[]> {\n let rows: { id: string; data: Record<string, unknown> }[];\n if (LIVE_READS) {\n rows = await liveOrBundle(`getSiteSymbols ${siteId}`, () => readCollectionThrowing(siteId, \"symbols\"), () => bundleSymbolRows(siteId));\n } else {\n const bundle = getBundle();\n rows = bundle && bundle.siteId === siteId ? bundleSymbolRows(siteId) : await readCollection(siteId, \"symbols\");\n }\n const out: StoredSymbol[] = [];\n for (const r of rows) {\n const res = SymbolSchema.safeParse(r.data);\n if (res.success) out.push({ id: r.id, ...res.data });\n else console.warn(`[xicms] symbool \"${r.id}\" ongeldig — overgeslagen.`);\n }\n return out;\n}\n\n/**\n * Vervangt symbolRef-blokken door de blokken van hun symbool (één niveau — symbolen kunnen zelf\n * geen symbolRef bevatten). `_key`s van de ingevoegde blokken worden geprefixt met de ref-key\n * zodat ze uniek blijven wanneer hetzelfde symbool vaker op een pagina staat. Verweesde refs\n * verdwijnen stil (de pagina mag nooit breken op een verwijderd symbool).\n */\nexport function expandSymbols<T extends { blocks?: unknown[] }>(page: T, symbols: StoredSymbol[]): T {\n const byId = new Map(symbols.map((s) => [s.id, s]));\n const blocks = (page.blocks ?? []).flatMap((b) => {\n const blk = b as { _type?: string; _key?: string; symbolId?: string };\n if (blk?._type !== \"symbolRef\") return [b];\n const sym = blk.symbolId ? byId.get(blk.symbolId) : undefined;\n if (!sym) return [];\n return sym.blocks.map((sb, i) => ({ ...(sb as Record<string, unknown>), _key: `${blk._key ?? \"sym\"}-${(sb as { _key?: string })._key ?? i}` }));\n });\n return { ...page, blocks };\n}\n\n/**\n * Gepubliceerde pagina op pad mét symbolRef-expansie (SEO: symboolinhoud zit in de SSR-HTML).\n * Geef `locale` mee (een NIET-default taal) om de vertaal-overlay toe te passen — op de pagina\n * én op de ingevoegde symbolen (elk symbool draagt zijn eigen `translations`).\n *\n * Bundle-first werkt hier automatisch: deze functie componeert `getPublishedPageByPath` en\n * `getSiteSymbols`, die beide al bundle-first zijn — de i18n-overlay en expansie draaien identiek\n * op bundle- én Firestore-data.\n */\nexport async function getPublishedPageByPathExpanded(\n siteId: string,\n path: string,\n locale?: string,\n opts?: { skipBundle?: boolean },\n): Promise<StoredPage | null> {\n let page = await getPublishedPageByPath(siteId, path, opts);\n if (!page) return null;\n // De caller geeft alléén een niet-default taal door; \"\" als default matcht dus nooit.\n if (locale) page = localizePage(page, locale, \"\");\n if (!(page.blocks ?? []).some((b) => (b as { _type?: string })._type === \"symbolRef\")) return page;\n let symbols = await getSiteSymbols(siteId);\n if (locale) {\n symbols = symbols.map((s) => ({\n ...s,\n blocks: localizeBlocks(s.blocks as Array<{ _key?: string }>, s.translations?.[locale]) as typeof s.blocks,\n }));\n }\n return expandSymbols(page, symbols);\n}\n\n/**\n * Publieke site-instellingen (`sites/{siteId}/settings/public`) server-side — voor de\n * `settingsData`-prop van XiCMSProvider. Zo zit het klant-thema/nav/footer al in de server-HTML\n * (geen theme-flits bij eerste paint) én vervalt de per-bezoeker `getDoc` in de browser: de\n * layout-route is ISR-gecached, dus dit kost ~1 read per revalidate i.p.v. per bezoeker.\n * Fouten leveren `{}` op — de site valt dan terug op de statische SiteConfig.\n */\nasync function getPublicSiteSettingsUncached(siteId: string): Promise<PublicSiteSettings> {\n const bundleSettings = (): PublicSiteSettings => {\n const b = getBundle();\n if (!b || b.siteId !== siteId) return {};\n const parsed = PublicSiteSettingsSchema.safeParse(b.settings ?? {});\n return parsed.success ? parsed.data : {};\n };\n // Live-modus: vers uit Firestore (throwing), met de bundle-settings als fallback bij een outage.\n if (LIVE_READS) {\n return liveOrBundle(\n `getPublicSiteSettings ${siteId}`,\n async () => {\n const adb = getAdminDb();\n let data: unknown = {};\n if (adb) {\n const d = await adb.doc(`sites/${siteId}/settings/public`).get();\n data = d.exists ? d.data() : {};\n } else {\n const { apiKey, projectId } = restConfigOrThrow();\n const url = `https://firestore.googleapis.com/v1/projects/${projectId}/databases/(default)/documents/sites/${siteId}/settings/public?key=${apiKey}`;\n const res = await fetch(url, { next: { revalidate: ISR_FALLBACK, tags: [cacheTags.collection(siteId, \"settings\")] } });\n // 404 = document bestaat (nog) niet → lege settings; andere niet-2xx = outage → gooi (fallback).\n if (res.status === 404) data = {};\n else if (!res.ok) throw new Error(`Firestore REST-read settings/public ${res.status}`);\n else {\n const doc = (await res.json()) as { fields?: Record<string, FsValue> };\n data = decodeFields(doc.fields ?? {});\n }\n }\n const parsed = PublicSiteSettingsSchema.safeParse(data ?? {});\n return parsed.success ? parsed.data : {};\n },\n bundleSettings,\n );\n }\n const bundle = getBundle();\n if (bundle && bundle.siteId === siteId) {\n return bundleSettings();\n }\n const adb = getAdminDb();\n let data: unknown = {};\n if (adb) {\n data = await safeAdminRead<unknown>({}, `getPublicSiteSettings ${siteId}`, async () => {\n const d = await adb.doc(`sites/${siteId}/settings/public`).get();\n return d.exists ? d.data() : {};\n });\n } else {\n const apiKey = process.env.NEXT_PUBLIC_FIREBASE_API_KEY;\n const projectId = process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID;\n if (apiKey && projectId) {\n const url = `https://firestore.googleapis.com/v1/projects/${projectId}/databases/(default)/documents/sites/${siteId}/settings/public?key=${apiKey}`;\n try {\n // GET-fetches worden — anders dan de runQuery-POSTs — wél door de Next-datacache gedekt.\n const res = await fetch(url, { next: { revalidate: ISR_FALLBACK, tags: [cacheTags.collection(siteId, \"settings\")] } });\n if (res.ok) {\n const doc = (await res.json()) as { fields?: Record<string, FsValue> };\n data = decodeFields(doc.fields ?? {});\n }\n } catch (err) {\n console.error(\"[xicms] Firestore REST-read van settings/public mislukt:\", err);\n }\n }\n }\n const parsed = PublicSiteSettingsSchema.safeParse(data ?? {});\n return parsed.success ? parsed.data : {};\n}\n\n/** Custom-block-rijen uit de gecommitte bundle (of [] als er voor deze site geen bundle is). */\nfunction bundleCustomBlockRows(siteId: string): { id: string; data: Record<string, unknown> }[] {\n const bundle = getBundle();\n return bundle && bundle.siteId === siteId\n ? bundle.customBlocks.map((c) => ({ id: c.id, data: c as Record<string, unknown> }))\n : [];\n}\n\n/** Alle custom blocks van een site (gevalideerd) — voor SSR-registratie via XiCMSProvider. Live-first of bundle-first. */\nasync function getSiteCustomBlocksUncached(siteId: string): Promise<CustomBlock[]> {\n let rows: { id: string; data: Record<string, unknown> }[];\n if (LIVE_READS) {\n rows = await liveOrBundle(`getSiteCustomBlocks ${siteId}`, () => readCollectionThrowing(siteId, \"customBlocks\"), () => bundleCustomBlockRows(siteId));\n } else {\n const bundle = getBundle();\n rows = bundle && bundle.siteId === siteId ? bundleCustomBlockRows(siteId) : await readCollection(siteId, \"customBlocks\");\n }\n const out: CustomBlock[] = [];\n for (const r of rows) {\n const res = CustomBlockSchema.safeParse({ ...r.data, type: r.data.type ?? r.id });\n if (res.success) out.push(res.data);\n else console.warn(`[xicms] custom block \"${r.id}\" ongeldig — overgeslagen.`);\n }\n return out;\n}\n"],"mappings":"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","names":["adb","rows","parsed","data"]}
1
+ {"version":3,"sources":["../../src/server/pages.ts"],"sourcesContent":["import \"server-only\";\n\nimport { cache } from \"react\";\n\nimport { getAdminDb } from \"../firebase/admin\";\nimport { PageSchema, type Page } from \"../schema/pages\";\nimport { SymbolSchema, type StoredSymbol } from \"../schema/symbols\";\nimport { CustomBlockSchema, type CustomBlock } from \"../schema/customBlocks\";\nimport { PublicSiteSettingsSchema, type PublicSiteSettings } from \"../schema/settings\";\nimport { localizePage, localizeBlocks } from \"../i18n/overlay\";\nimport { getBundle, getBundlePage } from \"./bundle\";\nimport { sanitizeRichText } from \"./sanitize\";\nimport type { NewsArticle } from \"../content/types\";\n\n/**\n * BUNDLE-FIRST, Firestore-fallback (5.7).\n *\n * Elke publieke reader hieronder kijkt eerst of er een gecommit content-bundle is (server/bundle.ts,\n * `content/published.bundle.json`). Zo ja → serveer daaruit met exact dezelfde return-vormen als het\n * Firestore-pad (incl. schema-validatie, i18n-overlay en symbol-expansie). Zo nee (dev/lokaal of een\n * repo zonder gecommit bundle) → val ongewijzigd terug op de Admin-SDK / keyless-REST-implementatie.\n * De bundle-tak is puur additief: signatures en gedrag van de fallback blijven identiek.\n */\n\nexport type StoredPage = Page & { id: string };\n\n/**\n * Cache-tags voor gerichte invalidatie.\n *\n * Zonder tags kende de site maar één knop: `revalidatePath(\"/\", \"layout\")` bij élke publicatie,\n * wat de HELE site ongeldig verklaart. Eén tekstwijziging op een site van 20 pagina's kostte\n * daarmee tot 20 verse Firestore-reads in plaats van 1 — precies het tegenovergestelde van goedkoop.\n *\n * Met tags invalideert de backend alleen wat er werkelijk wijzigde: die ene pagina, of de\n * instellingen, of de symbolen. De rest blijft staan en kost niets.\n */\n/**\n * Tijdgebaseerd ISR-venster — nu het VANGNET, niet het normale pad.\n *\n * Verse content komt van de cache-tags: publiceren duwt de wijziging binnen seconden door. De\n * timer dekt alleen het geval dat die push níét aankomt (site even onbereikbaar, geheim verkeerd,\n * deploy ertussen). Op 300s betekende dat: élke pagina hertelt elke 5 minuten bij Firestore, ook\n * als er maanden niets wijzigt — bij ~50 sites duizenden reads per dag voor niets.\n *\n * Een uur houdt de kosten laag en laat een gemiste push zichzelf herstellen. Per site bij te\n * stellen met `XICMS_ISR_FALLBACK` (seconden).\n */\nconst ISR_FALLBACK = Number(process.env.XICMS_ISR_FALLBACK) || 3600;\n\n/**\n * Kortere terugval voor bronnen ZONDER push, mocht die er ooit weer zijn.\n *\n * Sinds 6.6 heeft élke publieke bron een push: pagina's via `publishPage`, symbolen en bouwstenen\n * via hun callables, en instellingen via `saveSiteSettings` — die laatste bestond niet, waardoor\n * het beheer rechtstreeks naar Firestore schreef en er niets viel door te duwen. Deze constante\n * blijft staan als vangnet voor toekomstige bronnen die datzelfde gat zouden hebben.\n */\nconst ISR_NO_PUSH = Number(process.env.XICMS_ISR_FALLBACK_NOPUSH) || 300;\n\nexport const cacheTags = {\n page: (siteId: string, path: string) => `xicms:${siteId}:page:${path}`,\n pages: (siteId: string) => `xicms:${siteId}:pages`,\n collection: (siteId: string, name: string) => `xicms:${siteId}:${name}`,\n};\n\n/**\n * Tags voor een pagina-query. Filtert de query op één pad, dan krijgt de read ALLEEN de PAD-tag;\n * een query zonder padfilter (de lijst) krijgt de lijst-tag.\n *\n * WAAROM NIET ALLEBEI (zoals hiervoor): de lijst-tag stond óók op een read van één enkele pagina,\n * en `publishPage` stuurt die lijst-tag altijd mee. Daarmee verklaarde élke publicatie de cache van\n * élke pagina ongeldig — precies wat de gerichte tags moesten voorkomen. Gemeten op de staging-\n * deploy: `/diensten` stond op `x-vercel-cache: HIT, age: 28` en sprong na het publiceren van een\n * tekst op een ándere pagina terug naar `REVALIDATED, age: 0`. Kosten: een verse Firestore-read per\n * pagina per publicatie.\n *\n * Veilig omdat geen enkele pagina-render van de lijst afhangt: `listPublishedPages` wordt alleen\n * gebruikt in `generateStaticParams` en de sitemap, niet in de nav. De keerzijde is dat een\n * pad-WIJZIGING nu expliciet moet worden opgeruimd — daarom stuurt `publishPage` ook de tag van het\n * oude pad mee wanneer dat verandert (functions/src/index.ts).\n */\nfunction pageQueryTags(siteId: string, filters: Filter[]): string[] {\n const padFilter = filters.find((f) => JSON.stringify(f).includes('\"path\"'));\n const pad = padFilter ? (JSON.stringify(padFilter).match(/\"stringValue\":\"([^\"]*)\"/)?.[1] ?? null) : null;\n return pad ? [cacheTags.page(siteId, pad)] : [cacheTags.pages(siteId)];\n}\n\nfunction toPath(slugs: string[] | undefined): string {\n const clean = (slugs ?? []).filter(Boolean);\n return \"/\" + clean.join(\"/\");\n}\n\n/** Pad uit Next catch-all params (`[[...slug]]`). \"/\" voor de root. */\nexport function pathFromSlugs(slugs: string[] | undefined): string {\n const p = toPath(slugs);\n return p === \"/\" ? \"/\" : p.replace(/\\/$/, \"\");\n}\n\nfunction parse(id: string, data: unknown): StoredPage | null {\n const res = PageSchema.safeParse(data);\n if (!res.success) {\n // Niet stil 404'en: een gepubliceerde pagina die het schema niet haalt is een diagnose-signaal\n // (schema-evolutie zonder default, of corrupte write) — zelfde patroon als symbols/customBlocks.\n console.warn(`[xicms] pagina \"${id}\" faalt schema-validatie en wordt overgeslagen:`, res.error.issues[0]);\n return null;\n }\n return { id, ...res.data };\n}\n\n/**\n * Admin-SDK-reads mogen een PUBLIEKE pagina nooit laten 500'en — zelfde belofte als het REST-pad\n * (dat al overal try/catch → lege fallback heeft). Een Firestore-hikje wordt gelogd en levert de\n * fallback op; de route rendert dan 404/fallback i.p.v. een servererror.\n */\nasync function safeAdminRead<T>(fallback: T, what: string, fn: () => Promise<T>): Promise<T> {\n try {\n return await fn();\n } catch (e) {\n console.warn(`[xicms] Admin-read mislukt (${what}) — fallback gebruikt:`, (e as Error)?.message ?? e);\n return fallback;\n }\n}\n\n/* ---------------- keyless public reads via Firestore REST (alleen apiKey) ----------------\n * Gepubliceerde pagina's zijn publiek leesbaar (rules). De Firestore REST-API honoreert die\n * rules met enkel de web-apiKey (geen Admin-credentials, geen client-SDK), dus dit werkt\n * server-side op keyless deployments (org-policy zonder service-account-keys).\n */\ntype FsValue = Record<string, unknown>;\nfunction decode(v: FsValue): unknown {\n if (v == null) return null;\n if (\"stringValue\" in v) return v.stringValue;\n if (\"integerValue\" in v) return Number(v.integerValue);\n if (\"doubleValue\" in v) return v.doubleValue;\n if (\"booleanValue\" in v) return v.booleanValue;\n if (\"nullValue\" in v) return null;\n if (\"timestampValue\" in v) return v.timestampValue;\n if (\"referenceValue\" in v) return v.referenceValue;\n if (\"geoPointValue\" in v) return v.geoPointValue;\n if (\"arrayValue\" in v) return (((v.arrayValue as FsValue)?.values as FsValue[]) ?? []).map(decode);\n if (\"mapValue\" in v) return decodeFields(((v.mapValue as FsValue)?.fields as Record<string, FsValue>) ?? {});\n return null;\n}\nfunction decodeFields(fields: Record<string, FsValue>): Record<string, unknown> {\n return Object.fromEntries(Object.entries(fields).map(([k, x]) => [k, decode(x)]));\n}\n\ninterface Filter { fieldFilter: { field: { fieldPath: string }; op: \"EQUAL\"; value: { stringValue: string } } }\nfunction eq(fieldPath: string, value: string): Filter {\n return { fieldFilter: { field: { fieldPath }, op: \"EQUAL\", value: { stringValue: value } } };\n}\n\nasync function restQuery(siteId: string, filters: Filter[], limit?: number): Promise<StoredPage[]> {\n const apiKey = process.env.NEXT_PUBLIC_FIREBASE_API_KEY;\n const projectId = process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID;\n if (!apiKey || !projectId) return [];\n const url = `https://firestore.googleapis.com/v1/projects/${projectId}/databases/(default)/documents/sites/${siteId}:runQuery?key=${apiKey}`;\n const where = filters.length === 1 ? filters[0] : { compositeFilter: { op: \"AND\", filters } };\n const structuredQuery: Record<string, unknown> = { from: [{ collectionId: \"pages\" }], where };\n if (limit) structuredQuery.limit = limit;\n const tags = pageQueryTags(siteId, filters);\n // Netwerk-/parsefouten mogen een publieke pagina nooit laten crashen (500) — dan liever een\n // lege lijst (Next toont dan een 404 of de fallback, en de volgende revalidate probeert opnieuw).\n try {\n const res = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ structuredQuery }),\n // ISR + gerichte tags: de tijdslimiet is het vangnet, de tag is het normale pad.\n next: { revalidate: ISR_FALLBACK, tags },\n });\n if (!res.ok) return [];\n const rows = (await res.json()) as { document?: { name: string; fields: Record<string, FsValue> } }[];\n return rows\n .filter((r) => r.document)\n .map((r) => parse(r.document!.name.split(\"/\").pop()!, decodeFields(r.document!.fields)))\n .filter((p): p is StoredPage => p !== null);\n } catch (err) {\n console.error(\"[xicms] Firestore REST-query mislukt:\", err);\n return [];\n }\n}\n\n/* ================= LIVE-READS-modus (6.5): vers uit Firestore, bundle als STATISCHE FALLBACK =======\n *\n * Standaard is XiCMS **bundle-first** (5.7): een gecommitte `content/published.bundle.json` is de\n * bron → nul Firestore at-runtime, maar een wijziging verschijnt pas na de gedebouncede rebuild.\n *\n * Zet je per site de env-var `XICMS_LIVE_READS=1`, dan draait de reader **Firestore-first**:\n * - Primair lezen we vers uit Firestore (Admin of keyless REST). In combinatie met on-demand\n * revalidation (`/api/revalidate`, aangeroepen door de backend na een publicatie) staat een\n * wijziging binnen seconden live — zónder rebuild, dus zonder Vercel-bouwminuten.\n * - Is Firestore **onbereikbaar** (netwerk/permissie/ontbrekende config), dan valt de reader terug\n * op de gecommitte bundle: een STATISCHE last-known-good back-up. De site blijft dus staan als\n * Firebase plat ligt.\n * - Een BEREIKBARE Firestore is gezaghebbend: \"pagina bestaat niet\" → echte 404 (géén bundle-\n * fallback, anders zou een verwijderde pagina uit de back-up herrijzen).\n *\n * De preview-modus (`?preview=1`, `skipBundle`) leest sowieso rechtstreeks en negeert de bundle.\n */\n/**\n * Live-reads staan STANDAARD aan zodra Firestore bereikbaar is.\n *\n * Firestore is leidend voor de inhoud, dus een tekstwijziging hoort binnen seconden live te staan\n * zónder rebuild. Dat als opt-in-vlag laten betekende dat elke site die hem vergat alsnog een\n * Vercel-build per tekstwijziging kreeg — de trage én dure route, precies wat we niet willen.\n *\n * Daarom afgeleid uit CAPACITEIT in plaats van uit een handmatige schakelaar:\n * - server-credentials (Admin-SDK) of een publieke REST-config aanwezig → live-reads;\n * - geen van beide → bundle-only, want er valt niets vers te lezen;\n * - `XICMS_LIVE_READS=0` → expliciet uit (bewuste keuze voor een volledig statische site).\n *\n * De terugval blijft ongewijzigd: is Firestore onbereikbaar, dan serveert `liveOrBundle` de\n * gecommitte bundle als last-known-good. Standaard aanzetten vergroot dus niet de kans op een\n * kapotte site — alleen de kans dat content vers is.\n */\nfunction liveReadsEnabled(): boolean {\n const flag = process.env.XICMS_LIVE_READS?.toLowerCase();\n if (flag === \"0\" || flag === \"false\") return false;\n if (flag === \"1\" || flag === \"true\") return true;\n const heeftAdmin = Boolean(process.env.FIREBASE_PROJECT_ID && process.env.FIREBASE_CLIENT_EMAIL);\n const heeftRest = Boolean(process.env.NEXT_PUBLIC_FIREBASE_API_KEY && process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID);\n return heeftAdmin || heeftRest;\n}\n\nconst LIVE_READS = liveReadsEnabled();\n\n/** Leest of live-reads actief zijn — voor diagnose en tests. */\nexport function liveReadsActive(): boolean {\n return LIVE_READS;\n}\n\n/**\n * Live-modus-wrapper: probeer `fresh()` (Firestore). Slaagt die → verse data. Gooit die (Firestore\n * onbereikbaar) → `fromBundle()` (last-known-good back-up). Buiten live-modus/preview niet gebruikt.\n */\nasync function liveOrBundle<T>(what: string, fresh: () => Promise<T>, fromBundle: () => T): Promise<T> {\n try {\n return await fresh();\n } catch (e) {\n console.warn(`[xicms] Firestore onbereikbaar (${what}) — statische bundle-fallback gebruikt:`, (e as Error)?.message ?? e);\n return fromBundle();\n }\n}\n\n/** Firestore-config voor het keyless REST-pad, of GOOIT als die ontbreekt (→ bundle-fallback i.p.v.\n * stil een lege lijst serveren). */\nfunction restConfigOrThrow(): { apiKey: string; projectId: string } {\n const apiKey = process.env.NEXT_PUBLIC_FIREBASE_API_KEY;\n const projectId = process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID;\n if (!apiKey || !projectId) throw new Error(\"Firebase REST-config ontbreekt (apiKey/projectId)\");\n return { apiKey, projectId };\n}\n\n/** Als `restQuery`, maar GOOIT bij fouten (i.p.v. []) zodat live-modus de bundle-fallback kan kiezen. */\nasync function restQueryThrowing(siteId: string, filters: Filter[], limit?: number): Promise<StoredPage[]> {\n const { apiKey, projectId } = restConfigOrThrow();\n const tags = pageQueryTags(siteId, filters);\n const url = `https://firestore.googleapis.com/v1/projects/${projectId}/databases/(default)/documents/sites/${siteId}:runQuery?key=${apiKey}`;\n const where = filters.length === 1 ? filters[0] : { compositeFilter: { op: \"AND\", filters } };\n const structuredQuery: Record<string, unknown> = { from: [{ collectionId: \"pages\" }], where };\n if (limit) structuredQuery.limit = limit;\n const res = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ structuredQuery }),\n next: { revalidate: ISR_FALLBACK, tags },\n });\n if (!res.ok) throw new Error(`Firestore REST-query ${res.status}`);\n const rows = (await res.json()) as { document?: { name: string; fields: Record<string, FsValue> } }[];\n return rows\n .filter((r) => r.document)\n .map((r) => parse(r.document!.name.split(\"/\").pop()!, decodeFields(r.document!.fields)))\n .filter((p): p is StoredPage => p !== null);\n}\n\n/** Verse (throwing) collectie-read onder sites/{siteId}/ — Admin als beschikbaar, anders keyless REST. */\nasync function readCollectionThrowing(siteId: string, collectionId: string): Promise<{ id: string; data: Record<string, unknown> }[]> {\n const adb = getAdminDb();\n if (adb) {\n const snap = await adb.collection(`sites/${siteId}/${collectionId}`).get();\n return snap.docs.map((d) => ({ id: d.id, data: d.data() as Record<string, unknown> }));\n }\n const { apiKey, projectId } = restConfigOrThrow();\n const url = `https://firestore.googleapis.com/v1/projects/${projectId}/databases/(default)/documents/sites/${siteId}:runQuery?key=${apiKey}`;\n const res = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ structuredQuery: { from: [{ collectionId }] } }),\n // Collectie-tag: het opslaan van een symbool raakt alleen de symbolen-cache, niet de pagina's.\n next: { revalidate: ISR_FALLBACK, tags: [cacheTags.collection(siteId, collectionId)] },\n });\n if (!res.ok) throw new Error(`Firestore REST-read ${collectionId} ${res.status}`);\n const rows = (await res.json()) as { document?: { name: string; fields: Record<string, FsValue> } }[];\n return rows\n .filter((r) => r.document)\n .map((r) => ({ id: r.document!.name.split(\"/\").pop()!, data: decodeFields(r.document!.fields ?? {}) }));\n}\n\n/** Pagina's uit de gecommitte bundle (of [] als er voor deze site geen bundle is). */\nfunction bundlePages(siteId: string): StoredPage[] {\n const bundle = getBundle();\n if (!bundle || bundle.siteId !== siteId) return [];\n return bundle.pages.map((p) => parse(p.id, p)).filter((p): p is StoredPage => p !== null);\n}\n\n/** Alle gepubliceerde pagina's (sitemap / generateStaticParams). Live-first (bundle-fallback) of bundle-first. */\nexport async function listPublishedPages(siteId: string): Promise<StoredPage[]> {\n if (LIVE_READS) {\n return liveOrBundle(\n `listPublishedPages ${siteId}`,\n async () => {\n const adb = getAdminDb();\n if (adb) {\n const snap = await adb.collection(`sites/${siteId}/pages`).where(\"status\", \"==\", \"published\").get();\n return snap.docs.map((d) => parse(d.id, d.data())).filter((p): p is StoredPage => p !== null);\n }\n return restQueryThrowing(siteId, [eq(\"status\", \"published\")]);\n },\n () => bundlePages(siteId),\n );\n }\n const bundle = getBundle();\n if (bundle && bundle.siteId === siteId) {\n return bundle.pages.map((p) => parse(p.id, p)).filter((p): p is StoredPage => p !== null);\n }\n const adb = getAdminDb();\n if (adb) {\n return safeAdminRead([], `listPublishedPages ${siteId}`, async () => {\n const snap = await adb.collection(`sites/${siteId}/pages`).where(\"status\", \"==\", \"published\").get();\n return snap.docs.map((d) => parse(d.id, d.data())).filter((p): p is StoredPage => p !== null);\n });\n }\n return restQuery(siteId, [eq(\"status\", \"published\")]);\n}\n\n/**\n * Per-request-dedup via React cache(): metadata (generateMetadata) én de render lezen dezelfde\n * pagina — zonder cache() waren dat twee identieke Firestore-reads per request. NB: de\n * `next:{revalidate}`-hint op het REST-pad dekt dit niet (POST-fetches cachen niet); route-level\n * ISR (revalidate=300 in de consumer) blijft de echte kosten-demper tussen requests.\n */\nexport const getPublishedPageByPath = cache(getPublishedPageByPathUncached);\nexport const getSiteSymbols = cache(getSiteSymbolsUncached);\nexport const getSiteCustomBlocks = cache(getSiteCustomBlocksUncached);\nexport const getPublicSiteSettings = cache(getPublicSiteSettingsUncached);\n\n/**\n * Eén gepubliceerde pagina op pad. Bundle-first, anders keyless via REST/Admin.\n * `opts.skipBundle` (voorbeeld-modus, `?preview=1` in de visuele editor): negeert de gecommitte\n * bundel en leest rechtstreeks — de bundel komt pas met vertraging mee via de gedebouncede deploy-\n * worker, dus een beheerder die net gepubliceerd heeft ziet anders tot ~15 min lang de oude tekst.\n */\nasync function getPublishedPageByPathUncached(\n siteId: string,\n path: string,\n opts?: { skipBundle?: boolean },\n): Promise<StoredPage | null> {\n // Live-modus (niet in preview): vers uit Firestore, met de bundle als fallback bij een outage.\n if (LIVE_READS && !opts?.skipBundle) {\n return liveOrBundle(\n `getPublishedPageByPath ${siteId}${path}`,\n async () => {\n const adb = getAdminDb();\n if (adb) {\n const snap = await adb\n .collection(`sites/${siteId}/pages`)\n .where(\"status\", \"==\", \"published\")\n .where(\"path\", \"==\", path)\n .limit(1)\n .get();\n const d = snap.docs[0];\n return d ? parse(d.id, d.data()) : null;\n }\n const rows = await restQueryThrowing(siteId, [eq(\"status\", \"published\"), eq(\"path\", path)], 1);\n return rows[0] ?? null;\n },\n () => {\n const b = getBundle();\n if (!b || b.siteId !== siteId) return null;\n const hit = getBundlePage(b, path);\n return hit ? parse(hit.id, hit) : null;\n },\n );\n }\n const bundle = opts?.skipBundle ? null : getBundle();\n if (bundle && bundle.siteId === siteId) {\n const hit = getBundlePage(bundle, path);\n return hit ? parse(hit.id, hit) : null;\n }\n const adb = getAdminDb();\n if (adb) {\n return safeAdminRead(null, `getPublishedPageByPath ${siteId}${path}`, async () => {\n const snap = await adb\n .collection(`sites/${siteId}/pages`)\n .where(\"status\", \"==\", \"published\")\n .where(\"path\", \"==\", path)\n .limit(1)\n .get();\n const d = snap.docs[0];\n return d ? parse(d.id, d.data()) : null;\n });\n }\n const rows = await restQuery(siteId, [eq(\"status\", \"published\"), eq(\"path\", path)], 1);\n return rows[0] ?? null;\n}\n\n/** Draft-versie (voor preview achter auth). */\nexport async function getDraftPageById(siteId: string, pageId: string): Promise<StoredPage | null> {\n const db = getAdminDb();\n if (!db) return null;\n return safeAdminRead(null, `getDraftPageById ${siteId}/${pageId}`, async () => {\n const d = await db.doc(`sites/${siteId}/drafts/${pageId}`).get();\n return d.exists ? parse(d.id, d.data()) : null;\n });\n}\n\n/** Draft op pad (preview). */\nexport async function getDraftPageByPath(siteId: string, path: string): Promise<StoredPage | null> {\n const db = getAdminDb();\n if (!db) return null;\n return safeAdminRead(null, `getDraftPageByPath ${siteId}${path}`, async () => {\n const snap = await db.collection(`sites/${siteId}/drafts`).where(\"path\", \"==\", path).limit(1).get();\n const d = snap.docs[0];\n return d ? parse(d.id, d.data()) : null;\n });\n}\n\n/* ---------------- symbols & custom blocks (5.0) ---------------- */\n\n/** Generieke publieke collectie-read onder sites/{siteId}/ — Admin als beschikbaar, anders REST. */\nasync function readCollection(siteId: string, collectionId: string): Promise<{ id: string; data: Record<string, unknown> }[]> {\n const adb = getAdminDb();\n if (adb) {\n return safeAdminRead([], `readCollection ${siteId}/${collectionId}`, async () => {\n const snap = await adb.collection(`sites/${siteId}/${collectionId}`).get();\n return snap.docs.map((d) => ({ id: d.id, data: d.data() as Record<string, unknown> }));\n });\n }\n const apiKey = process.env.NEXT_PUBLIC_FIREBASE_API_KEY;\n const projectId = process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID;\n if (!apiKey || !projectId) return [];\n const url = `https://firestore.googleapis.com/v1/projects/${projectId}/databases/(default)/documents/sites/${siteId}:runQuery?key=${apiKey}`;\n try {\n const res = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ structuredQuery: { from: [{ collectionId }] } }),\n next: { revalidate: ISR_FALLBACK, tags: [cacheTags.collection(siteId, collectionId)] },\n });\n if (!res.ok) return [];\n const rows = (await res.json()) as { document?: { name: string; fields: Record<string, FsValue> } }[];\n return rows\n .filter((r) => r.document)\n .map((r) => ({ id: r.document!.name.split(\"/\").pop()!, data: decodeFields(r.document!.fields ?? {}) }));\n } catch (err) {\n console.error(`[xicms] Firestore REST-read van ${collectionId} mislukt:`, err);\n return [];\n }\n}\n\n/* ── Nieuws ───────────────────────────────────────────────────────────────────────────────────\n * Nieuws was het enige publieke contenttype ZONDER live-tak: `getNewsPageData` (server/bundle.ts)\n * leest uitsluitend de build-time bundle. Op een live-reads-site betekende dat: je publiceert een\n * artikel en er verandert niets op de site, zonder één foutmelding (SITE-AUDIT §11). Deze lezer\n * doet voor nieuws wat `listPublishedPages` voor pagina's doet.\n */\n\n/** Gepubliceerd volgens de bundle-writer (functions/src/lib/bundle.ts) — één definitie, twee kanten. */\nfunction isPublishedNews(data: Record<string, unknown>): boolean {\n return data.published === true || data.status === \"published\";\n}\n\n/**\n * Firestore-waarden → gewone JS-waarden.\n *\n * Een Admin-SDK-read geeft `Timestamp`, `DocumentReference` en `GeoPoint` terug: klasse-instanties.\n * React weigert die van een server- naar een client-component te sturen (\"Only plain objects, and a\n * few built-ins, can be passed to Client Components\"), en het nieuws gaat via `BlockDataProvider`\n * precies die grens over. De bundle had dit nooit, want die is al JSON — dus dit kwam pas boven bij\n * het draaien: één artikel met een `serverTimestamp` en `/nieuws/<slug>` gaf een 500.\n *\n * Het REST-pad levert al gewone waarden (`decodeFields`), dus daar is dit een no-op.\n */\nfunction plain(v: unknown): unknown {\n if (v == null || typeof v !== \"object\") return v;\n if (Array.isArray(v)) return v.map(plain);\n if (v instanceof Date) return v.toISOString();\n const kandidaat = v as { toDate?: () => Date };\n if (typeof kandidaat.toDate === \"function\") return kandidaat.toDate().toISOString(); // Timestamp\n const proto = Object.getPrototypeOf(v);\n // Geen gewoon object (DocumentReference, GeoPoint, Buffer…): weglaten in plaats van doorgeven.\n if (proto !== Object.prototype && proto !== null) return undefined;\n const uit: Record<string, unknown> = {};\n for (const [k, x] of Object.entries(v as Record<string, unknown>)) {\n const w = plain(x);\n if (w !== undefined) uit[k] = w;\n }\n return uit;\n}\n\n/**\n * Rijen → artikelen: filteren, nieuwste eerst, en `bodyHtml` SANEREN.\n *\n * Dat saneren is geen extraatje maar een voorwaarde. `NewsArticleView` vertrouwt bodyHtml zodra het\n * exact overeenkomt met wat de server meegaf, en rendert het dan rechtstreeks in de SSR-HTML; alleen\n * ONvertrouwde HTML gaat door DOMPurify in de browser. De bundle-lezer saneert daarom al\n * (`getBundleNews`). Zou deze live-lezer dat overslaan, dan werd ruwe Firestore-HTML \"vertrouwd\" en\n * ongefilterd gerenderd — dan zou een live-tak toevoegen een XSS-gat openen dat er nu niet is.\n */\nfunction toPublishedNews(rows: { id: string; data: Record<string, unknown> }[]): NewsArticle[] {\n return rows\n .filter((r) => isPublishedNews(r.data))\n .map((r) => {\n const raw = r.data.bodyHtml;\n const data: Record<string, unknown> = { id: r.id, ...(plain(r.data) as Record<string, unknown>) };\n if (typeof raw === \"string\") data.bodyHtml = sanitizeRichText(raw);\n return data;\n })\n .sort((a, b) => String(b.date ?? \"\").localeCompare(String(a.date ?? \"\"))) as unknown as NewsArticle[];\n}\n\n/** Nieuws-rijen uit de gecommitte bundle (of [] als er voor deze site geen bundle is). */\nfunction bundleNewsRows(siteId: string): { id: string; data: Record<string, unknown> }[] {\n const bundle = getBundle();\n return bundle && bundle.siteId === siteId\n ? bundle.news.map((n) => ({ id: n.id, data: n as Record<string, unknown> }))\n : [];\n}\n\nasync function listPublishedNewsUncached(siteId: string): Promise<NewsArticle[]> {\n let rows: { id: string; data: Record<string, unknown> }[];\n if (LIVE_READS) {\n rows = await liveOrBundle(\n `listPublishedNews ${siteId}`,\n () => readCollectionThrowing(siteId, \"news\"),\n () => bundleNewsRows(siteId),\n );\n } else {\n const bundle = getBundle();\n rows = bundle && bundle.siteId === siteId ? bundleNewsRows(siteId) : await readCollection(siteId, \"news\");\n }\n return toPublishedNews(rows);\n}\n\n/**\n * Gepubliceerde nieuwsartikelen, nieuwste eerst. Live-first (bundle als statische fallback) of\n * bundle-first — exact hetzelfde contract als `listPublishedPages`.\n *\n * Gebruik dit in de publieke nieuwsroutes. `getNewsPageData` blijft bestaan en doet precies wat de\n * naam zegt: de bundle lezen, synchroon — handig waar je gegarandeerd build-time data wilt (sitemap\n * bij een statische build), maar het is niet de verse bron.\n */\nexport const listPublishedNews = cache(listPublishedNewsUncached);\n\n/** Symbool-rijen uit de gecommitte bundle (of [] als er voor deze site geen bundle is). */\nfunction bundleSymbolRows(siteId: string): { id: string; data: Record<string, unknown> }[] {\n const bundle = getBundle();\n return bundle && bundle.siteId === siteId\n ? bundle.symbols.map((s) => ({ id: s.id, data: s as Record<string, unknown> }))\n : [];\n}\n\n/** Alle symbolen van een site (gevalideerd; ongeldige overgeslagen met waarschuwing). Live-first of bundle-first. */\nasync function getSiteSymbolsUncached(siteId: string): Promise<StoredSymbol[]> {\n let rows: { id: string; data: Record<string, unknown> }[];\n if (LIVE_READS) {\n rows = await liveOrBundle(`getSiteSymbols ${siteId}`, () => readCollectionThrowing(siteId, \"symbols\"), () => bundleSymbolRows(siteId));\n } else {\n const bundle = getBundle();\n rows = bundle && bundle.siteId === siteId ? bundleSymbolRows(siteId) : await readCollection(siteId, \"symbols\");\n }\n const out: StoredSymbol[] = [];\n for (const r of rows) {\n const res = SymbolSchema.safeParse(r.data);\n if (res.success) out.push({ id: r.id, ...res.data });\n else console.warn(`[xicms] symbool \"${r.id}\" ongeldig — overgeslagen.`);\n }\n return out;\n}\n\n/**\n * Vervangt symbolRef-blokken door de blokken van hun symbool (één niveau — symbolen kunnen zelf\n * geen symbolRef bevatten). `_key`s van de ingevoegde blokken worden geprefixt met de ref-key\n * zodat ze uniek blijven wanneer hetzelfde symbool vaker op een pagina staat. Verweesde refs\n * verdwijnen stil (de pagina mag nooit breken op een verwijderd symbool).\n */\nexport function expandSymbols<T extends { blocks?: unknown[] }>(page: T, symbols: StoredSymbol[]): T {\n const byId = new Map(symbols.map((s) => [s.id, s]));\n const blocks = (page.blocks ?? []).flatMap((b) => {\n const blk = b as { _type?: string; _key?: string; symbolId?: string };\n if (blk?._type !== \"symbolRef\") return [b];\n const sym = blk.symbolId ? byId.get(blk.symbolId) : undefined;\n if (!sym) return [];\n return sym.blocks.map((sb, i) => ({ ...(sb as Record<string, unknown>), _key: `${blk._key ?? \"sym\"}-${(sb as { _key?: string })._key ?? i}` }));\n });\n return { ...page, blocks };\n}\n\n/**\n * Gepubliceerde pagina op pad mét symbolRef-expansie (SEO: symboolinhoud zit in de SSR-HTML).\n * Geef `locale` mee (een NIET-default taal) om de vertaal-overlay toe te passen — op de pagina\n * én op de ingevoegde symbolen (elk symbool draagt zijn eigen `translations`).\n *\n * Bundle-first werkt hier automatisch: deze functie componeert `getPublishedPageByPath` en\n * `getSiteSymbols`, die beide al bundle-first zijn — de i18n-overlay en expansie draaien identiek\n * op bundle- én Firestore-data.\n */\nexport async function getPublishedPageByPathExpanded(\n siteId: string,\n path: string,\n locale?: string,\n opts?: { skipBundle?: boolean },\n): Promise<StoredPage | null> {\n let page = await getPublishedPageByPath(siteId, path, opts);\n if (!page) return null;\n // De caller geeft alléén een niet-default taal door; \"\" als default matcht dus nooit.\n if (locale) page = localizePage(page, locale, \"\");\n if (!(page.blocks ?? []).some((b) => (b as { _type?: string })._type === \"symbolRef\")) return page;\n let symbols = await getSiteSymbols(siteId);\n if (locale) {\n symbols = symbols.map((s) => ({\n ...s,\n blocks: localizeBlocks(s.blocks as Array<{ _key?: string }>, s.translations?.[locale]) as typeof s.blocks,\n }));\n }\n return expandSymbols(page, symbols);\n}\n\n/**\n * Publieke site-instellingen (`sites/{siteId}/settings/public`) server-side — voor de\n * `settingsData`-prop van XiCMSProvider. Zo zit het klant-thema/nav/footer al in de server-HTML\n * (geen theme-flits bij eerste paint) én vervalt de per-bezoeker `getDoc` in de browser: de\n * layout-route is ISR-gecached, dus dit kost ~1 read per revalidate i.p.v. per bezoeker.\n * Fouten leveren `{}` op — de site valt dan terug op de statische SiteConfig.\n */\nasync function getPublicSiteSettingsUncached(siteId: string): Promise<PublicSiteSettings> {\n const bundleSettings = (): PublicSiteSettings => {\n const b = getBundle();\n if (!b || b.siteId !== siteId) return {};\n const parsed = PublicSiteSettingsSchema.safeParse(b.settings ?? {});\n return parsed.success ? parsed.data : {};\n };\n // Live-modus: vers uit Firestore (throwing), met de bundle-settings als fallback bij een outage.\n if (LIVE_READS) {\n return liveOrBundle(\n `getPublicSiteSettings ${siteId}`,\n async () => {\n const adb = getAdminDb();\n let data: unknown = {};\n if (adb) {\n const d = await adb.doc(`sites/${siteId}/settings/public`).get();\n data = d.exists ? d.data() : {};\n } else {\n const { apiKey, projectId } = restConfigOrThrow();\n const url = `https://firestore.googleapis.com/v1/projects/${projectId}/databases/(default)/documents/sites/${siteId}/settings/public?key=${apiKey}`;\n const res = await fetch(url, { next: { revalidate: ISR_FALLBACK, tags: [cacheTags.collection(siteId, \"settings\")] } });\n // 404 = document bestaat (nog) niet → lege settings; andere niet-2xx = outage → gooi (fallback).\n if (res.status === 404) data = {};\n else if (!res.ok) throw new Error(`Firestore REST-read settings/public ${res.status}`);\n else {\n const doc = (await res.json()) as { fields?: Record<string, FsValue> };\n data = decodeFields(doc.fields ?? {});\n }\n }\n const parsed = PublicSiteSettingsSchema.safeParse(data ?? {});\n return parsed.success ? parsed.data : {};\n },\n bundleSettings,\n );\n }\n const bundle = getBundle();\n if (bundle && bundle.siteId === siteId) {\n return bundleSettings();\n }\n const adb = getAdminDb();\n let data: unknown = {};\n if (adb) {\n data = await safeAdminRead<unknown>({}, `getPublicSiteSettings ${siteId}`, async () => {\n const d = await adb.doc(`sites/${siteId}/settings/public`).get();\n return d.exists ? d.data() : {};\n });\n } else {\n const apiKey = process.env.NEXT_PUBLIC_FIREBASE_API_KEY;\n const projectId = process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID;\n if (apiKey && projectId) {\n const url = `https://firestore.googleapis.com/v1/projects/${projectId}/databases/(default)/documents/sites/${siteId}/settings/public?key=${apiKey}`;\n try {\n // GET-fetches worden — anders dan de runQuery-POSTs — wél door de Next-datacache gedekt.\n const res = await fetch(url, { next: { revalidate: ISR_FALLBACK, tags: [cacheTags.collection(siteId, \"settings\")] } });\n if (res.ok) {\n const doc = (await res.json()) as { fields?: Record<string, FsValue> };\n data = decodeFields(doc.fields ?? {});\n }\n } catch (err) {\n console.error(\"[xicms] Firestore REST-read van settings/public mislukt:\", err);\n }\n }\n }\n const parsed = PublicSiteSettingsSchema.safeParse(data ?? {});\n return parsed.success ? parsed.data : {};\n}\n\n/** Custom-block-rijen uit de gecommitte bundle (of [] als er voor deze site geen bundle is). */\nfunction bundleCustomBlockRows(siteId: string): { id: string; data: Record<string, unknown> }[] {\n const bundle = getBundle();\n return bundle && bundle.siteId === siteId\n ? bundle.customBlocks.map((c) => ({ id: c.id, data: c as Record<string, unknown> }))\n : [];\n}\n\n/** Alle custom blocks van een site (gevalideerd) — voor SSR-registratie via XiCMSProvider. Live-first of bundle-first. */\nasync function getSiteCustomBlocksUncached(siteId: string): Promise<CustomBlock[]> {\n let rows: { id: string; data: Record<string, unknown> }[];\n if (LIVE_READS) {\n rows = await liveOrBundle(`getSiteCustomBlocks ${siteId}`, () => readCollectionThrowing(siteId, \"customBlocks\"), () => bundleCustomBlockRows(siteId));\n } else {\n const bundle = getBundle();\n rows = bundle && bundle.siteId === siteId ? bundleCustomBlockRows(siteId) : await readCollection(siteId, \"customBlocks\");\n }\n const out: CustomBlock[] = [];\n for (const r of rows) {\n const res = CustomBlockSchema.safeParse({ ...r.data, type: r.data.type ?? r.id });\n if (res.success) out.push(res.data);\n else console.warn(`[xicms] custom block \"${r.id}\" ongeldig — overgeslagen.`);\n }\n return out;\n}\n"],"mappings":"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","names":["adb","rows","parsed","data"]}
package/dist/styles.css CHANGED
@@ -246,7 +246,10 @@
246
246
  inset: 0;
247
247
  display: flex;
248
248
  flex-direction: column;
249
- background: #f1f5f9;
249
+ /* Tokens, geen vaste kleuren: het beheer stond hardgecodeerd licht terwijl de velden erin de
250
+ Astryx-tokens volgden — in donkere modus werd dat een wit paneel met onleesbare labels. */
251
+ background: var(--color-background-body, #f1f5f9);
252
+ color: var(--color-text-primary, #0f172a);
250
253
  }
251
254
  .xicms-ve__bar {
252
255
  display: flex;
@@ -301,6 +304,11 @@
301
304
  opacity: 0.35;
302
305
  cursor: default;
303
306
  }
307
+ /* Ingedrukt: het tandwiel houdt de lade open, dus laat het ook zien dat hij aan staat. */
308
+ .xicms-ve__histbtn.is-on {
309
+ background: rgba(255, 255, 255, 0.2);
310
+ border-color: rgba(255, 255, 255, 0.5);
311
+ }
304
312
  /* Conflictbalk: iemand anders wijzigde deze pagina intussen (andere tab/collega). */
305
313
  .xicms-ve__conflict {
306
314
  display: flex;
@@ -316,10 +324,10 @@
316
324
  }
317
325
  /* Inklapbare secties in het zijpaneel (pagina-instellingen, versiegeschiedenis). */
318
326
  .xicms-ve__section {
319
- border: 1px solid #e2e8f0;
327
+ border: 1px solid var(--color-border, #e2e8f0);
320
328
  border-radius: 10px;
321
329
  padding: 0.6rem 0.8rem;
322
- background: #f8fafc;
330
+ background: var(--color-background-muted, #f8fafc);
323
331
  }
324
332
  .xicms-ve__section > summary {
325
333
  cursor: pointer;
@@ -328,9 +336,15 @@
328
336
  .xicms-ve__body {
329
337
  flex: 1 1 auto;
330
338
  display: grid;
331
- grid-template-columns: 1fr 360px;
339
+ /* Standaard alleen het canvas. Het paneel is een lade die je met het tandwiel opent; sinds
340
+ alle tekst inline bewerkbaar is, kost een altijd-open zijbalk 360px voor iets wat je maar
341
+ zelden nodig hebt — en hij duwt de pagina die je aan het vormgeven bent uit beeld. */
342
+ grid-template-columns: 1fr;
332
343
  min-height: 0;
333
344
  }
345
+ .xicms-ve__body.is-panel-open {
346
+ grid-template-columns: 1fr 360px;
347
+ }
334
348
  .xicms-ve__canvas {
335
349
  overflow: auto;
336
350
  padding: 1.5rem;
@@ -342,8 +356,9 @@
342
356
  }
343
357
  .xicms-ve__panel {
344
358
  overflow: auto;
345
- border-left: 1px solid #e2e8f0;
346
- background: #fff;
359
+ border-left: 1px solid var(--color-border, #e2e8f0);
360
+ background: var(--color-background-card, #fff);
361
+ color: var(--color-text-primary, #0f172a);
347
362
  padding: 1rem;
348
363
  }
349
364
  /* Blijft zichtbaar bovenaan het (op mobiel begrensde, scrollbare) zijpaneel terwijl je door de
@@ -358,11 +373,11 @@
358
373
  gap: 0.5rem;
359
374
  margin: -1rem -1rem 0;
360
375
  padding: 0.6rem 1rem;
361
- background: #fff;
362
- border-bottom: 1px solid #e2e8f0;
376
+ background: var(--color-background-card, #fff);
377
+ border-bottom: 1px solid var(--color-border, #e2e8f0);
363
378
  }
364
379
  .xicms-ve__hint {
365
- color: #64748b;
380
+ color: var(--color-text-secondary, #64748b);
366
381
  font-size: 0.9rem;
367
382
  padding: 1rem;
368
383
  }
@@ -372,10 +387,10 @@
372
387
  gap: 0.75rem;
373
388
  margin-top: 1rem;
374
389
  padding: 0.75rem;
375
- border: 1px dashed #cbd5e1;
390
+ border: 1px dashed var(--color-border, #cbd5e1);
376
391
  border-radius: 10px;
377
392
  font-size: 0.9rem;
378
- color: #475569;
393
+ color: var(--color-text-secondary, #475569);
379
394
  }
380
395
  .xicms-ve__inserter-select {
381
396
  width: 16rem;
@@ -476,22 +491,58 @@
476
491
  .xicms-ve-block.is-drop-after::after {
477
492
  bottom: -6px;
478
493
  }
479
- /* Inline (in-canvas) bewerkbaar tekstveld in de visuele editor. */
494
+ /* Inline (in-canvas) bewerkbaar tekstveld in de visuele editor.
495
+ In rust bewust ONZICHTBAAR: het blok zou anders als een formulier vol stippellijnen lezen, en
496
+ dan zie je de pagina niet meer. Het potloodje ernaast wijst het veld aan; de omlijning verschijnt
497
+ pas als je erlangs gaat, en wordt vast zodra je daadwerkelijk bewerkt. */
480
498
  .xicms-ve-inline {
481
- cursor: text;
482
499
  border-radius: 4px;
483
- outline: 1px dashed rgba(79, 70, 229, 0.5);
500
+ outline: 1px dashed transparent;
484
501
  outline-offset: 3px;
485
502
  transition: outline-color 0.12s, background 0.12s;
486
503
  }
487
- .xicms-ve-inline:hover {
488
- background: rgba(79, 70, 229, 0.06);
504
+ .xicms-ve-inline:hover,
505
+ .xicms-ve-inline.is-hover {
506
+ outline-color: rgba(79, 70, 229, 0.45);
507
+ background: rgba(79, 70, 229, 0.05);
489
508
  }
490
- .xicms-ve-inline:focus {
509
+ .xicms-ve-inline.is-editing {
510
+ cursor: text;
491
511
  outline: 2px solid var(--xicms-primary, #4f46e5);
492
512
  outline-offset: 3px;
493
513
  background: rgba(79, 70, 229, 0.06);
494
514
  }
515
+ /* Het potlood: de bewuste stap tussen "dit kán" en "ik bewerk het nu". Zo licht mogelijk — geen
516
+ rand, geen schaduw, geen wit schijfje: een blok met tien velden moet geen kerstboom worden.
517
+ In rust een lichte afdruk in de marge; onder de muis krijgt het pas kleur en een zachte cirkel. */
518
+ .xicms-ve-pencil {
519
+ position: fixed;
520
+ z-index: 10004;
521
+ width: 18px;
522
+ height: 18px;
523
+ padding: 0;
524
+ display: grid;
525
+ place-items: center;
526
+ border: 0;
527
+ border-radius: 50%;
528
+ /* Het canvas toont de site van de klant: die kan wit zijn of een donkere foto. Eén vaste kleur
529
+ verdwijnt dus altijd ergens. Een donker, halfdoorzichtig schijfje met een wit potlood houdt
530
+ zich op allebei staande — licht genoeg om niet op te dringen, donker genoeg om te zien. */
531
+ background: rgba(15, 23, 42, 0.4);
532
+ color: #fff;
533
+ cursor: pointer;
534
+ opacity: 0.5;
535
+ transition: opacity 0.12s, background 0.12s;
536
+ }
537
+ .xicms-ve-pencil:hover,
538
+ .xicms-ve-pencil:focus-visible {
539
+ opacity: 1;
540
+ background: var(--xicms-primary, #4f46e5);
541
+ }
542
+ .xicms-ve-pencil:focus-visible {
543
+ outline: 2px solid var(--xicms-primary, #4f46e5);
544
+ outline-offset: 1px;
545
+ }
495
546
  /* Zwevende opmaak-toolbar boven een gefocust inline rich-text-veld (B/I/U/link/koppen/lijst). */
496
547
  .xicms-ve-fmtbar {
497
548
  position: fixed;
@@ -695,35 +746,85 @@
695
746
  /* Zwevende werkbalk voor editors */
696
747
  .xicms-editbar {
697
748
  position: fixed;
698
- bottom: 12px;
749
+ /* `--xicms-bottombar` wordt gezet door de demo-balk (zie DemoLock): die beslaat de onderrand,
750
+ en zonder deze marge lagen de twee over elkaar heen. Zonder demo-balk is de variabele leeg
751
+ en blijft het gewoon 14px. */
752
+ bottom: calc(14px + var(--xicms-bottombar, 0px));
699
753
  left: 50%;
700
754
  transform: translateX(-50%);
701
755
  z-index: 9999;
702
756
  display: flex;
703
757
  align-items: center;
704
- gap: 0.4rem;
758
+ gap: 0.3rem;
705
759
  /* Compact + nooit breder dan het scherm → past op één regel, ook op smalle telefoons. */
706
760
  max-width: calc(100vw - 16px);
707
- padding: 0.35rem 0.5rem;
708
- background: #0f172a;
761
+ padding: 0.3rem 0.35rem 0.3rem 0.85rem;
762
+ /* Eigen kleuren, los van de site eronder: een donkere glasstaaf leest op een witte én op een
763
+ donkere pagina, en blijft op élke klantsite hetzelfde stukje gereedschap. */
764
+ background: rgba(15, 23, 42, 0.92);
765
+ -webkit-backdrop-filter: saturate(1.4) blur(10px);
766
+ backdrop-filter: saturate(1.4) blur(10px);
709
767
  color: #fff;
768
+ border: 1px solid rgba(255, 255, 255, 0.12);
710
769
  border-radius: 999px;
711
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
770
+ box-shadow: 0 10px 30px rgba(2, 6, 23, 0.35);
712
771
  font-size: 0.8rem;
772
+ line-height: 1;
773
+ }
774
+ .xicms-editbar__brand {
775
+ white-space: nowrap;
776
+ font-weight: 600;
777
+ letter-spacing: 0.01em;
778
+ /* Een merklabel, geen actie: het mag niet met de knoppen om aandacht vechten. */
779
+ opacity: 0.6;
780
+ padding-right: 0.2rem;
713
781
  }
714
- .xicms-editbar > span { white-space: nowrap; }
715
782
  /* Op smalle telefoons het merklabel weglaten zodat de knoppen zeker op één regel passen. */
716
783
  @media (max-width: 480px) {
717
- .xicms-editbar > span { display: none; }
784
+ .xicms-editbar__brand {
785
+ display: none;
786
+ }
718
787
  }
719
- .xicms-editbar .xicms-btn {
720
- padding: 0.3rem 0.7rem;
721
- font-size: 0.8rem;
788
+ .xicms-editbar__sep {
789
+ width: 1px;
790
+ align-self: stretch;
791
+ margin: 0.25rem 0.15rem;
792
+ background: rgba(255, 255, 255, 0.16);
793
+ }
794
+ .xicms-editbar__btn {
795
+ appearance: none;
796
+ display: inline-flex;
797
+ align-items: center;
798
+ border: 0;
799
+ border-radius: 999px;
800
+ padding: 0.42rem 0.8rem;
801
+ background: transparent;
802
+ color: rgba(255, 255, 255, 0.82);
803
+ font: inherit;
804
+ font-weight: 500;
722
805
  white-space: nowrap;
806
+ text-decoration: none;
807
+ cursor: pointer;
808
+ transition: background 0.12s, color 0.12s;
723
809
  }
724
- .xicms-editbar .xicms-btn--ghost {
810
+ .xicms-editbar__btn:hover {
811
+ background: rgba(255, 255, 255, 0.12);
725
812
  color: #fff;
726
- border-color: rgba(255, 255, 255, 0.3);
813
+ }
814
+ .xicms-editbar__btn:focus-visible {
815
+ outline: 2px solid #a5b4fc;
816
+ outline-offset: 2px;
817
+ }
818
+ /* De hoofdactie draagt als enige een vlak: één ding om op te klikken, de rest is bijzaak. */
819
+ .xicms-editbar__btn--primary {
820
+ background: #fff;
821
+ color: #0f172a;
822
+ font-weight: 600;
823
+ }
824
+ .xicms-editbar__btn--primary:hover {
825
+ background: #fff;
826
+ color: #0f172a;
827
+ filter: brightness(0.92);
727
828
  }
728
829
 
729
830
  /* Demo-bewerkmodus: bovenbalk met pseudo-login + duidelijke "niet opgeslagen"-melding */
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Kleur-afleidingen voor het thema. Puur, zonder React of andere afhankelijkheden.
3
+ *
4
+ * Stond eerst in `XiCMSProvider.tsx`. Dat leek logisch (daar wordt het gebruikt), maar het maakte
5
+ * een pure functie van vijf regels onbereikbaar zonder de hele provider-keten mee te trekken —
6
+ * React, de blok-registry, de modules, de editor. De test die `isDarkColor` controleert importeerde
7
+ * dus feitelijk de halve engine, en tikte op een bezette machine geregeld tegen de 5s-testlimiet
8
+ * aan: een test die willekeurig faalt zonder dat er iets mis is. `XiCMSProvider` re-exporteert het
9
+ * nog steeds, dus de publieke API blijft gelijk.
10
+ */
11
+ /**
12
+ * Is deze (hex-)kleur donker? Bepaalt de Astryx-thememodus voor sites met een vaste donkere
13
+ * achtergrond (bv. presets donker-modern/luxe-donker, `dark.auto: false`): zonder deze afleiding
14
+ * bleef Astryx op "light" staan en tekenden zijn componenten lichte kaarten op een donkere pagina.
15
+ * Niet-hex waarden gelden als licht (veilige default).
16
+ */
17
+ export declare function isDarkColor(color?: string): boolean;
@@ -0,0 +1,12 @@
1
+ function isDarkColor(color) {
2
+ const m = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(color?.trim() ?? "");
3
+ if (!m) return false;
4
+ let hex = m[1];
5
+ if (hex.length === 3) hex = hex.replace(/./g, (c) => c + c);
6
+ const [r, g, b] = [0, 2, 4].map((i) => parseInt(hex.slice(i, i + 2), 16) / 255);
7
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b < 0.45;
8
+ }
9
+ export {
10
+ isDarkColor
11
+ };
12
+ //# sourceMappingURL=color.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/theme/color.ts"],"sourcesContent":["/**\n * Kleur-afleidingen voor het thema. Puur, zonder React of andere afhankelijkheden.\n *\n * Stond eerst in `XiCMSProvider.tsx`. Dat leek logisch (daar wordt het gebruikt), maar het maakte\n * een pure functie van vijf regels onbereikbaar zonder de hele provider-keten mee te trekken —\n * React, de blok-registry, de modules, de editor. De test die `isDarkColor` controleert importeerde\n * dus feitelijk de halve engine, en tikte op een bezette machine geregeld tegen de 5s-testlimiet\n * aan: een test die willekeurig faalt zonder dat er iets mis is. `XiCMSProvider` re-exporteert het\n * nog steeds, dus de publieke API blijft gelijk.\n */\n\n/**\n * Is deze (hex-)kleur donker? Bepaalt de Astryx-thememodus voor sites met een vaste donkere\n * achtergrond (bv. presets donker-modern/luxe-donker, `dark.auto: false`): zonder deze afleiding\n * bleef Astryx op \"light\" staan en tekenden zijn componenten lichte kaarten op een donkere pagina.\n * Niet-hex waarden gelden als licht (veilige default).\n */\nexport function isDarkColor(color?: string): boolean {\n const m = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(color?.trim() ?? \"\");\n if (!m) return false;\n let hex = m[1];\n if (hex.length === 3) hex = hex.replace(/./g, (c) => c + c);\n const [r, g, b] = [0, 2, 4].map((i) => parseInt(hex.slice(i, i + 2), 16) / 255);\n return 0.2126 * r + 0.7152 * g + 0.0722 * b < 0.45;\n}\n"],"mappings":"AAiBO,SAAS,YAAY,OAAyB;AACnD,QAAM,IAAI,gCAAgC,KAAK,OAAO,KAAK,KAAK,EAAE;AAClE,MAAI,CAAC,EAAG,QAAO;AACf,MAAI,MAAM,EAAE,CAAC;AACb,MAAI,IAAI,WAAW,EAAG,OAAM,IAAI,QAAQ,MAAM,CAAC,MAAM,IAAI,CAAC;AAC1D,QAAM,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,EAAE,IAAI,CAAC,MAAM,SAAS,IAAI,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,GAAG;AAC9E,SAAO,SAAS,IAAI,SAAS,IAAI,SAAS,IAAI;AAChD;","names":[]}
package/dist/version.js CHANGED
@@ -1,4 +1,4 @@
1
- const CORE_VERSION = true ? "7.2.0" : "0.0.0-dev";
1
+ const CORE_VERSION = true ? "7.3.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.2.0",
3
+ "version": "7.3.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": {