@xiwebdesign/xicms-core 5.3.0 → 5.5.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 (135) hide show
  1. package/dist/XiCMSProvider.d.ts +11 -2
  2. package/dist/XiCMSProvider.js +10 -5
  3. package/dist/XiCMSProvider.js.map +1 -1
  4. package/dist/admin/blockEditors/GenericEditor.js +18 -8
  5. package/dist/admin/blockEditors/GenericEditor.js.map +1 -1
  6. package/dist/admin/customBlocks/CustomBlocksPanel.js +12 -2
  7. package/dist/admin/customBlocks/CustomBlocksPanel.js.map +1 -1
  8. package/dist/admin/lib/pagesClient.js +4 -1
  9. package/dist/admin/lib/pagesClient.js.map +1 -1
  10. package/dist/admin/lib/upload.js +14 -7
  11. package/dist/admin/lib/upload.js.map +1 -1
  12. package/dist/admin/media/MediaManager.js +60 -28
  13. package/dist/admin/media/MediaManager.js.map +1 -1
  14. package/dist/admin/media/MediaPicker.js +25 -15
  15. package/dist/admin/media/MediaPicker.js.map +1 -1
  16. package/dist/admin/pages/PageEditor.d.ts +2 -1
  17. package/dist/admin/pages/PageEditor.js +16 -2
  18. package/dist/admin/pages/PageEditor.js.map +1 -1
  19. package/dist/admin/pages/PagesPanel.js +9 -5
  20. package/dist/admin/pages/PagesPanel.js.map +1 -1
  21. package/dist/admin/panelRegistry.js +3 -0
  22. package/dist/admin/panelRegistry.js.map +1 -1
  23. package/dist/admin/shop/CategoriesTab.d.ts +3 -0
  24. package/dist/admin/shop/CategoriesTab.js +140 -0
  25. package/dist/admin/shop/CategoriesTab.js.map +1 -0
  26. package/dist/admin/shop/ConnectionsTab.d.ts +9 -0
  27. package/dist/admin/shop/ConnectionsTab.js +158 -0
  28. package/dist/admin/shop/ConnectionsTab.js.map +1 -0
  29. package/dist/admin/shop/OrdersTab.d.ts +3 -0
  30. package/dist/admin/shop/OrdersTab.js +236 -0
  31. package/dist/admin/shop/OrdersTab.js.map +1 -0
  32. package/dist/admin/shop/ProductsTab.d.ts +3 -0
  33. package/dist/admin/shop/ProductsTab.js +397 -0
  34. package/dist/admin/shop/ProductsTab.js.map +1 -0
  35. package/dist/admin/shop/ShopPanel.d.ts +4 -0
  36. package/dist/admin/shop/ShopPanel.js +25 -0
  37. package/dist/admin/shop/ShopPanel.js.map +1 -0
  38. package/dist/admin/shop/lib.d.ts +16 -0
  39. package/dist/admin/shop/lib.js +46 -0
  40. package/dist/admin/shop/lib.js.map +1 -0
  41. package/dist/admin/visual/VisualEditor.js +11 -2
  42. package/dist/admin/visual/VisualEditor.js.map +1 -1
  43. package/dist/admin.css +1 -1
  44. package/dist/agentFiles/files.d.ts +15 -3
  45. package/dist/agentFiles/files.js +57 -0
  46. package/dist/agentFiles/files.js.map +1 -1
  47. package/dist/auth/AuthProvider.js +3 -3
  48. package/dist/auth/AuthProvider.js.map +1 -1
  49. package/dist/blocks/builtins/Columns.js +1 -1
  50. package/dist/blocks/builtins/Columns.js.map +1 -1
  51. package/dist/blocks/builtins/Gallery.js +2 -2
  52. package/dist/blocks/builtins/Gallery.js.map +1 -1
  53. package/dist/blocks/builtins/Hero.js +1 -1
  54. package/dist/blocks/builtins/Hero.js.map +1 -1
  55. package/dist/blocks/builtins/SymbolRef.js.map +1 -1
  56. package/dist/components/PageView.js +4 -3
  57. package/dist/components/PageView.js.map +1 -1
  58. package/dist/demo/index.d.ts +7 -0
  59. package/dist/demo/index.js +23 -0
  60. package/dist/demo/index.js.map +1 -0
  61. package/dist/index.d.ts +2 -4
  62. package/dist/index.js +0 -20
  63. package/dist/index.js.map +1 -1
  64. package/dist/modules/builtins.d.ts +1 -0
  65. package/dist/modules/builtins.js +4 -0
  66. package/dist/modules/builtins.js.map +1 -1
  67. package/dist/modules/index.d.ts +4 -0
  68. package/dist/modules/index.js +12 -0
  69. package/dist/modules/index.js.map +1 -1
  70. package/dist/modules/registry.d.ts +3 -4
  71. package/dist/modules/registry.js +2 -3
  72. package/dist/modules/registry.js.map +1 -1
  73. package/dist/modules/shop/blocks/Cart.d.ts +2 -0
  74. package/dist/modules/shop/blocks/Cart.js +91 -0
  75. package/dist/modules/shop/blocks/Cart.js.map +1 -0
  76. package/dist/modules/shop/blocks/Checkout.d.ts +2 -0
  77. package/dist/modules/shop/blocks/Checkout.js +126 -0
  78. package/dist/modules/shop/blocks/Checkout.js.map +1 -0
  79. package/dist/modules/shop/blocks/Donation.d.ts +2 -0
  80. package/dist/modules/shop/blocks/Donation.js +145 -0
  81. package/dist/modules/shop/blocks/Donation.js.map +1 -0
  82. package/dist/modules/shop/blocks/OrderStatus.d.ts +2 -0
  83. package/dist/modules/shop/blocks/OrderStatus.js +40 -0
  84. package/dist/modules/shop/blocks/OrderStatus.js.map +1 -0
  85. package/dist/modules/shop/blocks/ProductDetail.d.ts +2 -0
  86. package/dist/modules/shop/blocks/ProductDetail.js +168 -0
  87. package/dist/modules/shop/blocks/ProductDetail.js.map +1 -0
  88. package/dist/modules/shop/blocks/ProductGrid.d.ts +8 -0
  89. package/dist/modules/shop/blocks/ProductGrid.js +136 -0
  90. package/dist/modules/shop/blocks/ProductGrid.js.map +1 -0
  91. package/dist/modules/shop/blocks/ShopCategories.d.ts +2 -0
  92. package/dist/modules/shop/blocks/ShopCategories.js +69 -0
  93. package/dist/modules/shop/blocks/ShopCategories.js.map +1 -0
  94. package/dist/modules/shop/cartStore.d.ts +22 -0
  95. package/dist/modules/shop/cartStore.js +85 -0
  96. package/dist/modules/shop/cartStore.js.map +1 -0
  97. package/dist/modules/shop/lib.d.ts +25 -0
  98. package/dist/modules/shop/lib.js +61 -0
  99. package/dist/modules/shop/lib.js.map +1 -0
  100. package/dist/modules/shop/module.d.ts +1 -0
  101. package/dist/modules/shop/module.js +39 -0
  102. package/dist/modules/shop/module.js.map +1 -0
  103. package/dist/modules/shop/settings.d.ts +41 -0
  104. package/dist/modules/shop/settings.js +18 -0
  105. package/dist/modules/shop/settings.js.map +1 -0
  106. package/dist/schema/blocks.d.ts +27 -12
  107. package/dist/schema/blocks.js +3 -0
  108. package/dist/schema/blocks.js.map +1 -1
  109. package/dist/schema/index.d.ts +1 -0
  110. package/dist/schema/index.js +1 -0
  111. package/dist/schema/index.js.map +1 -1
  112. package/dist/schema/settings.d.ts +1 -34
  113. package/dist/schema/settings.js +2 -8
  114. package/dist/schema/settings.js.map +1 -1
  115. package/dist/schema/shop.d.ts +939 -0
  116. package/dist/schema/shop.js +209 -0
  117. package/dist/schema/shop.js.map +1 -0
  118. package/dist/schema/siteConfig.d.ts +3 -71
  119. package/dist/schema/siteConfig.js +3 -8
  120. package/dist/schema/siteConfig.js.map +1 -1
  121. package/dist/schema/symbols.d.ts +2 -2
  122. package/dist/schema/symbols.js.map +1 -1
  123. package/dist/server/index.d.ts +1 -1
  124. package/dist/server/index.js +2 -0
  125. package/dist/server/index.js.map +1 -1
  126. package/dist/server/pages.d.ts +23 -3
  127. package/dist/server/pages.js +37 -3
  128. package/dist/server/pages.js.map +1 -1
  129. package/dist/server/seo.js +5 -3
  130. package/dist/server/seo.js.map +1 -1
  131. package/dist/settings/useSiteSettings.d.ts +15 -3
  132. package/dist/settings/useSiteSettings.js +26 -5
  133. package/dist/settings/useSiteSettings.js.map +1 -1
  134. package/dist/styles.css +14 -0
  135. package/package.json +6 -2
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/agentFiles/files.ts"],"sourcesContent":["/**\n * Agent-bestandslaag (5.0, primo-concept \"DB ⇄ files\"): pure serialisatie-, hash- en\n * sync-logica voor de `pull-site`/`push-site`-scripts. Een site wordt een map met YAML/HTML-\n * bestanden die een coding-agent direct kan bewerken; `.xicms/manifest.json` onthoudt per\n * entiteit een inhouds-hash van het pull-moment zodat push conflicten kan detecteren\n * (drie-wegen: basis/lokaal/remote) in plaats van stil werk te overschrijven.\n *\n * Alleen pure functies — geen I/O, geen Firebase — zodat dit unit-testbaar is en zowel de\n * scripts als toekomstige tooling (bv. een cloud-agent) er op kunnen bouwen.\n */\nimport { z } from \"zod\";\nimport { PageSchema, type Page } from \"../schema/pages\";\nimport { SymbolSchema, type Symbol } from \"../schema/symbols\";\nimport { CustomBlockSchema, type CustomBlock } from \"../schema/customBlocks\";\n\n/* ---------------- hashing ---------------- */\n\n/** Stabiele JSON-representatie (gesorteerde sleutels, zonder undefined) voor hashing. */\nexport function stableStringify(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(stableStringify).join(\",\")}]`;\n if (value && typeof value === \"object\") {\n const entries = Object.entries(value as Record<string, unknown>)\n .filter(([, v]) => v !== undefined)\n .sort(([a], [b]) => (a < b ? -1 : 1));\n return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`).join(\",\")}}`;\n }\n return JSON.stringify(value) ?? \"null\";\n}\n\n/** FNV-1a 64-bit (hex) — snel, dependency-vrij en client-safe (geen node:crypto). Dit is een\n * wijzigingsdetector, geen beveiligingshash. */\nexport function contentHash(value: unknown): string {\n const s = stableStringify(value);\n let h = 0xcbf29ce484222325n;\n const prime = 0x100000001b3n;\n for (let i = 0; i < s.length; i++) {\n h ^= BigInt(s.charCodeAt(i));\n h = (h * prime) & 0xffffffffffffffffn;\n }\n return h.toString(16).padStart(16, \"0\");\n}\n\n/* ---------------- manifest + sync-beslissing ---------------- */\n\nexport interface ManifestEntry {\n /** Relatief bestandspad binnen de site-map (bv. \"pages/diensten/web.yaml\"). */\n file: string;\n /** contentHash van de geserialiseerde entiteit op pull-moment (de \"basis\"). */\n hash: string;\n}\n\nexport interface Manifest {\n format: \"xicms-files\";\n version: 1;\n siteId: string;\n pulledAt: string;\n /** Sleutels: \"<soort>:<id>\", bv. \"page:abc123\", \"symbol:cta\", \"customBlock:custom-usps\". */\n entities: Record<string, ManifestEntry>;\n}\n\nexport function emptyManifest(siteId: string, pulledAt: string): Manifest {\n return { format: \"xicms-files\", version: 1, siteId, pulledAt, entities: {} };\n}\n\nexport type SyncAction = \"push\" | \"skip\" | \"conflict\" | \"create\" | \"remote-deleted\";\n\n/**\n * Drie-wegen-beslissing per entiteit:\n * - basis (manifest-hash van pull), lokaal (bestand nu), remote (database nu).\n * - lokaal ongewijzigd → skip (niets te pushen), ook als remote intussen veranderde.\n * - lokaal gewijzigd + remote ongewijzigd → push.\n * - beide gewijzigd → conflict (push weigert zonder --force).\n * - geen basis (nieuw bestand) → create; bestaat remote al ook → conflict.\n * - basis wel, remote weg → remote-deleted (conflict-achtig: iemand verwijderde de entiteit).\n */\nexport function decideSync(base: string | undefined, local: string, remote: string | undefined): SyncAction {\n if (base === undefined) return remote === undefined ? \"create\" : remote === local ? \"skip\" : \"conflict\";\n if (remote === undefined) return \"remote-deleted\";\n if (remote === local) return \"skip\"; // al identiek — niets te doen\n if (local === base) return \"skip\"; // lokaal niets gewijzigd — remote-wijziging laten staan\n if (remote === base) return \"push\"; // alleen lokaal gewijzigd\n return \"conflict\"; // beide kanten gewijzigd\n}\n\n/* ---------------- bestandsnamen ---------------- */\n\n/** Pagina-pad → relatief YAML-pad: \"/\" → \"pages/index.yaml\", \"/diensten/web\" → \"pages/diensten/web.yaml\". */\nexport function pagePathToFile(path: string): string {\n const clean = path.replace(/^\\/+|\\/+$/g, \"\");\n return clean === \"\" ? \"pages/index.yaml\" : `pages/${clean}.yaml`;\n}\n\n/** Veilige bestandsnaam voor een id (symbolen kunnen elk Firestore-id hebben). */\nexport function idToFilename(id: string): string {\n return id.replace(/[^a-zA-Z0-9_-]/g, \"_\");\n}\n\n/* ---------------- serialisatie (entiteit ⇄ plain object) ---------------- */\n\n/** Pagina-doc → bestandsobject met vaste, leesbare sleutelvolgorde. */\nexport function serializePage(page: Page & { id: string }): Record<string, unknown> {\n return {\n id: page.id,\n title: page.title,\n slug: page.slug,\n path: page.path,\n parentId: page.parentId ?? null,\n order: page.order ?? 0,\n ...(page.seo && Object.keys(page.seo).length > 0 ? { seo: page.seo } : {}),\n blocks: page.blocks ?? [],\n ...(page.translations && Object.keys(page.translations).length > 0 ? { translations: page.translations } : {}),\n };\n}\n\nconst PageFileSchema = PageSchema.extend({ id: z.string().min(1).max(64) });\n\n/** Bestandsobject → gevalideerde pagina. Gooit met een leesbare melding bij ongeldige inhoud. */\nexport function parsePageFile(obj: unknown): Page & { id: string } {\n const res = PageFileSchema.safeParse(obj);\n if (!res.success) {\n const i = res.error.issues[0];\n throw new Error(`ongeldige pagina: ${i?.path.join(\".\") || \"(root)\"}: ${i?.message}`);\n }\n return res.data;\n}\n\nexport function serializeSymbol(symbol: Symbol & { id: string }): Record<string, unknown> {\n return {\n id: symbol.id,\n name: symbol.name,\n blocks: symbol.blocks ?? [],\n ...(symbol.translations && Object.keys(symbol.translations).length > 0 ? { translations: symbol.translations } : {}),\n };\n}\n\nconst SymbolFileSchema = SymbolSchema.extend({ id: z.string().min(1).max(120) });\n\nexport function parseSymbolFile(obj: unknown): Symbol & { id: string } {\n const res = SymbolFileSchema.safeParse(obj);\n if (!res.success) {\n const i = res.error.issues[0];\n throw new Error(`ongeldig symbool: ${i?.path.join(\".\") || \"(root)\"}: ${i?.message}`);\n }\n return res.data;\n}\n\n/** Custom block → drie bestanden: block.yaml (meta + Field-IR), template.html, style.css. */\nexport function serializeCustomBlock(def: CustomBlock): { meta: Record<string, unknown>; template: string; css: string } {\n return {\n meta: { type: def.type, label: def.label, icon: def.icon, version: def.version, fields: def.fields },\n template: def.template,\n css: def.css,\n };\n}\n\nexport function parseCustomBlockFiles(meta: unknown, template: string, css: string): CustomBlock {\n const res = CustomBlockSchema.safeParse({ ...(meta as Record<string, unknown>), template, css });\n if (!res.success) {\n const i = res.error.issues[0];\n throw new Error(`ongeldige bouwsteen: ${i?.path.join(\".\") || \"(root)\"}: ${i?.message}`);\n }\n return res.data;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/agentFiles/files.ts"],"sourcesContent":["/**\n * Agent-bestandslaag (5.0, primo-concept \"DB ⇄ files\"): pure serialisatie-, hash- en\n * sync-logica voor de `pull-site`/`push-site`-scripts. Een site wordt een map met YAML/HTML-\n * bestanden die een coding-agent direct kan bewerken; `.xicms/manifest.json` onthoudt per\n * entiteit een inhouds-hash van het pull-moment zodat push conflicten kan detecteren\n * (drie-wegen: basis/lokaal/remote) in plaats van stil werk te overschrijven.\n *\n * Alleen pure functies — geen I/O, geen Firebase — zodat dit unit-testbaar is en zowel de\n * scripts als toekomstige tooling (bv. een cloud-agent) er op kunnen bouwen.\n */\nimport { z } from \"zod\";\nimport { PageSchema, type Page } from \"../schema/pages\";\nimport { SymbolSchema, type SymbolDefinition } from \"../schema/symbols\";\nimport { CustomBlockSchema, type CustomBlock } from \"../schema/customBlocks\";\nimport { FormSchema, type FormDefinition } from \"../schema/forms\";\nimport type { NewsArticle } from \"../content/types\";\n\n/* ---------------- hashing ---------------- */\n\n/** Stabiele JSON-representatie (gesorteerde sleutels, zonder undefined) voor hashing. */\nexport function stableStringify(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(stableStringify).join(\",\")}]`;\n if (value && typeof value === \"object\") {\n const entries = Object.entries(value as Record<string, unknown>)\n .filter(([, v]) => v !== undefined)\n .sort(([a], [b]) => (a < b ? -1 : 1));\n return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`).join(\",\")}}`;\n }\n return JSON.stringify(value) ?? \"null\";\n}\n\n/** FNV-1a 64-bit (hex) — snel, dependency-vrij en client-safe (geen node:crypto). Dit is een\n * wijzigingsdetector, geen beveiligingshash. */\nexport function contentHash(value: unknown): string {\n const s = stableStringify(value);\n let h = 0xcbf29ce484222325n;\n const prime = 0x100000001b3n;\n for (let i = 0; i < s.length; i++) {\n h ^= BigInt(s.charCodeAt(i));\n h = (h * prime) & 0xffffffffffffffffn;\n }\n return h.toString(16).padStart(16, \"0\");\n}\n\n/* ---------------- manifest + sync-beslissing ---------------- */\n\nexport interface ManifestEntry {\n /** Relatief bestandspad binnen de site-map (bv. \"pages/diensten/web.yaml\"). */\n file: string;\n /** contentHash van de geserialiseerde entiteit op pull-moment (de \"basis\"). */\n hash: string;\n}\n\nexport interface Manifest {\n format: \"xicms-files\";\n version: 1;\n siteId: string;\n pulledAt: string;\n /** Sleutels: \"<soort>:<id>\", bv. \"page:abc123\", \"symbol:cta\", \"customBlock:custom-usps\". */\n entities: Record<string, ManifestEntry>;\n}\n\nexport function emptyManifest(siteId: string, pulledAt: string): Manifest {\n return { format: \"xicms-files\", version: 1, siteId, pulledAt, entities: {} };\n}\n\nexport type SyncAction = \"push\" | \"skip\" | \"conflict\" | \"create\" | \"remote-deleted\";\n\n/**\n * Drie-wegen-beslissing per entiteit:\n * - basis (manifest-hash van pull), lokaal (bestand nu), remote (database nu).\n * - lokaal ongewijzigd → skip (niets te pushen), ook als remote intussen veranderde.\n * - lokaal gewijzigd + remote ongewijzigd → push.\n * - beide gewijzigd → conflict (push weigert zonder --force).\n * - geen basis (nieuw bestand) → create; bestaat remote al ook → conflict.\n * - basis wel, remote weg → remote-deleted (conflict-achtig: iemand verwijderde de entiteit).\n */\nexport function decideSync(base: string | undefined, local: string, remote: string | undefined): SyncAction {\n if (base === undefined) return remote === undefined ? \"create\" : remote === local ? \"skip\" : \"conflict\";\n if (remote === undefined) return \"remote-deleted\";\n if (remote === local) return \"skip\"; // al identiek — niets te doen\n if (local === base) return \"skip\"; // lokaal niets gewijzigd — remote-wijziging laten staan\n if (remote === base) return \"push\"; // alleen lokaal gewijzigd\n return \"conflict\"; // beide kanten gewijzigd\n}\n\n/* ---------------- bestandsnamen ---------------- */\n\n/** Pagina-pad → relatief YAML-pad: \"/\" → \"pages/index.yaml\", \"/diensten/web\" → \"pages/diensten/web.yaml\". */\nexport function pagePathToFile(path: string): string {\n const clean = path.replace(/^\\/+|\\/+$/g, \"\");\n return clean === \"\" ? \"pages/index.yaml\" : `pages/${clean}.yaml`;\n}\n\n/** Veilige bestandsnaam voor een id (symbolen kunnen elk Firestore-id hebben). */\nexport function idToFilename(id: string): string {\n return id.replace(/[^a-zA-Z0-9_-]/g, \"_\");\n}\n\n/* ---------------- serialisatie (entiteit ⇄ plain object) ---------------- */\n\n/** Pagina-doc → bestandsobject met vaste, leesbare sleutelvolgorde. */\nexport function serializePage(page: Page & { id: string }): Record<string, unknown> {\n return {\n id: page.id,\n title: page.title,\n slug: page.slug,\n path: page.path,\n parentId: page.parentId ?? null,\n order: page.order ?? 0,\n ...(page.seo && Object.keys(page.seo).length > 0 ? { seo: page.seo } : {}),\n blocks: page.blocks ?? [],\n ...(page.translations && Object.keys(page.translations).length > 0 ? { translations: page.translations } : {}),\n };\n}\n\nconst PageFileSchema = PageSchema.extend({ id: z.string().min(1).max(64) });\n\n/** Bestandsobject → gevalideerde pagina. Gooit met een leesbare melding bij ongeldige inhoud. */\nexport function parsePageFile(obj: unknown): Page & { id: string } {\n const res = PageFileSchema.safeParse(obj);\n if (!res.success) {\n const i = res.error.issues[0];\n throw new Error(`ongeldige pagina: ${i?.path.join(\".\") || \"(root)\"}: ${i?.message}`);\n }\n return res.data;\n}\n\nexport function serializeSymbol(symbol: SymbolDefinition & { id: string }): Record<string, unknown> {\n return {\n id: symbol.id,\n name: symbol.name,\n blocks: symbol.blocks ?? [],\n ...(symbol.translations && Object.keys(symbol.translations).length > 0 ? { translations: symbol.translations } : {}),\n };\n}\n\nconst SymbolFileSchema = SymbolSchema.extend({ id: z.string().min(1).max(120) });\n\nexport function parseSymbolFile(obj: unknown): SymbolDefinition & { id: string } {\n const res = SymbolFileSchema.safeParse(obj);\n if (!res.success) {\n const i = res.error.issues[0];\n throw new Error(`ongeldig symbool: ${i?.path.join(\".\") || \"(root)\"}: ${i?.message}`);\n }\n return res.data;\n}\n\n/** Custom block → drie bestanden: block.yaml (meta + Field-IR), template.html, style.css. */\nexport function serializeCustomBlock(def: CustomBlock): { meta: Record<string, unknown>; template: string; css: string } {\n return {\n meta: { type: def.type, label: def.label, icon: def.icon, version: def.version, fields: def.fields },\n template: def.template,\n css: def.css,\n };\n}\n\nexport function parseCustomBlockFiles(meta: unknown, template: string, css: string): CustomBlock {\n const res = CustomBlockSchema.safeParse({ ...(meta as Record<string, unknown>), template, css });\n if (!res.success) {\n const i = res.error.issues[0];\n throw new Error(`ongeldige bouwsteen: ${i?.path.join(\".\") || \"(root)\"}: ${i?.message}`);\n }\n return res.data;\n}\n\n/* ---------------- nieuws & formulieren (round-trip in pull/push) ---------------- */\n\n/**\n * Runtime-schema voor nieuwsartikelen in de bestandslaag. `NewsArticle` is elders alleen een\n * TS-interface (het beheer schrijft rechtstreeks naar Firestore); voor pull/push valideren we\n * wél — een agent-geschreven YAML met een kapotte datum of ontbrekende titel hoort luid te\n * falen, niet stil een kapot artikel te maken. `createdAt`/`updatedAt` (servertimestamps)\n * blijven bewust buiten de bestanden: die beheert Firestore.\n */\nconst NewsFileSchema = z.object({\n id: z.string().min(1).max(120),\n title: z.string().min(1).max(200),\n slug: z.string().min(1).max(200),\n excerpt: z.string().max(1000).optional(),\n body: z.string().max(50_000).default(\"\"),\n bodyHtml: z.string().max(200_000).optional(),\n coverUrl: z.string().max(2000).optional(),\n date: z.string().regex(/^\\d{4}-\\d{2}-\\d{2}$/, \"datum als yyyy-mm-dd\"),\n published: z.boolean().default(false),\n});\n\n/** Nieuws-doc → bestandsobject (vaste sleutelvolgorde, zonder servertimestamps/lege velden). */\nexport function serializeNews(a: NewsArticle): Record<string, unknown> {\n return {\n id: a.id,\n title: a.title,\n slug: a.slug,\n date: a.date,\n published: a.published,\n ...(a.excerpt ? { excerpt: a.excerpt } : {}),\n ...(a.coverUrl ? { coverUrl: a.coverUrl } : {}),\n body: a.body ?? \"\",\n ...(a.bodyHtml ? { bodyHtml: a.bodyHtml } : {}),\n };\n}\n\n/** Bestandsobject → gevalideerd nieuwsartikel. Gooit met een leesbare melding. */\nexport function parseNewsFile(obj: unknown): NewsArticle {\n const res = NewsFileSchema.safeParse(obj);\n if (!res.success) {\n const i = res.error.issues[0];\n throw new Error(`ongeldig nieuwsartikel: ${i?.path.join(\".\") || \"(root)\"}: ${i?.message}`);\n }\n return res.data;\n}\n\nexport function serializeForm(form: FormDefinition & { id: string }): Record<string, unknown> {\n return {\n id: form.id,\n name: form.name,\n submitLabel: form.submitLabel,\n successMessage: form.successMessage,\n ...(form.successRedirect ? { successRedirect: form.successRedirect } : {}),\n ...(form.confirmSubmitter !== undefined ? { confirmSubmitter: form.confirmSubmitter } : {}),\n fields: form.fields,\n };\n}\n\nconst FormFileSchema = FormSchema.extend({ id: z.string().min(1).max(120) });\n\nexport function parseFormFile(obj: unknown): FormDefinition & { id: string } {\n const res = FormFileSchema.safeParse(obj);\n if (!res.success) {\n const i = res.error.issues[0];\n throw new Error(`ongeldig formulier: ${i?.path.join(\".\") || \"(root)\"}: ${i?.message}`);\n }\n return res.data;\n}\n"],"mappings":"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","names":[]}
@@ -35,12 +35,12 @@ function AuthProvider({
35
35
  window.addEventListener(DEMO_EVENT, sync);
36
36
  return () => window.removeEventListener(DEMO_EVENT, sync);
37
37
  }, [demo]);
38
- async function loadClaims(u) {
38
+ async function loadClaims(u, forceRefresh = false) {
39
39
  if (!u) {
40
40
  setClaims(null);
41
41
  return;
42
42
  }
43
- const token = await u.getIdTokenResult(true);
43
+ const token = await u.getIdTokenResult(forceRefresh);
44
44
  setClaims({
45
45
  superadmin: token.claims.superadmin === true,
46
46
  roles: token.claims.roles ?? {}
@@ -88,7 +88,7 @@ function AuthProvider({
88
88
  if (auth) await fbSignOut(auth);
89
89
  },
90
90
  async refresh() {
91
- await loadClaims(getFirebaseAuth()?.currentUser ?? user);
91
+ await loadClaims(getFirebaseAuth()?.currentUser ?? user, true);
92
92
  },
93
93
  demo,
94
94
  demoSignIn() {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/auth/AuthProvider.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n createContext,\n useContext,\n useEffect,\n useMemo,\n useState,\n type ReactNode,\n} from \"react\";\nimport {\n GoogleAuthProvider,\n onAuthStateChanged,\n signInWithEmailAndPassword,\n signInWithPopup,\n signOut as fbSignOut,\n type User,\n} from \"firebase/auth\";\nimport { getFirebaseAuth, isFirebaseConfigured } from \"../firebase/client\";\nimport { roleForSite, type Role, type XiClaims } from \"./roles\";\nimport { DEMO_EVENT, isDemoLoggedIn, setDemoLoggedIn } from \"../demo/demoStore\";\n\ninterface AuthState {\n /** Of Firebase überhaupt geconfigureerd is. */\n configured: boolean;\n /** Bezig met laden van de auth-status. */\n loading: boolean;\n user: User | null;\n claims: XiClaims | null;\n /** Effectieve rol op de huidige site (uit `siteId`). */\n role: Role | null;\n signIn: (email: string, password: string) => Promise<void>;\n /** Inloggen met Google (popup). */\n signInWithGoogle: () => Promise<void>;\n signOut: () => Promise<void>;\n /** Forceer het opnieuw inlezen van claims (na een rolwijziging). */\n refresh: () => Promise<void>;\n /** Demo-bewerkmodus actief (config.demoEditing) — pseudo-login, niets wordt opgeslagen. */\n demo: boolean;\n /** Pseudo-login voor de demo: geeft lokaal een siteadmin-rol zonder Firebase. */\n demoSignIn: () => void;\n}\n\nconst AuthContext = createContext<AuthState | null>(null);\n\nexport function AuthProvider({\n siteId,\n demo = false,\n children,\n}: {\n siteId: string;\n demo?: boolean;\n children: ReactNode;\n}) {\n const configured = isFirebaseConfigured();\n const [loading, setLoading] = useState(configured);\n const [user, setUser] = useState<User | null>(null);\n const [claims, setClaims] = useState<XiClaims | null>(null);\n const [demoLoggedIn, setDemoLoggedInState] = useState(false);\n\n // Demo-login uit de (sessie)opslag inlezen + meeluisteren met wijzigingen (login/logout/reset).\n useEffect(() => {\n if (!demo) return;\n const sync = () => setDemoLoggedInState(isDemoLoggedIn());\n sync();\n window.addEventListener(DEMO_EVENT, sync);\n return () => window.removeEventListener(DEMO_EVENT, sync);\n }, [demo]);\n\n async function loadClaims(u: User | null) {\n if (!u) {\n setClaims(null);\n return;\n }\n const token = await u.getIdTokenResult(true);\n setClaims({\n superadmin: token.claims.superadmin === true,\n roles: (token.claims.roles as XiClaims[\"roles\"]) ?? {},\n });\n }\n\n useEffect(() => {\n const auth = getFirebaseAuth();\n if (!auth) {\n setLoading(false);\n return;\n }\n const unsub = onAuthStateChanged(auth, async (u) => {\n setUser(u);\n await loadClaims(u);\n setLoading(false);\n });\n return () => unsub();\n }, []);\n\n const value = useMemo<AuthState>(() => {\n // In de demo met actieve pseudo-login: doe alsof een siteadmin is ingelogd (lokaal, geen Firebase).\n const demoActiveLogin = demo && demoLoggedIn;\n const effectiveUser = demoActiveLogin\n ? ({ uid: \"demo\", email: \"demo@voorbeeld.nl\", displayName: \"Demo-beheerder\" } as unknown as User)\n : user;\n const effectiveClaims: XiClaims | null = demoActiveLogin ? { roles: { [siteId]: \"siteadmin\" } } : claims;\n\n return {\n configured,\n loading,\n user: effectiveUser,\n claims: effectiveClaims,\n role: roleForSite(effectiveClaims, siteId),\n async signIn(email, password) {\n const auth = getFirebaseAuth();\n if (!auth) throw new Error(\"Firebase is niet geconfigureerd.\");\n await signInWithEmailAndPassword(auth, email, password);\n },\n async signInWithGoogle() {\n const auth = getFirebaseAuth();\n if (!auth) throw new Error(\"Firebase is niet geconfigureerd.\");\n await signInWithPopup(auth, new GoogleAuthProvider());\n },\n async signOut() {\n if (demo) {\n setDemoLoggedIn(false);\n return;\n }\n const auth = getFirebaseAuth();\n if (auth) await fbSignOut(auth);\n },\n async refresh() {\n // Altijd de actuele Firebase-user (niet de gememoïseerde) — na een snelle\n // uitlog/inlog-wissel zou de closure anders claims van de vorige user verversen.\n await loadClaims(getFirebaseAuth()?.currentUser ?? user);\n },\n demo,\n demoSignIn() {\n setDemoLoggedIn(true);\n },\n };\n }, [configured, loading, user, claims, siteId, demo, demoLoggedIn]);\n\n return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;\n}\n\nexport function useAuth(): AuthState {\n const ctx = useContext(AuthContext);\n if (!ctx) throw new Error(\"useAuth moet binnen <XiCMSProvider> worden gebruikt.\");\n return ctx;\n}\n"],"mappings":";AA2IS;AAzIT;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,OAEN;AACP,SAAS,iBAAiB,4BAA4B;AACtD,SAAS,mBAA6C;AACtD,SAAS,YAAY,gBAAgB,uBAAuB;AAuB5D,MAAM,cAAc,cAAgC,IAAI;AAEjD,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA,OAAO;AAAA,EACP;AACF,GAIG;AACD,QAAM,aAAa,qBAAqB;AACxC,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,UAAU;AACjD,QAAM,CAAC,MAAM,OAAO,IAAI,SAAsB,IAAI;AAClD,QAAM,CAAC,QAAQ,SAAS,IAAI,SAA0B,IAAI;AAC1D,QAAM,CAAC,cAAc,oBAAoB,IAAI,SAAS,KAAK;AAG3D,YAAU,MAAM;AACd,QAAI,CAAC,KAAM;AACX,UAAM,OAAO,MAAM,qBAAqB,eAAe,CAAC;AACxD,SAAK;AACL,WAAO,iBAAiB,YAAY,IAAI;AACxC,WAAO,MAAM,OAAO,oBAAoB,YAAY,IAAI;AAAA,EAC1D,GAAG,CAAC,IAAI,CAAC;AAET,iBAAe,WAAW,GAAgB;AACxC,QAAI,CAAC,GAAG;AACN,gBAAU,IAAI;AACd;AAAA,IACF;AACA,UAAM,QAAQ,MAAM,EAAE,iBAAiB,IAAI;AAC3C,cAAU;AAAA,MACR,YAAY,MAAM,OAAO,eAAe;AAAA,MACxC,OAAQ,MAAM,OAAO,SAA+B,CAAC;AAAA,IACvD,CAAC;AAAA,EACH;AAEA,YAAU,MAAM;AACd,UAAM,OAAO,gBAAgB;AAC7B,QAAI,CAAC,MAAM;AACT,iBAAW,KAAK;AAChB;AAAA,IACF;AACA,UAAM,QAAQ,mBAAmB,MAAM,OAAO,MAAM;AAClD,cAAQ,CAAC;AACT,YAAM,WAAW,CAAC;AAClB,iBAAW,KAAK;AAAA,IAClB,CAAC;AACD,WAAO,MAAM,MAAM;AAAA,EACrB,GAAG,CAAC,CAAC;AAEL,QAAM,QAAQ,QAAmB,MAAM;AAErC,UAAM,kBAAkB,QAAQ;AAChC,UAAM,gBAAgB,kBACjB,EAAE,KAAK,QAAQ,OAAO,qBAAqB,aAAa,iBAAiB,IAC1E;AACJ,UAAM,kBAAmC,kBAAkB,EAAE,OAAO,EAAE,CAAC,MAAM,GAAG,YAAY,EAAE,IAAI;AAElG,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,MAAM,YAAY,iBAAiB,MAAM;AAAA,MACzC,MAAM,OAAO,OAAO,UAAU;AAC5B,cAAM,OAAO,gBAAgB;AAC7B,YAAI,CAAC,KAAM,OAAM,IAAI,MAAM,kCAAkC;AAC7D,cAAM,2BAA2B,MAAM,OAAO,QAAQ;AAAA,MACxD;AAAA,MACA,MAAM,mBAAmB;AACvB,cAAM,OAAO,gBAAgB;AAC7B,YAAI,CAAC,KAAM,OAAM,IAAI,MAAM,kCAAkC;AAC7D,cAAM,gBAAgB,MAAM,IAAI,mBAAmB,CAAC;AAAA,MACtD;AAAA,MACA,MAAM,UAAU;AACd,YAAI,MAAM;AACR,0BAAgB,KAAK;AACrB;AAAA,QACF;AACA,cAAM,OAAO,gBAAgB;AAC7B,YAAI,KAAM,OAAM,UAAU,IAAI;AAAA,MAChC;AAAA,MACA,MAAM,UAAU;AAGd,cAAM,WAAW,gBAAgB,GAAG,eAAe,IAAI;AAAA,MACzD;AAAA,MACA;AAAA,MACA,aAAa;AACX,wBAAgB,IAAI;AAAA,MACtB;AAAA,IACF;AAAA,EACF,GAAG,CAAC,YAAY,SAAS,MAAM,QAAQ,QAAQ,MAAM,YAAY,CAAC;AAElE,SAAO,oBAAC,YAAY,UAAZ,EAAqB,OAAe,UAAS;AACvD;AAEO,SAAS,UAAqB;AACnC,QAAM,MAAM,WAAW,WAAW;AAClC,MAAI,CAAC,IAAK,OAAM,IAAI,MAAM,sDAAsD;AAChF,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../../src/auth/AuthProvider.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n createContext,\n useContext,\n useEffect,\n useMemo,\n useState,\n type ReactNode,\n} from \"react\";\nimport {\n GoogleAuthProvider,\n onAuthStateChanged,\n signInWithEmailAndPassword,\n signInWithPopup,\n signOut as fbSignOut,\n type User,\n} from \"firebase/auth\";\nimport { getFirebaseAuth, isFirebaseConfigured } from \"../firebase/client\";\nimport { roleForSite, type Role, type XiClaims } from \"./roles\";\nimport { DEMO_EVENT, isDemoLoggedIn, setDemoLoggedIn } from \"../demo/demoStore\";\n\ninterface AuthState {\n /** Of Firebase überhaupt geconfigureerd is. */\n configured: boolean;\n /** Bezig met laden van de auth-status. */\n loading: boolean;\n user: User | null;\n claims: XiClaims | null;\n /** Effectieve rol op de huidige site (uit `siteId`). */\n role: Role | null;\n signIn: (email: string, password: string) => Promise<void>;\n /** Inloggen met Google (popup). */\n signInWithGoogle: () => Promise<void>;\n signOut: () => Promise<void>;\n /** Forceer het opnieuw inlezen van claims (na een rolwijziging). */\n refresh: () => Promise<void>;\n /** Demo-bewerkmodus actief (config.demoEditing) — pseudo-login, niets wordt opgeslagen. */\n demo: boolean;\n /** Pseudo-login voor de demo: geeft lokaal een siteadmin-rol zonder Firebase. */\n demoSignIn: () => void;\n}\n\nconst AuthContext = createContext<AuthState | null>(null);\n\nexport function AuthProvider({\n siteId,\n demo = false,\n children,\n}: {\n siteId: string;\n demo?: boolean;\n children: ReactNode;\n}) {\n const configured = isFirebaseConfigured();\n const [loading, setLoading] = useState(configured);\n const [user, setUser] = useState<User | null>(null);\n const [claims, setClaims] = useState<XiClaims | null>(null);\n const [demoLoggedIn, setDemoLoggedInState] = useState(false);\n\n // Demo-login uit de (sessie)opslag inlezen + meeluisteren met wijzigingen (login/logout/reset).\n useEffect(() => {\n if (!demo) return;\n const sync = () => setDemoLoggedInState(isDemoLoggedIn());\n sync();\n window.addEventListener(DEMO_EVENT, sync);\n return () => window.removeEventListener(DEMO_EVENT, sync);\n }, [demo]);\n\n async function loadClaims(u: User | null, forceRefresh = false) {\n if (!u) {\n setClaims(null);\n return;\n }\n // Standaard GEEN geforceerde token-refresh: dat is een netwerk-roundtrip op élke\n // auth-state-change (incl. eerste load van elke ingelogde editor). Alleen refresh() —\n // na een rolwijziging — forceert, want dan is het gecachte token per definitie verouderd.\n const token = await u.getIdTokenResult(forceRefresh);\n setClaims({\n superadmin: token.claims.superadmin === true,\n roles: (token.claims.roles as XiClaims[\"roles\"]) ?? {},\n });\n }\n\n useEffect(() => {\n const auth = getFirebaseAuth();\n if (!auth) {\n setLoading(false);\n return;\n }\n const unsub = onAuthStateChanged(auth, async (u) => {\n setUser(u);\n await loadClaims(u);\n setLoading(false);\n });\n return () => unsub();\n }, []);\n\n const value = useMemo<AuthState>(() => {\n // In de demo met actieve pseudo-login: doe alsof een siteadmin is ingelogd (lokaal, geen Firebase).\n const demoActiveLogin = demo && demoLoggedIn;\n const effectiveUser = demoActiveLogin\n ? ({ uid: \"demo\", email: \"demo@voorbeeld.nl\", displayName: \"Demo-beheerder\" } as unknown as User)\n : user;\n const effectiveClaims: XiClaims | null = demoActiveLogin ? { roles: { [siteId]: \"siteadmin\" } } : claims;\n\n return {\n configured,\n loading,\n user: effectiveUser,\n claims: effectiveClaims,\n role: roleForSite(effectiveClaims, siteId),\n async signIn(email, password) {\n const auth = getFirebaseAuth();\n if (!auth) throw new Error(\"Firebase is niet geconfigureerd.\");\n await signInWithEmailAndPassword(auth, email, password);\n },\n async signInWithGoogle() {\n const auth = getFirebaseAuth();\n if (!auth) throw new Error(\"Firebase is niet geconfigureerd.\");\n await signInWithPopup(auth, new GoogleAuthProvider());\n },\n async signOut() {\n if (demo) {\n setDemoLoggedIn(false);\n return;\n }\n const auth = getFirebaseAuth();\n if (auth) await fbSignOut(auth);\n },\n async refresh() {\n // Altijd de actuele Firebase-user (niet de gememoïseerde) — na een snelle\n // uitlog/inlog-wissel zou de closure anders claims van de vorige user verversen.\n // Force-refresh: na een rolwijziging is het gecachte token verouderd.\n await loadClaims(getFirebaseAuth()?.currentUser ?? user, true);\n },\n demo,\n demoSignIn() {\n setDemoLoggedIn(true);\n },\n };\n }, [configured, loading, user, claims, siteId, demo, demoLoggedIn]);\n\n return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;\n}\n\nexport function useAuth(): AuthState {\n const ctx = useContext(AuthContext);\n if (!ctx) throw new Error(\"useAuth moet binnen <XiCMSProvider> worden gebruikt.\");\n return ctx;\n}\n"],"mappings":"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","names":[]}
@@ -6,7 +6,7 @@ import { BlockRenderer } from "../BlockRenderer";
6
6
  import { useRegistry } from "../context";
7
7
  function ColumnsRender({ block, siteId, mode }) {
8
8
  const registry = useRegistry();
9
- return /* @__PURE__ */ jsx("div", { className: "xicms-columns", style: { gridTemplateColumns: `repeat(${block.columns.length}, 1fr)` }, children: block.columns.map((col, idx) => /* @__PURE__ */ jsx("div", { className: "xicms-column", children: /* @__PURE__ */ jsx(BlockRenderer, { blocks: col.blocks, registry, siteId, mode }) }, idx)) });
9
+ return /* @__PURE__ */ jsx("div", { className: "xicms-columns", "data-cols": block.columns.length, style: { gridTemplateColumns: `repeat(${block.columns.length}, 1fr)` }, children: block.columns.map((col, idx) => /* @__PURE__ */ jsx("div", { className: "xicms-column", children: /* @__PURE__ */ jsx(BlockRenderer, { blocks: col.blocks, registry, siteId, mode }) }, idx)) });
10
10
  }
11
11
  const columnsBlock = defineBlock({
12
12
  type: "columns",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/blocks/builtins/Columns.tsx"],"sourcesContent":["\"use client\";\n\nimport type { z } from \"zod\";\nimport { ColumnsBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { BlockRenderer } from \"../BlockRenderer\";\nimport { useRegistry } from \"../context\";\n\ntype Columns = z.infer<typeof ColumnsBlockSchema>;\n\nfunction ColumnsRender({ block, siteId, mode }: BlockRenderProps<Columns>) {\n const registry = useRegistry();\n return (\n <div className=\"xicms-columns\" style={{ gridTemplateColumns: `repeat(${block.columns.length}, 1fr)` }}>\n {block.columns.map((col, idx) => (\n <div key={idx} className=\"xicms-column\">\n <BlockRenderer blocks={col.blocks} registry={registry} siteId={siteId} mode={mode} />\n </div>\n ))}\n </div>\n );\n}\n\nexport const columnsBlock = defineBlock({\n type: \"columns\",\n schema: ColumnsBlockSchema,\n Render: ColumnsRender,\n label: \"Kolommen\",\n icon: \"▥\",\n defaults: () => ({ columns: [{ blocks: [] }, { blocks: [] }] }),\n});\n"],"mappings":";AAgBU;AAbV,SAAS,0BAA0B;AACnC,SAAS,mBAA0C;AACnD,SAAS,qBAAqB;AAC9B,SAAS,mBAAmB;AAI5B,SAAS,cAAc,EAAE,OAAO,QAAQ,KAAK,GAA8B;AACzE,QAAM,WAAW,YAAY;AAC7B,SACE,oBAAC,SAAI,WAAU,iBAAgB,OAAO,EAAE,qBAAqB,UAAU,MAAM,QAAQ,MAAM,SAAS,GACjG,gBAAM,QAAQ,IAAI,CAAC,KAAK,QACvB,oBAAC,SAAc,WAAU,gBACvB,8BAAC,iBAAc,QAAQ,IAAI,QAAQ,UAAoB,QAAgB,MAAY,KAD3E,GAEV,CACD,GACH;AAEJ;AAEO,MAAM,eAAe,YAAY;AAAA,EACtC,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,OAAO,EAAE,SAAS,CAAC,EAAE,QAAQ,CAAC,EAAE,GAAG,EAAE,QAAQ,CAAC,EAAE,CAAC,EAAE;AAC/D,CAAC;","names":[]}
1
+ {"version":3,"sources":["../../../src/blocks/builtins/Columns.tsx"],"sourcesContent":["\"use client\";\n\nimport type { z } from \"zod\";\nimport { ColumnsBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { BlockRenderer } from \"../BlockRenderer\";\nimport { useRegistry } from \"../context\";\n\ntype Columns = z.infer<typeof ColumnsBlockSchema>;\n\nfunction ColumnsRender({ block, siteId, mode }: BlockRenderProps<Columns>) {\n const registry = useRegistry();\n return (\n <div className=\"xicms-columns\" data-cols={block.columns.length} style={{ gridTemplateColumns: `repeat(${block.columns.length}, 1fr)` }}>\n {block.columns.map((col, idx) => (\n <div key={idx} className=\"xicms-column\">\n <BlockRenderer blocks={col.blocks} registry={registry} siteId={siteId} mode={mode} />\n </div>\n ))}\n </div>\n );\n}\n\nexport const columnsBlock = defineBlock({\n type: \"columns\",\n schema: ColumnsBlockSchema,\n Render: ColumnsRender,\n label: \"Kolommen\",\n icon: \"▥\",\n defaults: () => ({ columns: [{ blocks: [] }, { blocks: [] }] }),\n});\n"],"mappings":";AAgBU;AAbV,SAAS,0BAA0B;AACnC,SAAS,mBAA0C;AACnD,SAAS,qBAAqB;AAC9B,SAAS,mBAAmB;AAI5B,SAAS,cAAc,EAAE,OAAO,QAAQ,KAAK,GAA8B;AACzE,QAAM,WAAW,YAAY;AAC7B,SACE,oBAAC,SAAI,WAAU,iBAAgB,aAAW,MAAM,QAAQ,QAAQ,OAAO,EAAE,qBAAqB,UAAU,MAAM,QAAQ,MAAM,SAAS,GAClI,gBAAM,QAAQ,IAAI,CAAC,KAAK,QACvB,oBAAC,SAAc,WAAU,gBACvB,8BAAC,iBAAc,QAAQ,IAAI,QAAQ,UAAoB,QAAgB,MAAY,KAD3E,GAEV,CACD,GACH;AAEJ;AAEO,MAAM,eAAe,YAAY;AAAA,EACtC,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,OAAO,EAAE,SAAS,CAAC,EAAE,QAAQ,CAAC,EAAE,GAAG,EAAE,QAAQ,CAAC,EAAE,CAAC,EAAE;AAC/D,CAAC;","names":[]}
@@ -109,7 +109,7 @@ function LightboxOverlay({
109
109
  }
110
110
  function GalleryRender({ block }) {
111
111
  const settings = useModuleSettings("gallery");
112
- const allItems = (block.items ?? []).filter((i) => i.url);
112
+ const allItems = (block.items ?? []).map((it, _i) => ({ ...it, _i })).filter((i) => i.url);
113
113
  const [open, setOpen] = useState(false);
114
114
  const [index, setIndex] = useState(0);
115
115
  const [activeTag, setActiveTag] = useState(null);
@@ -157,7 +157,7 @@ function GalleryRender({ block }) {
157
157
  children: /* @__PURE__ */ jsx(Img, { src: i.url, alt: i.alt ?? "", width: 600, height: 600, sizes: "(max-width: 640px) 50vw, 280px", style: imgStyle })
158
158
  }
159
159
  ) : /* @__PURE__ */ jsx(Img, { src: i.url, alt: i.alt ?? "", width: 600, height: 600, sizes: "(max-width: 640px) 50vw, 280px", style: imgStyle }),
160
- i.caption && /* @__PURE__ */ jsx("figcaption", { className: "xicms-gallery__caption", style: { marginTop: "0.4rem", fontSize: "0.85rem", color: "var(--color-text-secondary)" }, children: i.caption })
160
+ i.caption && /* @__PURE__ */ jsx("figcaption", { className: "xicms-gallery__caption", "data-xicms-edit": `items.${i._i}.caption`, style: { marginTop: "0.4rem", fontSize: "0.85rem", color: "var(--color-text-secondary)" }, children: i.caption })
161
161
  ] }, idx)) }),
162
162
  lightbox && open && /* @__PURE__ */ jsx(LightboxOverlay, { items, index, onIndex: setIndex, onClose: close })
163
163
  ] });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/blocks/builtins/Gallery.tsx"],"sourcesContent":["\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport type { z } from \"zod\";\nimport { GalleryBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { useModuleSettings } from \"../../modules/context\";\nimport { Grid } from \"../ui\";\nimport { Img } from \"../../media/ImageContext\";\n\ntype Gallery = z.infer<typeof GalleryBlockSchema>;\ntype Item = Gallery[\"items\"][number];\n\n/** Per-site instelbaar via `modules.gallery.config` (door de agent of in /beheer). */\nexport interface GallerySettings {\n columns?: number;\n lightbox?: boolean;\n}\n\n/**\n * Eigen lightbox-overlay: rendert via een portal naar `document.body` (zo raakt geen enkele\n * ancestor-transform — bv. de scroll-reveal — de full-screen positionering). Sluit op klik náást\n * de foto én op Escape; vorige/volgende met teller; herstelt focus zónder de pagina te laten\n * verspringen (`preventScroll`) en zet de body-scroll vast zolang hij open is.\n */\nexport function LightboxOverlay({\n items,\n index,\n onIndex,\n onClose,\n}: {\n items: Item[];\n index: number;\n onIndex: (i: number) => void;\n onClose: () => void;\n}) {\n const it = items[index];\n const total = items.length;\n const overlayRef = useRef<HTMLDivElement | null>(null);\n const prev = useCallback(() => onIndex((index - 1 + total) % total), [index, total, onIndex]);\n const next = useCallback(() => onIndex((index + 1) % total), [index, total, onIndex]);\n // Swipe (pointer events, werkt voor touch én muis-sleep): horizontaal > 40px = vorige/volgende.\n const swipeStart = useRef<number | null>(null);\n const onPointerDown = (e: React.PointerEvent) => {\n swipeStart.current = e.clientX;\n };\n const onPointerUp = (e: React.PointerEvent) => {\n if (swipeStart.current === null || total < 2) return;\n const dx = e.clientX - swipeStart.current;\n swipeStart.current = null;\n if (dx > 40) prev();\n else if (dx < -40) next();\n };\n\n useEffect(() => {\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") onClose();\n else if (e.key === \"ArrowLeft\") prev();\n else if (e.key === \"ArrowRight\") next();\n else if (e.key === \"Tab\") {\n // Focus-trap (aria-modal): Tab cirkelt binnen de dialog i.p.v. naar de pagina eronder.\n const el = overlayRef.current;\n const focusables = el ? [...el.querySelectorAll<HTMLElement>(\"button, a[href]\")] : [];\n if (focusables.length === 0) return;\n const first = focusables[0];\n const last = focusables[focusables.length - 1];\n const active = document.activeElement;\n if (e.shiftKey && (active === first || !el?.contains(active))) {\n e.preventDefault();\n last.focus();\n } else if (!e.shiftKey && (active === last || !el?.contains(active))) {\n e.preventDefault();\n first.focus();\n }\n }\n };\n document.addEventListener(\"keydown\", onKey);\n const prevOverflow = document.body.style.overflow;\n document.body.style.overflow = \"hidden\"; // scroll-lock terwijl open\n // Initiële focus in de dialog (dialog-conventie; toetsenbord/screenreader landt niet \"achter\" de overlay).\n overlayRef.current?.querySelector<HTMLElement>(\".xicms-lightbox__close\")?.focus({ preventScroll: true });\n return () => {\n document.removeEventListener(\"keydown\", onKey);\n document.body.style.overflow = prevOverflow;\n };\n }, [onClose, prev, next]);\n\n if (typeof document === \"undefined\") return null;\n return createPortal(\n <div\n ref={overlayRef}\n className=\"xicms-lightbox\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={it.alt || it.caption || \"Foto\"}\n onClick={onClose}\n >\n <button type=\"button\" className=\"xicms-lightbox__close\" aria-label=\"Sluiten\" onClick={onClose}>✕</button>\n {total > 1 && (\n <>\n <button type=\"button\" className=\"xicms-lightbox__nav xicms-lightbox__nav--prev\" aria-label=\"Vorige\" onClick={(e) => { e.stopPropagation(); prev(); }}>‹</button>\n <button type=\"button\" className=\"xicms-lightbox__nav xicms-lightbox__nav--next\" aria-label=\"Volgende\" onClick={(e) => { e.stopPropagation(); next(); }}>›</button>\n <span className=\"xicms-lightbox__counter\">{index + 1} / {total}</span>\n </>\n )}\n {/* Klik op de foto/onderschrift sluit NIET (stopPropagation); klik op de rand wel.\n touchAction pan-y: verticaal scrollen blijft van de browser, horizontaal is swipen. */}\n <figure\n className=\"xicms-lightbox__figure\"\n onClick={(e) => e.stopPropagation()}\n onPointerDown={onPointerDown}\n onPointerUp={onPointerUp}\n style={{ touchAction: \"pan-y\" }}\n >\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img src={it.url} alt={it.alt ?? \"\"} />\n {it.caption && <figcaption>{it.caption}</figcaption>}\n </figure>\n </div>,\n document.body,\n );\n}\n\n/** Galerij — responsive grid met instelbare beeldverhouding en optionele lightbox. */\nfunction GalleryRender({ block }: BlockRenderProps<Gallery>) {\n const settings = useModuleSettings<GallerySettings>(\"gallery\");\n const allItems = (block.items ?? []).filter((i) => i.url);\n const [open, setOpen] = useState(false);\n const [index, setIndex] = useState(0);\n const [activeTag, setActiveTag] = useState<string | null>(null);\n const triggerRef = useRef<HTMLButtonElement | null>(null);\n\n // Filterbalk op item-tags (opt-in): lightbox-navigatie volgt de gefilterde lijst.\n const tags = block.showFilter ? [...new Set(allItems.map((i) => i.tag).filter((t): t is string => Boolean(t)))] : [];\n const items = activeTag ? allItems.filter((i) => i.tag === activeTag) : allItems;\n\n if (allItems.length === 0) return null;\n\n const cols = block.columns && block.columns > 0 ? block.columns : settings.columns ?? 3;\n const lightbox =\n block.lightbox === \"on\" ? true : block.lightbox === \"off\" ? false : settings.lightbox ?? true;\n const ar = block.aspectRatio ?? \"1/1\";\n\n const imgStyle = {\n width: \"100%\",\n aspectRatio: ar === \"auto\" ? undefined : ar.replace(\"/\", \" / \"),\n objectFit: \"cover\" as const,\n borderRadius: \"var(--radius-element)\",\n display: \"block\",\n };\n\n const close = () => {\n setOpen(false);\n triggerRef.current?.focus({ preventScroll: true }); // geen page-jump bij sluiten\n };\n\n return (\n <div className=\"xicms-gallery\">\n {tags.length > 0 && (\n <div className=\"xicms-gallery__filter\" role=\"group\" aria-label=\"Filter op categorie\">\n <button type=\"button\" className=\"xicms-gallery__filterbtn\" aria-pressed={activeTag === null} data-active={activeTag === null ? \"1\" : \"0\"} onClick={() => { setActiveTag(null); setIndex(0); }}>\n Alles\n </button>\n {tags.map((t) => (\n <button key={t} type=\"button\" className=\"xicms-gallery__filterbtn\" aria-pressed={activeTag === t} data-active={activeTag === t ? \"1\" : \"0\"} onClick={() => { setActiveTag(t); setIndex(0); }}>\n {t}\n </button>\n ))}\n </div>\n )}\n <Grid columns={{ minWidth: 160, max: Math.min(cols, 6) }} gap={2}>\n {items.map((i, idx) => (\n <figure key={idx} style={{ margin: 0 }} className=\"xicms-gallery__item\">\n {lightbox ? (\n <button\n type=\"button\"\n onClick={(e) => { triggerRef.current = e.currentTarget; setIndex(idx); setOpen(true); }}\n aria-label={i.alt || i.caption || `Foto ${idx + 1} vergroten`}\n style={{ display: \"block\", width: \"100%\", padding: 0, border: 0, background: \"none\", cursor: \"zoom-in\" }}\n >\n <Img src={i.url as string} alt={i.alt ?? \"\"} width={600} height={600} sizes=\"(max-width: 640px) 50vw, 280px\" style={imgStyle} />\n </button>\n ) : (\n <Img src={i.url as string} alt={i.alt ?? \"\"} width={600} height={600} sizes=\"(max-width: 640px) 50vw, 280px\" style={imgStyle} />\n )}\n {i.caption && (\n <figcaption className=\"xicms-gallery__caption\" style={{ marginTop: \"0.4rem\", fontSize: \"0.85rem\", color: \"var(--color-text-secondary)\" }}>\n {i.caption}\n </figcaption>\n )}\n </figure>\n ))}\n </Grid>\n\n {lightbox && open && (\n <LightboxOverlay items={items} index={index} onIndex={setIndex} onClose={close} />\n )}\n </div>\n );\n}\n\nexport const galleryBlock = defineBlock({\n type: \"gallery\",\n schema: GalleryBlockSchema,\n Render: GalleryRender,\n label: \"Galerij\",\n icon: \"🖼\",\n defaults: () => ({ items: [], columns: 0, lightbox: \"inherit\", aspectRatio: \"1/1\" }),\n});\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/blocks/builtins/Gallery.tsx"],"sourcesContent":["\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport type { z } from \"zod\";\nimport { GalleryBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { useModuleSettings } from \"../../modules/context\";\nimport { Grid } from \"../ui\";\nimport { Img } from \"../../media/ImageContext\";\n\ntype Gallery = z.infer<typeof GalleryBlockSchema>;\ntype Item = Gallery[\"items\"][number];\n\n/** Per-site instelbaar via `modules.gallery.config` (door de agent of in /beheer). */\nexport interface GallerySettings {\n columns?: number;\n lightbox?: boolean;\n}\n\n/**\n * Eigen lightbox-overlay: rendert via een portal naar `document.body` (zo raakt geen enkele\n * ancestor-transform — bv. de scroll-reveal — de full-screen positionering). Sluit op klik náást\n * de foto én op Escape; vorige/volgende met teller; herstelt focus zónder de pagina te laten\n * verspringen (`preventScroll`) en zet de body-scroll vast zolang hij open is.\n */\nexport function LightboxOverlay({\n items,\n index,\n onIndex,\n onClose,\n}: {\n items: Item[];\n index: number;\n onIndex: (i: number) => void;\n onClose: () => void;\n}) {\n const it = items[index];\n const total = items.length;\n const overlayRef = useRef<HTMLDivElement | null>(null);\n const prev = useCallback(() => onIndex((index - 1 + total) % total), [index, total, onIndex]);\n const next = useCallback(() => onIndex((index + 1) % total), [index, total, onIndex]);\n // Swipe (pointer events, werkt voor touch én muis-sleep): horizontaal > 40px = vorige/volgende.\n const swipeStart = useRef<number | null>(null);\n const onPointerDown = (e: React.PointerEvent) => {\n swipeStart.current = e.clientX;\n };\n const onPointerUp = (e: React.PointerEvent) => {\n if (swipeStart.current === null || total < 2) return;\n const dx = e.clientX - swipeStart.current;\n swipeStart.current = null;\n if (dx > 40) prev();\n else if (dx < -40) next();\n };\n\n useEffect(() => {\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") onClose();\n else if (e.key === \"ArrowLeft\") prev();\n else if (e.key === \"ArrowRight\") next();\n else if (e.key === \"Tab\") {\n // Focus-trap (aria-modal): Tab cirkelt binnen de dialog i.p.v. naar de pagina eronder.\n const el = overlayRef.current;\n const focusables = el ? [...el.querySelectorAll<HTMLElement>(\"button, a[href]\")] : [];\n if (focusables.length === 0) return;\n const first = focusables[0];\n const last = focusables[focusables.length - 1];\n const active = document.activeElement;\n if (e.shiftKey && (active === first || !el?.contains(active))) {\n e.preventDefault();\n last.focus();\n } else if (!e.shiftKey && (active === last || !el?.contains(active))) {\n e.preventDefault();\n first.focus();\n }\n }\n };\n document.addEventListener(\"keydown\", onKey);\n const prevOverflow = document.body.style.overflow;\n document.body.style.overflow = \"hidden\"; // scroll-lock terwijl open\n // Initiële focus in de dialog (dialog-conventie; toetsenbord/screenreader landt niet \"achter\" de overlay).\n overlayRef.current?.querySelector<HTMLElement>(\".xicms-lightbox__close\")?.focus({ preventScroll: true });\n return () => {\n document.removeEventListener(\"keydown\", onKey);\n document.body.style.overflow = prevOverflow;\n };\n }, [onClose, prev, next]);\n\n if (typeof document === \"undefined\") return null;\n return createPortal(\n <div\n ref={overlayRef}\n className=\"xicms-lightbox\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={it.alt || it.caption || \"Foto\"}\n onClick={onClose}\n >\n <button type=\"button\" className=\"xicms-lightbox__close\" aria-label=\"Sluiten\" onClick={onClose}>✕</button>\n {total > 1 && (\n <>\n <button type=\"button\" className=\"xicms-lightbox__nav xicms-lightbox__nav--prev\" aria-label=\"Vorige\" onClick={(e) => { e.stopPropagation(); prev(); }}>‹</button>\n <button type=\"button\" className=\"xicms-lightbox__nav xicms-lightbox__nav--next\" aria-label=\"Volgende\" onClick={(e) => { e.stopPropagation(); next(); }}>›</button>\n <span className=\"xicms-lightbox__counter\">{index + 1} / {total}</span>\n </>\n )}\n {/* Klik op de foto/onderschrift sluit NIET (stopPropagation); klik op de rand wel.\n touchAction pan-y: verticaal scrollen blijft van de browser, horizontaal is swipen. */}\n <figure\n className=\"xicms-lightbox__figure\"\n onClick={(e) => e.stopPropagation()}\n onPointerDown={onPointerDown}\n onPointerUp={onPointerUp}\n style={{ touchAction: \"pan-y\" }}\n >\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img src={it.url} alt={it.alt ?? \"\"} />\n {it.caption && <figcaption>{it.caption}</figcaption>}\n </figure>\n </div>,\n document.body,\n );\n}\n\n/** Galerij — responsive grid met instelbare beeldverhouding en optionele lightbox. */\nfunction GalleryRender({ block }: BlockRenderProps<Gallery>) {\n const settings = useModuleSettings<GallerySettings>(\"gallery\");\n // `_i` = de index in block.items (vóór url-/tagfilters): het inline-edit-pad\n // (`items.<_i>.caption`) moet naar het ORIGINELE array-item wijzen, niet naar de filterpositie.\n const allItems = (block.items ?? []).map((it, _i) => ({ ...it, _i })).filter((i) => i.url);\n const [open, setOpen] = useState(false);\n const [index, setIndex] = useState(0);\n const [activeTag, setActiveTag] = useState<string | null>(null);\n const triggerRef = useRef<HTMLButtonElement | null>(null);\n\n // Filterbalk op item-tags (opt-in): lightbox-navigatie volgt de gefilterde lijst.\n const tags = block.showFilter ? [...new Set(allItems.map((i) => i.tag).filter((t): t is string => Boolean(t)))] : [];\n const items = activeTag ? allItems.filter((i) => i.tag === activeTag) : allItems;\n\n if (allItems.length === 0) return null;\n\n const cols = block.columns && block.columns > 0 ? block.columns : settings.columns ?? 3;\n const lightbox =\n block.lightbox === \"on\" ? true : block.lightbox === \"off\" ? false : settings.lightbox ?? true;\n const ar = block.aspectRatio ?? \"1/1\";\n\n const imgStyle = {\n width: \"100%\",\n aspectRatio: ar === \"auto\" ? undefined : ar.replace(\"/\", \" / \"),\n objectFit: \"cover\" as const,\n borderRadius: \"var(--radius-element)\",\n display: \"block\",\n };\n\n const close = () => {\n setOpen(false);\n triggerRef.current?.focus({ preventScroll: true }); // geen page-jump bij sluiten\n };\n\n return (\n <div className=\"xicms-gallery\">\n {tags.length > 0 && (\n <div className=\"xicms-gallery__filter\" role=\"group\" aria-label=\"Filter op categorie\">\n <button type=\"button\" className=\"xicms-gallery__filterbtn\" aria-pressed={activeTag === null} data-active={activeTag === null ? \"1\" : \"0\"} onClick={() => { setActiveTag(null); setIndex(0); }}>\n Alles\n </button>\n {tags.map((t) => (\n <button key={t} type=\"button\" className=\"xicms-gallery__filterbtn\" aria-pressed={activeTag === t} data-active={activeTag === t ? \"1\" : \"0\"} onClick={() => { setActiveTag(t); setIndex(0); }}>\n {t}\n </button>\n ))}\n </div>\n )}\n <Grid columns={{ minWidth: 160, max: Math.min(cols, 6) }} gap={2}>\n {items.map((i, idx) => (\n <figure key={idx} style={{ margin: 0 }} className=\"xicms-gallery__item\">\n {lightbox ? (\n <button\n type=\"button\"\n onClick={(e) => { triggerRef.current = e.currentTarget; setIndex(idx); setOpen(true); }}\n aria-label={i.alt || i.caption || `Foto ${idx + 1} vergroten`}\n style={{ display: \"block\", width: \"100%\", padding: 0, border: 0, background: \"none\", cursor: \"zoom-in\" }}\n >\n <Img src={i.url as string} alt={i.alt ?? \"\"} width={600} height={600} sizes=\"(max-width: 640px) 50vw, 280px\" style={imgStyle} />\n </button>\n ) : (\n <Img src={i.url as string} alt={i.alt ?? \"\"} width={600} height={600} sizes=\"(max-width: 640px) 50vw, 280px\" style={imgStyle} />\n )}\n {i.caption && (\n <figcaption className=\"xicms-gallery__caption\" data-xicms-edit={`items.${i._i}.caption`} style={{ marginTop: \"0.4rem\", fontSize: \"0.85rem\", color: \"var(--color-text-secondary)\" }}>\n {i.caption}\n </figcaption>\n )}\n </figure>\n ))}\n </Grid>\n\n {lightbox && open && (\n <LightboxOverlay items={items} index={index} onIndex={setIndex} onClose={close} />\n )}\n </div>\n );\n}\n\nexport const galleryBlock = defineBlock({\n type: \"gallery\",\n schema: GalleryBlockSchema,\n Render: GalleryRender,\n label: \"Galerij\",\n icon: \"🖼\",\n defaults: () => ({ items: [], columns: 0, lightbox: \"inherit\", aspectRatio: \"1/1\" }),\n});\n"],"mappings":"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","names":[]}
@@ -54,7 +54,7 @@ function HeroRender({ block }) {
54
54
  block.overlayGradient && coverMedia && /* @__PURE__ */ jsx("div", { className: "xicms-hero__gradient", "aria-hidden": "true" }),
55
55
  /* @__PURE__ */ jsxs("div", { className: "xicms-hero__inner", children: [
56
56
  block.eyebrow && /* @__PURE__ */ jsx(Text, { type: "label", className: "xicms-hero__eyebrow", style: light, "data-xicms-edit": "eyebrow", children: block.eyebrow }),
57
- block.title && /* @__PURE__ */ jsx(Heading, { level: 1, type: "display-1", className: "xicms-hero__title", style: light, "data-xicms-edit": "title", children: block.title }),
57
+ block.title && /* @__PURE__ */ jsx(Heading, { level: block.headingLevel === "2" ? 2 : 1, type: "display-1", className: "xicms-hero__title", style: light, "data-xicms-edit": "title", children: block.title }),
58
58
  block.subtitle && /* @__PURE__ */ jsx(Text, { type: "large", className: "xicms-hero__sub", style: light, "data-xicms-edit": "subtitle", children: block.subtitle }),
59
59
  buttons.length > 0 && /* @__PURE__ */ jsx("div", { className: "xicms-hero__buttons", children: buttons.filter((b) => b.label).map((b, i) => /* @__PURE__ */ jsx(Btn, { label: b.label, href: b.href, variant: b.variant, size: "lg" }, i)) })
60
60
  ] }),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/blocks/builtins/Hero.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useRef, useState, type CSSProperties } from \"react\";\nimport type { z } from \"zod\";\nimport { HeroBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { Btn, Heading, Text } from \"../ui\";\nimport { Img } from \"../../media/ImageContext\";\n\ntype Hero = z.infer<typeof HeroBlockSchema>;\n\nfunction HeroRender({ block }: BlockRenderProps<Hero>) {\n // Achtergrondvideo WCAG-proof (2.2.2): geen autoPlay-attribuut (hydration-veilig) — playback\n // start in een effect, en alléén zonder reduced-motion-voorkeur. Altijd een pauzeknop, en\n // imageUrl dient als poster (direct beeld i.p.v. zwart vlak → betere LCP).\n const videoRef = useRef<HTMLVideoElement>(null);\n const [playing, setPlaying] = useState(false);\n useEffect(() => {\n const v = videoRef.current;\n if (!v) return;\n if (window.matchMedia?.(\"(prefers-reduced-motion: reduce)\").matches) return;\n v.play().then(() => setPlaying(true)).catch(() => undefined);\n }, [block.videoUrl]);\n const toggleVideo = () => {\n const v = videoRef.current;\n if (!v) return;\n if (v.paused) v.play().then(() => setPlaying(true)).catch(() => undefined);\n else {\n v.pause();\n setPlaying(false);\n }\n };\n const overlay =\n block.overlayOpacity > 0\n ? { background: block.overlayColor ?? \"#000000\", opacity: block.overlayOpacity / 100 }\n : null;\n // Beeld/video als dekkende achtergrond (niet bij height \"auto\"): dan moet de tekst licht zijn.\n const coverMedia = Boolean(block.videoUrl || block.imageUrl) && block.minHeight !== \"auto\";\n const light: CSSProperties | undefined = coverMedia\n ? { color: \"#ffffff\", textShadow: \"0 2px 14px rgba(0,0,0,.45)\" }\n : undefined;\n const buttons = block.buttons ?? [];\n\n return (\n <section\n className=\"xicms-hero\"\n data-align={block.align}\n data-valign={block.vAlign}\n data-height={block.minHeight}\n data-media={coverMedia ? \"cover\" : undefined}\n >\n {block.videoUrl ? (\n <video\n ref={videoRef}\n className=\"xicms-hero__media\"\n src={block.videoUrl}\n poster={block.imageUrl || undefined}\n muted\n loop\n playsInline\n preload=\"metadata\"\n aria-hidden=\"true\"\n />\n ) : block.imageUrl ? (\n <Img src={block.imageUrl} alt=\"\" className=\"xicms-hero__media\" width={1600} height={900} sizes=\"100vw\" priority editField=\"imageUrl\" editFill />\n ) : null}\n {overlay && <div className=\"xicms-hero__overlay\" style={overlay} aria-hidden=\"true\" />}\n {/* Gradient (donker onderaan → transparant): leesbare tekst zonder de hele foto te dimmen. */}\n {block.overlayGradient && coverMedia && <div className=\"xicms-hero__gradient\" aria-hidden=\"true\" />}\n <div className=\"xicms-hero__inner\">\n {block.eyebrow && (\n <Text type=\"label\" className=\"xicms-hero__eyebrow\" style={light} data-xicms-edit=\"eyebrow\">\n {block.eyebrow}\n </Text>\n )}\n {block.title && (\n <Heading level={1} type=\"display-1\" className=\"xicms-hero__title\" style={light} data-xicms-edit=\"title\">\n {block.title}\n </Heading>\n )}\n {block.subtitle && (\n <Text type=\"large\" className=\"xicms-hero__sub\" style={light} data-xicms-edit=\"subtitle\">\n {block.subtitle}\n </Text>\n )}\n {buttons.length > 0 && (\n <div className=\"xicms-hero__buttons\">\n {/* Via Astryx Btn (zelfde systeem als Cta/Pricing/…): volgt de site-theming en de\n outline→ghost-variantmapping — geen tweede, handmatig te onderhouden knop-CSS. */}\n {buttons.filter((b) => b.label).map((b, i) => (\n <Btn key={i} label={b.label} href={b.href} variant={b.variant} size=\"lg\" />\n ))}\n </div>\n )}\n </div>\n {block.videoUrl && (\n <button\n type=\"button\"\n className=\"xicms-hero__videotoggle\"\n onClick={toggleVideo}\n aria-label={playing ? \"Achtergrondvideo pauzeren\" : \"Achtergrondvideo afspelen\"}\n >\n {playing ? \"⏸\" : \"▶\"}\n </button>\n )}\n </section>\n );\n}\n\nexport const heroBlock = defineBlock({\n type: \"hero\",\n schema: HeroBlockSchema,\n Render: HeroRender,\n label: \"Hero\",\n icon: \"✨\",\n defaults: () => ({ title: \"Welkom\", subtitle: \"\", align: \"left\", vAlign: \"center\", minHeight: \"auto\", overlayOpacity: 0, buttons: [] }),\n});\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/blocks/builtins/Hero.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useRef, useState, type CSSProperties } from \"react\";\nimport type { z } from \"zod\";\nimport { HeroBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { Btn, Heading, Text } from \"../ui\";\nimport { Img } from \"../../media/ImageContext\";\n\ntype Hero = z.infer<typeof HeroBlockSchema>;\n\nfunction HeroRender({ block }: BlockRenderProps<Hero>) {\n // Achtergrondvideo WCAG-proof (2.2.2): geen autoPlay-attribuut (hydration-veilig) — playback\n // start in een effect, en alléén zonder reduced-motion-voorkeur. Altijd een pauzeknop, en\n // imageUrl dient als poster (direct beeld i.p.v. zwart vlak → betere LCP).\n const videoRef = useRef<HTMLVideoElement>(null);\n const [playing, setPlaying] = useState(false);\n useEffect(() => {\n const v = videoRef.current;\n if (!v) return;\n if (window.matchMedia?.(\"(prefers-reduced-motion: reduce)\").matches) return;\n v.play().then(() => setPlaying(true)).catch(() => undefined);\n }, [block.videoUrl]);\n const toggleVideo = () => {\n const v = videoRef.current;\n if (!v) return;\n if (v.paused) v.play().then(() => setPlaying(true)).catch(() => undefined);\n else {\n v.pause();\n setPlaying(false);\n }\n };\n const overlay =\n block.overlayOpacity > 0\n ? { background: block.overlayColor ?? \"#000000\", opacity: block.overlayOpacity / 100 }\n : null;\n // Beeld/video als dekkende achtergrond (niet bij height \"auto\"): dan moet de tekst licht zijn.\n const coverMedia = Boolean(block.videoUrl || block.imageUrl) && block.minHeight !== \"auto\";\n const light: CSSProperties | undefined = coverMedia\n ? { color: \"#ffffff\", textShadow: \"0 2px 14px rgba(0,0,0,.45)\" }\n : undefined;\n const buttons = block.buttons ?? [];\n\n return (\n <section\n className=\"xicms-hero\"\n data-align={block.align}\n data-valign={block.vAlign}\n data-height={block.minHeight}\n data-media={coverMedia ? \"cover\" : undefined}\n >\n {block.videoUrl ? (\n <video\n ref={videoRef}\n className=\"xicms-hero__media\"\n src={block.videoUrl}\n poster={block.imageUrl || undefined}\n muted\n loop\n playsInline\n preload=\"metadata\"\n aria-hidden=\"true\"\n />\n ) : block.imageUrl ? (\n <Img src={block.imageUrl} alt=\"\" className=\"xicms-hero__media\" width={1600} height={900} sizes=\"100vw\" priority editField=\"imageUrl\" editFill />\n ) : null}\n {overlay && <div className=\"xicms-hero__overlay\" style={overlay} aria-hidden=\"true\" />}\n {/* Gradient (donker onderaan → transparant): leesbare tekst zonder de hele foto te dimmen. */}\n {block.overlayGradient && coverMedia && <div className=\"xicms-hero__gradient\" aria-hidden=\"true\" />}\n <div className=\"xicms-hero__inner\">\n {block.eyebrow && (\n <Text type=\"label\" className=\"xicms-hero__eyebrow\" style={light} data-xicms-edit=\"eyebrow\">\n {block.eyebrow}\n </Text>\n )}\n {block.title && (\n <Heading level={block.headingLevel === \"2\" ? 2 : 1} type=\"display-1\" className=\"xicms-hero__title\" style={light} data-xicms-edit=\"title\">\n {block.title}\n </Heading>\n )}\n {block.subtitle && (\n <Text type=\"large\" className=\"xicms-hero__sub\" style={light} data-xicms-edit=\"subtitle\">\n {block.subtitle}\n </Text>\n )}\n {buttons.length > 0 && (\n <div className=\"xicms-hero__buttons\">\n {/* Via Astryx Btn (zelfde systeem als Cta/Pricing/…): volgt de site-theming en de\n outline→ghost-variantmapping — geen tweede, handmatig te onderhouden knop-CSS. */}\n {buttons.filter((b) => b.label).map((b, i) => (\n <Btn key={i} label={b.label} href={b.href} variant={b.variant} size=\"lg\" />\n ))}\n </div>\n )}\n </div>\n {block.videoUrl && (\n <button\n type=\"button\"\n className=\"xicms-hero__videotoggle\"\n onClick={toggleVideo}\n aria-label={playing ? \"Achtergrondvideo pauzeren\" : \"Achtergrondvideo afspelen\"}\n >\n {playing ? \"⏸\" : \"▶\"}\n </button>\n )}\n </section>\n );\n}\n\nexport const heroBlock = defineBlock({\n type: \"hero\",\n schema: HeroBlockSchema,\n Render: HeroRender,\n label: \"Hero\",\n icon: \"✨\",\n defaults: () => ({ title: \"Welkom\", subtitle: \"\", align: \"left\", vAlign: \"center\", minHeight: \"auto\", overlayOpacity: 0, buttons: [] }),\n});\n"],"mappings":"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","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/blocks/builtins/SymbolRef.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport type { z } from \"zod\";\nimport { doc, onSnapshot } from \"firebase/firestore\";\nimport { getDb } from \"../../firebase/client\";\nimport { SymbolRefBlockSchema } from \"../../schema/blocks\";\nimport { SymbolSchema, type Symbol } from \"../../schema/symbols\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { BlockRenderer } from \"../BlockRenderer\";\nimport { useRegistry } from \"../context\";\n\ntype SymbolRef = z.infer<typeof SymbolRefBlockSchema>;\n\n/**\n * Verwijzing naar een gedeeld symbool. Op de LIVE site wordt een symbolRef normaliter al\n * server-side vervangen door de symbool-blokken (server/pages.ts `expandSymbols`) — dan komt\n * deze Render niet eens aan bod. In de editor/preview (en als client-fallback) rendert hij het\n * symbool live vanuit Firestore, met een subtiel kader zodat de editor ziet dat dit gedeelde\n * inhoud is die op meerdere pagina's meebeweegt.\n */\nfunction SymbolRefRender({ block, siteId, mode }: BlockRenderProps<SymbolRef>) {\n const registry = useRegistry();\n const [symbol, setSymbol] = useState<Symbol | null | undefined>(undefined);\n useEffect(() => {\n const db = getDb();\n if (!db || !block.symbolId) {\n setSymbol(null);\n return;\n }\n return onSnapshot(\n doc(db, \"sites\", siteId, \"symbols\", block.symbolId),\n (snap) => {\n const parsed = snap.exists() ? SymbolSchema.safeParse(snap.data()) : null;\n setSymbol(parsed?.success ? parsed.data : null);\n },\n () => setSymbol(null),\n );\n }, [siteId, block.symbolId]);\n\n if (symbol === undefined) return null; // laden — geen flits\n if (symbol === null) {\n // Verweesde referentie: nooit de pagina breken; in preview uitleggen, live niets tonen.\n return mode === \"preview\" ? (\n <p className=\"xicms-ve__hint\">🧩 Symbool niet gevonden ({block.symbolId}) — kies een ander symbool of verwijder dit blok.</p>\n ) : null;\n }\n const inner = <BlockRenderer blocks={symbol.blocks} registry={registry} siteId={siteId} mode={mode} />;\n if (mode === \"preview\") {\n return (\n <div className=\"xicms-symbol\" data-symbol={block.symbolId}>\n <div className=\"xicms-symbol__badge\">🧩 {symbol.name} — gedeeld symbool (wijzigingen gelden op élke pagina)</div>\n {inner}\n </div>\n );\n }\n return inner;\n}\n\nexport const symbolRefBlock = defineBlock({\n type: \"symbolRef\",\n schema: SymbolRefBlockSchema,\n Render: SymbolRefRender,\n label: \"Symbool (gedeelde sectie)\",\n icon: \"🧩\",\n defaults: () => ({ symbolId: \"\" }),\n});\n"],"mappings":";AA4CM,SAGU,KAHV;AA1CN,SAAS,WAAW,gBAAgB;AAEpC,SAAS,KAAK,kBAAkB;AAChC,SAAS,aAAa;AACtB,SAAS,4BAA4B;AACrC,SAAS,oBAAiC;AAC1C,SAAS,mBAA0C;AACnD,SAAS,qBAAqB;AAC9B,SAAS,mBAAmB;AAW5B,SAAS,gBAAgB,EAAE,OAAO,QAAQ,KAAK,GAAgC;AAC7E,QAAM,WAAW,YAAY;AAC7B,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAoC,MAAS;AACzE,YAAU,MAAM;AACd,UAAM,KAAK,MAAM;AACjB,QAAI,CAAC,MAAM,CAAC,MAAM,UAAU;AAC1B,gBAAU,IAAI;AACd;AAAA,IACF;AACA,WAAO;AAAA,MACL,IAAI,IAAI,SAAS,QAAQ,WAAW,MAAM,QAAQ;AAAA,MAClD,CAAC,SAAS;AACR,cAAM,SAAS,KAAK,OAAO,IAAI,aAAa,UAAU,KAAK,KAAK,CAAC,IAAI;AACrE,kBAAU,QAAQ,UAAU,OAAO,OAAO,IAAI;AAAA,MAChD;AAAA,MACA,MAAM,UAAU,IAAI;AAAA,IACtB;AAAA,EACF,GAAG,CAAC,QAAQ,MAAM,QAAQ,CAAC;AAE3B,MAAI,WAAW,OAAW,QAAO;AACjC,MAAI,WAAW,MAAM;AAEnB,WAAO,SAAS,YACd,qBAAC,OAAE,WAAU,kBAAiB;AAAA;AAAA,MAA2B,MAAM;AAAA,MAAS;AAAA,OAAiD,IACvH;AAAA,EACN;AACA,QAAM,QAAQ,oBAAC,iBAAc,QAAQ,OAAO,QAAQ,UAAoB,QAAgB,MAAY;AACpG,MAAI,SAAS,WAAW;AACtB,WACE,qBAAC,SAAI,WAAU,gBAAe,eAAa,MAAM,UAC/C;AAAA,2BAAC,SAAI,WAAU,uBAAsB;AAAA;AAAA,QAAI,OAAO;AAAA,QAAK;AAAA,SAAsD;AAAA,MAC1G;AAAA,OACH;AAAA,EAEJ;AACA,SAAO;AACT;AAEO,MAAM,iBAAiB,YAAY;AAAA,EACxC,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,OAAO,EAAE,UAAU,GAAG;AAClC,CAAC;","names":[]}
1
+ {"version":3,"sources":["../../../src/blocks/builtins/SymbolRef.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport type { z } from \"zod\";\nimport { doc, onSnapshot } from \"firebase/firestore\";\nimport { getDb } from \"../../firebase/client\";\nimport { SymbolRefBlockSchema } from \"../../schema/blocks\";\nimport { SymbolSchema, type SymbolDefinition } from \"../../schema/symbols\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { BlockRenderer } from \"../BlockRenderer\";\nimport { useRegistry } from \"../context\";\n\ntype SymbolRef = z.infer<typeof SymbolRefBlockSchema>;\n\n/**\n * Verwijzing naar een gedeeld symbool. Op de LIVE site wordt een symbolRef normaliter al\n * server-side vervangen door de symbool-blokken (server/pages.ts `expandSymbols`) — dan komt\n * deze Render niet eens aan bod. In de editor/preview (en als client-fallback) rendert hij het\n * symbool live vanuit Firestore, met een subtiel kader zodat de editor ziet dat dit gedeelde\n * inhoud is die op meerdere pagina's meebeweegt.\n */\nfunction SymbolRefRender({ block, siteId, mode }: BlockRenderProps<SymbolRef>) {\n const registry = useRegistry();\n const [symbol, setSymbol] = useState<SymbolDefinition | null | undefined>(undefined);\n useEffect(() => {\n const db = getDb();\n if (!db || !block.symbolId) {\n setSymbol(null);\n return;\n }\n return onSnapshot(\n doc(db, \"sites\", siteId, \"symbols\", block.symbolId),\n (snap) => {\n const parsed = snap.exists() ? SymbolSchema.safeParse(snap.data()) : null;\n setSymbol(parsed?.success ? parsed.data : null);\n },\n () => setSymbol(null),\n );\n }, [siteId, block.symbolId]);\n\n if (symbol === undefined) return null; // laden — geen flits\n if (symbol === null) {\n // Verweesde referentie: nooit de pagina breken; in preview uitleggen, live niets tonen.\n return mode === \"preview\" ? (\n <p className=\"xicms-ve__hint\">🧩 Symbool niet gevonden ({block.symbolId}) — kies een ander symbool of verwijder dit blok.</p>\n ) : null;\n }\n const inner = <BlockRenderer blocks={symbol.blocks} registry={registry} siteId={siteId} mode={mode} />;\n if (mode === \"preview\") {\n return (\n <div className=\"xicms-symbol\" data-symbol={block.symbolId}>\n <div className=\"xicms-symbol__badge\">🧩 {symbol.name} — gedeeld symbool (wijzigingen gelden op élke pagina)</div>\n {inner}\n </div>\n );\n }\n return inner;\n}\n\nexport const symbolRefBlock = defineBlock({\n type: \"symbolRef\",\n schema: SymbolRefBlockSchema,\n Render: SymbolRefRender,\n label: \"Symbool (gedeelde sectie)\",\n icon: \"🧩\",\n defaults: () => ({ symbolId: \"\" }),\n});\n"],"mappings":";AA4CM,SAGU,KAHV;AA1CN,SAAS,WAAW,gBAAgB;AAEpC,SAAS,KAAK,kBAAkB;AAChC,SAAS,aAAa;AACtB,SAAS,4BAA4B;AACrC,SAAS,oBAA2C;AACpD,SAAS,mBAA0C;AACnD,SAAS,qBAAqB;AAC9B,SAAS,mBAAmB;AAW5B,SAAS,gBAAgB,EAAE,OAAO,QAAQ,KAAK,GAAgC;AAC7E,QAAM,WAAW,YAAY;AAC7B,QAAM,CAAC,QAAQ,SAAS,IAAI,SAA8C,MAAS;AACnF,YAAU,MAAM;AACd,UAAM,KAAK,MAAM;AACjB,QAAI,CAAC,MAAM,CAAC,MAAM,UAAU;AAC1B,gBAAU,IAAI;AACd;AAAA,IACF;AACA,WAAO;AAAA,MACL,IAAI,IAAI,SAAS,QAAQ,WAAW,MAAM,QAAQ;AAAA,MAClD,CAAC,SAAS;AACR,cAAM,SAAS,KAAK,OAAO,IAAI,aAAa,UAAU,KAAK,KAAK,CAAC,IAAI;AACrE,kBAAU,QAAQ,UAAU,OAAO,OAAO,IAAI;AAAA,MAChD;AAAA,MACA,MAAM,UAAU,IAAI;AAAA,IACtB;AAAA,EACF,GAAG,CAAC,QAAQ,MAAM,QAAQ,CAAC;AAE3B,MAAI,WAAW,OAAW,QAAO;AACjC,MAAI,WAAW,MAAM;AAEnB,WAAO,SAAS,YACd,qBAAC,OAAE,WAAU,kBAAiB;AAAA;AAAA,MAA2B,MAAM;AAAA,MAAS;AAAA,OAAiD,IACvH;AAAA,EACN;AACA,QAAM,QAAQ,oBAAC,iBAAc,QAAQ,OAAO,QAAQ,UAAoB,QAAgB,MAAY;AACpG,MAAI,SAAS,WAAW;AACtB,WACE,qBAAC,SAAI,WAAU,gBAAe,eAAa,MAAM,UAC/C;AAAA,2BAAC,SAAI,WAAU,uBAAsB;AAAA;AAAA,QAAI,OAAO;AAAA,QAAK;AAAA,SAAsD;AAAA,MAC1G;AAAA,OACH;AAAA,EAEJ;AACA,SAAO;AACT;AAEO,MAAM,iBAAiB,YAAY;AAAA,EACxC,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,OAAO,EAAE,UAAU,GAAG;AAClC,CAAC;","names":[]}
@@ -1,6 +1,5 @@
1
1
  "use client";
2
2
  import { useEffect } from "react";
3
- import { getFunctions, httpsCallable } from "firebase/functions";
4
3
  import { getFirebaseApp } from "../firebase/client";
5
4
  import { functionsRegion } from "../firebase/region";
6
5
  import { useSiteConfig } from "../config";
@@ -16,8 +15,10 @@ function PageView({ path }) {
16
15
  }
17
16
  const app = getFirebaseApp();
18
17
  if (!app) return;
19
- const fn = httpsCallable(getFunctions(app, functionsRegion()), "trackView");
20
- void fn({ siteId, path }).catch(() => {
18
+ void import("firebase/functions").then(({ getFunctions, httpsCallable }) => {
19
+ const fn = httpsCallable(getFunctions(app, functionsRegion()), "trackView");
20
+ return fn({ siteId, path });
21
+ }).catch(() => {
21
22
  });
22
23
  }, [siteId, path]);
23
24
  return null;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/PageView.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect } from \"react\";\nimport { getFunctions, httpsCallable } from \"firebase/functions\";\nimport { getFirebaseApp } from \"../firebase/client\";\nimport { functionsRegion } from \"../firebase/region\";\nimport { useSiteConfig } from \"../config\";\n\n/**\n * Registreert één pageview voor de huidige site/pad — max. één keer per browsersessie per pad\n * (sessionStorage). Privacy-vriendelijk en goedkoop. No-op zonder Firebase. Plaats dit in de\n * publieke layout van een klantsite (binnen <XiCMSProvider>).\n */\nexport function PageView({ path }: { path: string }) {\n const { siteId } = useSiteConfig();\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const key = `xicms_view:${siteId}:${path}`;\n try {\n if (sessionStorage.getItem(key)) return;\n sessionStorage.setItem(key, \"1\");\n } catch {\n /* private mode e.d. — ga gewoon door */\n }\n const app = getFirebaseApp();\n if (!app) return;\n const fn = httpsCallable(getFunctions(app, functionsRegion()), \"trackView\");\n void fn({ siteId, path }).catch(() => {\n /* stats mogen nooit de site breken */\n });\n }, [siteId, path]);\n\n return null;\n}\n"],"mappings":";AAEA,SAAS,iBAAiB;AAC1B,SAAS,cAAc,qBAAqB;AAC5C,SAAS,sBAAsB;AAC/B,SAAS,uBAAuB;AAChC,SAAS,qBAAqB;AAOvB,SAAS,SAAS,EAAE,KAAK,GAAqB;AACnD,QAAM,EAAE,OAAO,IAAI,cAAc;AAEjC,YAAU,MAAM;AACd,QAAI,OAAO,WAAW,YAAa;AACnC,UAAM,MAAM,cAAc,MAAM,IAAI,IAAI;AACxC,QAAI;AACF,UAAI,eAAe,QAAQ,GAAG,EAAG;AACjC,qBAAe,QAAQ,KAAK,GAAG;AAAA,IACjC,QAAQ;AAAA,IAER;AACA,UAAM,MAAM,eAAe;AAC3B,QAAI,CAAC,IAAK;AACV,UAAM,KAAK,cAAc,aAAa,KAAK,gBAAgB,CAAC,GAAG,WAAW;AAC1E,SAAK,GAAG,EAAE,QAAQ,KAAK,CAAC,EAAE,MAAM,MAAM;AAAA,IAEtC,CAAC;AAAA,EACH,GAAG,CAAC,QAAQ,IAAI,CAAC;AAEjB,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../../src/components/PageView.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect } from \"react\";\nimport { getFirebaseApp } from \"../firebase/client\";\nimport { functionsRegion } from \"../firebase/region\";\nimport { useSiteConfig } from \"../config\";\n\n/**\n * Registreert één pageview voor de huidige site/pad — max. één keer per browsersessie per pad\n * (sessionStorage). Privacy-vriendelijk en goedkoop. No-op zonder Firebase. Plaats dit in de\n * publieke layout van een klantsite (binnen <XiCMSProvider>).\n */\nexport function PageView({ path }: { path: string }) {\n const { siteId } = useSiteConfig();\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const key = `xicms_view:${siteId}:${path}`;\n try {\n if (sessionStorage.getItem(key)) return;\n sessionStorage.setItem(key, \"1\");\n } catch {\n /* private mode e.d. — ga gewoon door */\n }\n const app = getFirebaseApp();\n if (!app) return;\n // Lazy import: firebase/functions weegt anders mee in de bundle van élke publieke pagina,\n // terwijl het alleen voor deze ene fire-and-forget-call nodig is.\n void import(\"firebase/functions\")\n .then(({ getFunctions, httpsCallable }) => {\n const fn = httpsCallable(getFunctions(app, functionsRegion()), \"trackView\");\n return fn({ siteId, path });\n })\n .catch(() => {\n /* stats mogen nooit de site breken */\n });\n }, [siteId, path]);\n\n return null;\n}\n"],"mappings":";AAEA,SAAS,iBAAiB;AAC1B,SAAS,sBAAsB;AAC/B,SAAS,uBAAuB;AAChC,SAAS,qBAAqB;AAOvB,SAAS,SAAS,EAAE,KAAK,GAAqB;AACnD,QAAM,EAAE,OAAO,IAAI,cAAc;AAEjC,YAAU,MAAM;AACd,QAAI,OAAO,WAAW,YAAa;AACnC,UAAM,MAAM,cAAc,MAAM,IAAI,IAAI;AACxC,QAAI;AACF,UAAI,eAAe,QAAQ,GAAG,EAAG;AACjC,qBAAe,QAAQ,KAAK,GAAG;AAAA,IACjC,QAAQ;AAAA,IAER;AACA,UAAM,MAAM,eAAe;AAC3B,QAAI,CAAC,IAAK;AAGV,SAAK,OAAO,oBAAoB,EAC7B,KAAK,CAAC,EAAE,cAAc,cAAc,MAAM;AACzC,YAAM,KAAK,cAAc,aAAa,KAAK,gBAAgB,CAAC,GAAG,WAAW;AAC1E,aAAO,GAAG,EAAE,QAAQ,KAAK,CAAC;AAAA,IAC5B,CAAC,EACA,MAAM,MAAM;AAAA,IAEb,CAAC;AAAA,EACL,GAAG,CAAC,QAAQ,IAAI,CAAC;AAEjB,SAAO;AACT;","names":[]}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @xiwebdesign/xicms-core/demo — demo-bewerkmodus (lokaal-only, geen Firebase-writes).
3
+ * Alleen voor showcase-/demo-sites (XiCMS.nl): seed registreren, de lokaal-bewerkte pagina
4
+ * resolven en demo-nieuws/-formulieren/-media aanbieden. Klantsites importeren dit nooit.
5
+ */
6
+ export { registerSeed, resolveDemoPage, loadDemoDraft, demoIdForPath, registerDemoNews, registerDemoForm, registerDemoMedia, DEMO_EVENT, } from "./demoStore";
7
+ export { DemoStudio } from "./DemoStudio";
@@ -0,0 +1,23 @@
1
+ import {
2
+ registerSeed,
3
+ resolveDemoPage,
4
+ loadDemoDraft,
5
+ demoIdForPath,
6
+ registerDemoNews,
7
+ registerDemoForm,
8
+ registerDemoMedia,
9
+ DEMO_EVENT
10
+ } from "./demoStore";
11
+ import { DemoStudio } from "./DemoStudio";
12
+ export {
13
+ DEMO_EVENT,
14
+ DemoStudio,
15
+ demoIdForPath,
16
+ loadDemoDraft,
17
+ registerDemoForm,
18
+ registerDemoMedia,
19
+ registerDemoNews,
20
+ registerSeed,
21
+ resolveDemoPage
22
+ };
23
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/demo/index.ts"],"sourcesContent":["/**\n * @xiwebdesign/xicms-core/demo — demo-bewerkmodus (lokaal-only, geen Firebase-writes).\n * Alleen voor showcase-/demo-sites (XiCMS.nl): seed registreren, de lokaal-bewerkte pagina\n * resolven en demo-nieuws/-formulieren/-media aanbieden. Klantsites importeren dit nooit.\n */\nexport {\n registerSeed,\n resolveDemoPage,\n loadDemoDraft,\n demoIdForPath,\n registerDemoNews,\n registerDemoForm,\n registerDemoMedia,\n DEMO_EVENT,\n} from \"./demoStore\";\nexport { DemoStudio } from \"./DemoStudio\";\n"],"mappings":"AAKA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,kBAAkB;","names":[]}
package/dist/index.d.ts CHANGED
@@ -23,14 +23,14 @@ export type { SiteConfig, SiteMode } from "./schema/siteConfig";
23
23
  export { FieldSchema, FieldsSchema, fieldsToZod, fieldsDefaults, isFieldVisible, isRichTextKey } from "./schema/fields";
24
24
  export type { Field, FieldHints, ShowIf } from "./schema/fields";
25
25
  export { SymbolSchema } from "./schema/symbols";
26
- export type { Symbol, StoredSymbol } from "./schema/symbols";
26
+ export type { SymbolDefinition, StoredSymbol } from "./schema/symbols";
27
27
  export { CustomBlockSchema } from "./schema/customBlocks";
28
28
  export type { CustomBlock, CustomBlock as CustomBlockData } from "./schema/customBlocks";
29
29
  export { customBlockToDefinition } from "./blocks/customTemplate/definition";
30
30
  export { interpretTemplate, scopeCss } from "./blocks/customTemplate/interpret";
31
31
  export { siteLocales, parseLocaleSlugs, localizedPath, localizePage, localizeBlocks, pruneTranslations, TranslationsSchema, } from "./i18n/overlay";
32
32
  export type { Translations, LocaleConfig } from "./i18n/overlay";
33
- export type { ThemeTokens, NavItem, SiteSettings, FormsSender } from "./schema/settings";
33
+ export type { ThemeTokens, NavItem, SiteSettings, PublicSiteSettings, FormsSender } from "./schema/settings";
34
34
  export type { Seo } from "./schema/seo";
35
35
  export type { Page, PageStatus } from "./schema/pages";
36
36
  export type { FormDefinition, FormField } from "./schema/forms";
@@ -50,7 +50,5 @@ export { PageRenderer } from "./render/PageRenderer";
50
50
  export { PublishedPage } from "./render/PublishedPage";
51
51
  export { defineModule, resolveModules, isModuleActive, ModulesProvider, useModules, useModuleActive, useModuleSettings, applyBlockOverrides, moduleIdForBlockType, builtinModules, validateModuleContract, assertModuleContract, useConsent, ConsentBanner, ConsentGate, ConsentReopenLink, type ConsentState, type ConsentSettings, type ConsentCategory, type ModuleDefinition, type ModuleSiteSetting, type ModulesSiteConfig, type Capability, type ResolvedModules, type BlockOverride, type BlockOverrides, } from "./modules";
52
52
  export { DemoLock } from "./lock/DemoLock";
53
- export { registerSeed, resolveDemoPage, loadDemoDraft, demoIdForPath, registerDemoNews, registerDemoForm, registerDemoMedia, DEMO_EVENT, } from "./demo/demoStore";
54
- export { DemoStudio } from "./demo/DemoStudio";
55
53
  export { getFirebaseApp, getFirebaseAuth, getDb, getBucket, isFirebaseConfigured, } from "./firebase/client";
56
54
  export { initAppCheck } from "./firebase/appcheck";
package/dist/index.js CHANGED
@@ -76,17 +76,6 @@ import {
76
76
  ConsentReopenLink
77
77
  } from "./modules";
78
78
  import { DemoLock } from "./lock/DemoLock";
79
- import {
80
- registerSeed,
81
- resolveDemoPage,
82
- loadDemoDraft,
83
- demoIdForPath,
84
- registerDemoNews,
85
- registerDemoForm,
86
- registerDemoMedia,
87
- DEMO_EVENT
88
- } from "./demo/demoStore";
89
- import { DemoStudio } from "./demo/DemoStudio";
90
79
  import {
91
80
  getFirebaseApp,
92
81
  getFirebaseAuth,
@@ -103,9 +92,7 @@ export {
103
92
  ConsentGate,
104
93
  ConsentReopenLink,
105
94
  CustomBlockSchema,
106
- DEMO_EVENT,
107
95
  DemoLock,
108
- DemoStudio,
109
96
  EditBar,
110
97
  EditorProvider,
111
98
  FieldSchema,
@@ -145,7 +132,6 @@ export {
145
132
  definePattern,
146
133
  defineSiteConfig,
147
134
  defineTheme,
148
- demoIdForPath,
149
135
  fieldsDefaults,
150
136
  fieldsToZod,
151
137
  getBucket,
@@ -160,7 +146,6 @@ export {
160
146
  isModuleActive,
161
147
  isRichTextKey,
162
148
  isSuperadmin,
163
- loadDemoDraft,
164
149
  localizeBlocks,
165
150
  localizePage,
166
151
  localizedPath,
@@ -168,11 +153,6 @@ export {
168
153
  moduleIdForBlockType,
169
154
  parseLocaleSlugs,
170
155
  pruneTranslations,
171
- registerDemoForm,
172
- registerDemoMedia,
173
- registerDemoNews,
174
- registerSeed,
175
- resolveDemoPage,
176
156
  resolveModules,
177
157
  roleForSite,
178
158
  scopeCss,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Publieke runtime-API van @xiwebdesign/xicms-core (lichte client/runtime laag).\n * - Admin (TipTap/Radix) zit in \"@xiwebdesign/xicms-core/admin\".\n * - Server-only code (firebase-admin, sanitize, SEO) zit in \"@xiwebdesign/xicms-core/server\".\n * - Pure schema's in \"@xiwebdesign/xicms-core/schema\".\n */\n\n// Provider & config\nexport { XiCMSProvider } from \"./XiCMSProvider\";\nexport { useSiteConfig } from \"./config\";\n\n// Theming & design-primitieven (maatwerk zonder core te forken).\n// - `defineTheme` maakt een eigen Astryx-thema (tokens + component-overrides) → geef door als\n// XiCMSProvider `astryxTheme`-prop.\n// - De `ui`-primitieven (Card/Stack/Grid/Heading/Text/Btn…) zijn de bouwstenen waarop de ingebouwde\n// blokken draaien; gebruik ze in eigen blokken voor een consistent maar te herstijlen uiterlijk.\nexport { defineTheme, useTheme } from \"@astryxdesign/core/theme\";\nexport type { DefinedTheme } from \"@astryxdesign/core/theme\";\n// Framework-router-integratie: geef `next/link` door als XiCMSProvider `linkComponent`-prop\n// (soepele client-nav + prefetch op alle Astryx-knoppen/links). `useLinkComponent` voor eigen blokken.\nexport { LinkProvider, useLinkComponent } from \"@astryxdesign/core/Link\";\nexport type { LinkComponentType } from \"@astryxdesign/core/Link\";\n// Beeldoptimalisatie: geef `next/image` door als XiCMSProvider `imageComponent`-prop. `Img` is de\n// wrapper die de blokken gebruiken; `useImageComponent` voor eigen blokken.\nexport { Img, ImageProvider, useImageComponent, InlineImageEditProvider } from \"./media/ImageContext\";\nexport type { ImageComponentType, ImgProps } from \"./media/ImageContext\";\nexport * from \"./blocks/ui\";\nexport {\n SiteSettingsProvider,\n useSiteSettings,\n useMergedSiteConfig,\n} from \"./settings/useSiteSettings\";\nexport { SiteFooter } from \"./components/site/SiteFooter\";\nexport { EditorProvider, useEditor } from \"./editor/EditorProvider\";\nexport {\n definePattern,\n instantiatePattern,\n collectPatternTypes,\n builtinPatterns,\n type Pattern,\n type PatternBlock,\n} from \"./patterns\";\nexport { builtinThemePresets, type ThemePreset } from \"./theme/presets\";\nexport { defineSiteConfig } from \"./schema/siteConfig\";\nexport type { SiteConfig, SiteMode } from \"./schema/siteConfig\";\n// 5.0: Field-IR (primo-port), symbolen en custom blocks.\nexport { FieldSchema, FieldsSchema, fieldsToZod, fieldsDefaults, isFieldVisible, isRichTextKey } from \"./schema/fields\";\nexport type { Field, FieldHints, ShowIf } from \"./schema/fields\";\nexport { SymbolSchema } from \"./schema/symbols\";\nexport type { Symbol, StoredSymbol } from \"./schema/symbols\";\nexport { CustomBlockSchema } from \"./schema/customBlocks\";\nexport type { CustomBlock, CustomBlock as CustomBlockData } from \"./schema/customBlocks\";\nexport { customBlockToDefinition } from \"./blocks/customTemplate/definition\";\nexport { interpretTemplate, scopeCss } from \"./blocks/customTemplate/interpret\";\n// 5.0: localisatie — overlay-model (primo's locale-per-waarde, additief vertaald).\nexport {\n siteLocales,\n parseLocaleSlugs,\n localizedPath,\n localizePage,\n localizeBlocks,\n pruneTranslations,\n TranslationsSchema,\n} from \"./i18n/overlay\";\nexport type { Translations, LocaleConfig } from \"./i18n/overlay\";\nexport type { ThemeTokens, NavItem, SiteSettings, FormsSender } from \"./schema/settings\";\nexport type { Seo } from \"./schema/seo\";\nexport type { Page, PageStatus } from \"./schema/pages\";\nexport type { FormDefinition, FormField } from \"./schema/forms\";\n\n// Auth\nexport { AuthProvider, useAuth } from \"./auth/AuthProvider\";\nexport { LoginForm } from \"./auth/LoginForm\";\nexport {\n roleForSite,\n canEdit,\n canManageSite,\n isSuperadmin,\n type Role,\n type XiClaims,\n} from \"./auth/roles\";\n\n// De legacy inline-laag (ContentProvider/useContent + EditableText/EditableRichText/EditableImage,\n// het v1 platte `cms/content`-model) is in 5.0 VERWIJDERD, zoals bij de deprecatie aangekondigd.\n// Inline bewerken loopt via het block-model: markeer tekstvelden met `data-xicms-edit` in de Render.\nexport type { NewsArticle, MediaItem, MediaVariant } from \"./content/types\";\n\nexport { EditBar } from \"./components/EditBar\";\nexport { ResponsiveImage } from \"./components/ResponsiveImage\";\nexport { PageView } from \"./components/PageView\";\nexport { NewsList, NewsArticleView, useNews } from \"./components/News\";\n\n// Block-systeem (runtime: registry + renderer; editors zitten in /admin)\nexport {\n defineBlock,\n createRegistry,\n mergeRegistry,\n validateBlocks,\n buildBlockAddOptions,\n type BlockDefinition,\n type BlockRegistry,\n type BlockRenderProps,\n type BlockEditorProps,\n type BlockCategory,\n} from \"./blocks/registry\";\nexport { BlockRenderer } from \"./blocks/BlockRenderer\";\nexport { RegistryProvider, useRegistry } from \"./blocks/context\";\nexport { builtinBlocks, ConsentEmbed } from \"./blocks/builtins\";\nexport { PageRenderer } from \"./render/PageRenderer\";\nexport { PublishedPage } from \"./render/PublishedPage\";\n\n// Module-systeem (XiCMS 3.0): modules bundelen blocks + instellingen; per site aan/uit + te stylen.\nexport {\n defineModule,\n resolveModules,\n isModuleActive,\n ModulesProvider,\n useModules,\n useModuleActive,\n useModuleSettings,\n applyBlockOverrides,\n moduleIdForBlockType,\n builtinModules,\n validateModuleContract,\n assertModuleContract,\n useConsent,\n ConsentBanner,\n ConsentGate,\n ConsentReopenLink,\n type ConsentState,\n type ConsentSettings,\n type ConsentCategory,\n type ModuleDefinition,\n type ModuleSiteSetting,\n type ModulesSiteConfig,\n type Capability,\n type ResolvedModules,\n type BlockOverride,\n type BlockOverrides,\n} from \"./modules\";\n\n// Demo-lock\nexport { DemoLock } from \"./lock/DemoLock\";\n\n// Demo-bewerkmodus (lokaal-only): seed registreren + de live (lokaal-bewerkte) pagina ophalen\nexport {\n registerSeed,\n resolveDemoPage,\n loadDemoDraft,\n demoIdForPath,\n registerDemoNews,\n registerDemoForm,\n registerDemoMedia,\n DEMO_EVENT,\n} from \"./demo/demoStore\";\nexport { DemoStudio } from \"./demo/DemoStudio\";\n\n// Firebase (client)\nexport {\n getFirebaseApp,\n getFirebaseAuth,\n getDb,\n getBucket,\n isFirebaseConfigured,\n} from \"./firebase/client\";\nexport { initAppCheck } from \"./firebase/appcheck\";\n"],"mappings":"AAQA,SAAS,qBAAqB;AAC9B,SAAS,qBAAqB;AAO9B,SAAS,aAAa,gBAAgB;AAItC,SAAS,cAAc,wBAAwB;AAI/C,SAAS,KAAK,eAAe,mBAAmB,+BAA+B;AAE/E,cAAc;AACd;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,kBAAkB;AAC3B,SAAS,gBAAgB,iBAAiB;AAC1C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP,SAAS,2BAA6C;AACtD,SAAS,wBAAwB;AAGjC,SAAS,aAAa,cAAc,aAAa,gBAAgB,gBAAgB,qBAAqB;AAEtG,SAAS,oBAAoB;AAE7B,SAAS,yBAAyB;AAElC,SAAS,+BAA+B;AACxC,SAAS,mBAAmB,gBAAgB;AAE5C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAQP,SAAS,cAAc,eAAe;AACtC,SAAS,iBAAiB;AAC1B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAOP,SAAS,eAAe;AACxB,SAAS,uBAAuB;AAChC,SAAS,gBAAgB;AACzB,SAAS,UAAU,iBAAiB,eAAe;AAGnD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAMK;AACP,SAAS,qBAAqB;AAC9B,SAAS,kBAAkB,mBAAmB;AAC9C,SAAS,eAAe,oBAAoB;AAC5C,SAAS,oBAAoB;AAC7B,SAAS,qBAAqB;AAG9B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAWK;AAGP,SAAS,gBAAgB;AAGzB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,kBAAkB;AAG3B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,oBAAoB;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Publieke runtime-API van @xiwebdesign/xicms-core (lichte client/runtime laag).\n * - Admin (TipTap/Radix) zit in \"@xiwebdesign/xicms-core/admin\".\n * - Server-only code (firebase-admin, sanitize, SEO) zit in \"@xiwebdesign/xicms-core/server\".\n * - Pure schema's in \"@xiwebdesign/xicms-core/schema\".\n */\n\n// Provider & config\nexport { XiCMSProvider } from \"./XiCMSProvider\";\nexport { useSiteConfig } from \"./config\";\n\n// Theming & design-primitieven (maatwerk zonder core te forken).\n// - `defineTheme` maakt een eigen Astryx-thema (tokens + component-overrides) → geef door als\n// XiCMSProvider `astryxTheme`-prop.\n// - De `ui`-primitieven (Card/Stack/Grid/Heading/Text/Btn…) zijn de bouwstenen waarop de ingebouwde\n// blokken draaien; gebruik ze in eigen blokken voor een consistent maar te herstijlen uiterlijk.\nexport { defineTheme, useTheme } from \"@astryxdesign/core/theme\";\nexport type { DefinedTheme } from \"@astryxdesign/core/theme\";\n// Framework-router-integratie: geef `next/link` door als XiCMSProvider `linkComponent`-prop\n// (soepele client-nav + prefetch op alle Astryx-knoppen/links). `useLinkComponent` voor eigen blokken.\nexport { LinkProvider, useLinkComponent } from \"@astryxdesign/core/Link\";\nexport type { LinkComponentType } from \"@astryxdesign/core/Link\";\n// Beeldoptimalisatie: geef `next/image` door als XiCMSProvider `imageComponent`-prop. `Img` is de\n// wrapper die de blokken gebruiken; `useImageComponent` voor eigen blokken.\nexport { Img, ImageProvider, useImageComponent, InlineImageEditProvider } from \"./media/ImageContext\";\nexport type { ImageComponentType, ImgProps } from \"./media/ImageContext\";\nexport * from \"./blocks/ui\";\nexport {\n SiteSettingsProvider,\n useSiteSettings,\n useMergedSiteConfig,\n} from \"./settings/useSiteSettings\";\nexport { SiteFooter } from \"./components/site/SiteFooter\";\nexport { EditorProvider, useEditor } from \"./editor/EditorProvider\";\nexport {\n definePattern,\n instantiatePattern,\n collectPatternTypes,\n builtinPatterns,\n type Pattern,\n type PatternBlock,\n} from \"./patterns\";\nexport { builtinThemePresets, type ThemePreset } from \"./theme/presets\";\nexport { defineSiteConfig } from \"./schema/siteConfig\";\nexport type { SiteConfig, SiteMode } from \"./schema/siteConfig\";\n// 5.0: Field-IR (primo-port), symbolen en custom blocks.\nexport { FieldSchema, FieldsSchema, fieldsToZod, fieldsDefaults, isFieldVisible, isRichTextKey } from \"./schema/fields\";\nexport type { Field, FieldHints, ShowIf } from \"./schema/fields\";\nexport { SymbolSchema } from \"./schema/symbols\";\nexport type { SymbolDefinition, StoredSymbol } from \"./schema/symbols\";\nexport { CustomBlockSchema } from \"./schema/customBlocks\";\nexport type { CustomBlock, CustomBlock as CustomBlockData } from \"./schema/customBlocks\";\nexport { customBlockToDefinition } from \"./blocks/customTemplate/definition\";\nexport { interpretTemplate, scopeCss } from \"./blocks/customTemplate/interpret\";\n// 5.0: localisatie — overlay-model (primo's locale-per-waarde, additief vertaald).\nexport {\n siteLocales,\n parseLocaleSlugs,\n localizedPath,\n localizePage,\n localizeBlocks,\n pruneTranslations,\n TranslationsSchema,\n} from \"./i18n/overlay\";\nexport type { Translations, LocaleConfig } from \"./i18n/overlay\";\nexport type { ThemeTokens, NavItem, SiteSettings, PublicSiteSettings, FormsSender } from \"./schema/settings\";\nexport type { Seo } from \"./schema/seo\";\nexport type { Page, PageStatus } from \"./schema/pages\";\nexport type { FormDefinition, FormField } from \"./schema/forms\";\n\n// Auth\nexport { AuthProvider, useAuth } from \"./auth/AuthProvider\";\nexport { LoginForm } from \"./auth/LoginForm\";\nexport {\n roleForSite,\n canEdit,\n canManageSite,\n isSuperadmin,\n type Role,\n type XiClaims,\n} from \"./auth/roles\";\n\n// De legacy inline-laag (ContentProvider/useContent + EditableText/EditableRichText/EditableImage,\n// het v1 platte `cms/content`-model) is in 5.0 VERWIJDERD, zoals bij de deprecatie aangekondigd.\n// Inline bewerken loopt via het block-model: markeer tekstvelden met `data-xicms-edit` in de Render.\nexport type { NewsArticle, MediaItem, MediaVariant } from \"./content/types\";\n\nexport { EditBar } from \"./components/EditBar\";\nexport { ResponsiveImage } from \"./components/ResponsiveImage\";\nexport { PageView } from \"./components/PageView\";\nexport { NewsList, NewsArticleView, useNews } from \"./components/News\";\n\n// Block-systeem (runtime: registry + renderer; editors zitten in /admin)\nexport {\n defineBlock,\n createRegistry,\n mergeRegistry,\n validateBlocks,\n buildBlockAddOptions,\n type BlockDefinition,\n type BlockRegistry,\n type BlockRenderProps,\n type BlockEditorProps,\n type BlockCategory,\n} from \"./blocks/registry\";\nexport { BlockRenderer } from \"./blocks/BlockRenderer\";\nexport { RegistryProvider, useRegistry } from \"./blocks/context\";\nexport { builtinBlocks, ConsentEmbed } from \"./blocks/builtins\";\nexport { PageRenderer } from \"./render/PageRenderer\";\nexport { PublishedPage } from \"./render/PublishedPage\";\n\n// Module-systeem (XiCMS 3.0): modules bundelen blocks + instellingen; per site aan/uit + te stylen.\nexport {\n defineModule,\n resolveModules,\n isModuleActive,\n ModulesProvider,\n useModules,\n useModuleActive,\n useModuleSettings,\n applyBlockOverrides,\n moduleIdForBlockType,\n builtinModules,\n validateModuleContract,\n assertModuleContract,\n useConsent,\n ConsentBanner,\n ConsentGate,\n ConsentReopenLink,\n type ConsentState,\n type ConsentSettings,\n type ConsentCategory,\n type ModuleDefinition,\n type ModuleSiteSetting,\n type ModulesSiteConfig,\n type Capability,\n type ResolvedModules,\n type BlockOverride,\n type BlockOverrides,\n} from \"./modules\";\n\n// Demo-lock\nexport { DemoLock } from \"./lock/DemoLock\";\n\n// Demo-bewerkmodus (lokaal-only) leeft op het subpad `@xiwebdesign/xicms-core/demo` — dit zijn\n// demo-store-internals die niet in de publieke runtime-API thuishoren (klantsites gebruiken ze nooit;\n// alleen de XiCMS.nl-showcase). DemoStudio wordt intern door XiCMSProvider gerenderd.\n\n// Firebase (client)\nexport {\n getFirebaseApp,\n getFirebaseAuth,\n getDb,\n getBucket,\n isFirebaseConfigured,\n} from \"./firebase/client\";\nexport { initAppCheck } from \"./firebase/appcheck\";\n"],"mappings":"AAQA,SAAS,qBAAqB;AAC9B,SAAS,qBAAqB;AAO9B,SAAS,aAAa,gBAAgB;AAItC,SAAS,cAAc,wBAAwB;AAI/C,SAAS,KAAK,eAAe,mBAAmB,+BAA+B;AAE/E,cAAc;AACd;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,kBAAkB;AAC3B,SAAS,gBAAgB,iBAAiB;AAC1C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP,SAAS,2BAA6C;AACtD,SAAS,wBAAwB;AAGjC,SAAS,aAAa,cAAc,aAAa,gBAAgB,gBAAgB,qBAAqB;AAEtG,SAAS,oBAAoB;AAE7B,SAAS,yBAAyB;AAElC,SAAS,+BAA+B;AACxC,SAAS,mBAAmB,gBAAgB;AAE5C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAQP,SAAS,cAAc,eAAe;AACtC,SAAS,iBAAiB;AAC1B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAOP,SAAS,eAAe;AACxB,SAAS,uBAAuB;AAChC,SAAS,gBAAgB;AACzB,SAAS,UAAU,iBAAiB,eAAe;AAGnD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAMK;AACP,SAAS,qBAAqB;AAC9B,SAAS,kBAAkB,mBAAmB;AAC9C,SAAS,eAAe,oBAAoB;AAC5C,SAAS,oBAAoB;AAC7B,SAAS,qBAAqB;AAG9B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAWK;AAGP,SAAS,gBAAgB;AAOzB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,oBAAoB;","names":[]}
@@ -41,6 +41,7 @@ export declare const beforeAfterModule: ModuleDefinition;
41
41
  export declare const portfolioModule: ModuleDefinition;
42
42
  export declare const jobsModule: ModuleDefinition;
43
43
  export declare const breadcrumbsModule: ModuleDefinition;
44
+ export { shopModule } from "./shop/module";
44
45
  export declare const consentModule: ModuleDefinition;
45
46
  /** Alle ingebouwde modules (volgorde bepaalt block- en tab-volgorde). */
46
47
  export declare const builtinModules: ModuleDefinition[];
@@ -353,6 +353,8 @@ const breadcrumbsModule = defineModule({
353
353
  defaultEnabled: false,
354
354
  blocks: [breadcrumbsBlock]
355
355
  });
356
+ import { shopModule } from "./shop/module";
357
+ import { shopModule as shopModule2 } from "./shop/module";
356
358
  const consentModule = defineModule({
357
359
  id: "consent",
358
360
  version: CORE_VERSION,
@@ -417,6 +419,7 @@ const builtinModules = [
417
419
  portfolioModule,
418
420
  jobsModule,
419
421
  breadcrumbsModule,
422
+ shopModule2,
420
423
  consentModule
421
424
  ];
422
425
  export {
@@ -451,6 +454,7 @@ export {
451
454
  pricingModule,
452
455
  searchModule,
453
456
  settingsModule,
457
+ shopModule,
454
458
  sliderModule,
455
459
  statsModule,
456
460
  tabsModule,