@xiwebdesign/xicms-core 5.0.6 → 5.1.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 (44) hide show
  1. package/dist/XiCMSProvider.d.ts +1 -1
  2. package/dist/XiCMSProvider.js +42 -30
  3. package/dist/XiCMSProvider.js.map +1 -1
  4. package/dist/admin/editors/RichTextEditor.js +7 -1
  5. package/dist/admin/editors/RichTextEditor.js.map +1 -1
  6. package/dist/admin/ui/ArrayEditor.d.ts +6 -1
  7. package/dist/admin/ui/ArrayEditor.js +22 -3
  8. package/dist/admin/ui/ArrayEditor.js.map +1 -1
  9. package/dist/admin/visual/VisualEditor.js +35 -9
  10. package/dist/admin/visual/VisualEditor.js.map +1 -1
  11. package/dist/blocks/builtins/Agenda.js +2 -1
  12. package/dist/blocks/builtins/Agenda.js.map +1 -1
  13. package/dist/blocks/builtins/Breadcrumbs.js +2 -1
  14. package/dist/blocks/builtins/Breadcrumbs.js.map +1 -1
  15. package/dist/blocks/builtins/Contact.js +2 -1
  16. package/dist/blocks/builtins/Contact.js.map +1 -1
  17. package/dist/blocks/builtins/Faq.js +2 -1
  18. package/dist/blocks/builtins/Faq.js.map +1 -1
  19. package/dist/blocks/builtins/Jobs.js +2 -1
  20. package/dist/blocks/builtins/Jobs.js.map +1 -1
  21. package/dist/blocks/builtins/OpeningHours.js +2 -1
  22. package/dist/blocks/builtins/OpeningHours.js.map +1 -1
  23. package/dist/blocks/builtins/Pricing.js +2 -1
  24. package/dist/blocks/builtins/Pricing.js.map +1 -1
  25. package/dist/blocks/builtins/Search.js +18 -8
  26. package/dist/blocks/builtins/Search.js.map +1 -1
  27. package/dist/blocks/builtins/Team.js +2 -1
  28. package/dist/blocks/builtins/Team.js.map +1 -1
  29. package/dist/blocks/builtins/Testimonials.js +2 -1
  30. package/dist/blocks/builtins/Testimonials.js.map +1 -1
  31. package/dist/blocks/builtins/jsonld.d.ts +9 -0
  32. package/dist/blocks/builtins/jsonld.js +7 -0
  33. package/dist/blocks/builtins/jsonld.js.map +1 -0
  34. package/dist/components/News.js +2 -1
  35. package/dist/components/News.js.map +1 -1
  36. package/dist/i18n/overlay.d.ts +13 -4
  37. package/dist/i18n/overlay.js +54 -8
  38. package/dist/i18n/overlay.js.map +1 -1
  39. package/dist/server/index.d.ts +1 -0
  40. package/dist/server/index.js +2 -0
  41. package/dist/server/index.js.map +1 -1
  42. package/dist/server/pages.js +34 -13
  43. package/dist/server/pages.js.map +1 -1
  44. package/package.json +1 -1
@@ -36,21 +36,54 @@ function setPath(obj, keys, value) {
36
36
  cur[keys[keys.length - 1]] = value;
37
37
  return root;
38
38
  }
39
+ function localizeNestedBlock(b, overlay) {
40
+ let cur = b;
41
+ const localizeSlot = (slot) => {
42
+ if (!Array.isArray(slot?.blocks)) return slot;
43
+ const nb = localizeBlocks(slot.blocks, overlay);
44
+ return nb === slot.blocks ? slot : { ...slot, blocks: nb };
45
+ };
46
+ if (Array.isArray(cur.columns)) {
47
+ const cols = cur.columns;
48
+ const next = cols.map(localizeSlot);
49
+ if (next.some((c, i) => c !== cols[i])) cur = { ...cur, columns: next };
50
+ }
51
+ if (Array.isArray(cur.tabs)) {
52
+ const tabs = cur.tabs;
53
+ const next = tabs.map(localizeSlot);
54
+ if (next.some((t, i) => t !== tabs[i])) cur = { ...cur, tabs: next };
55
+ }
56
+ for (const field of ["main", "aside"]) {
57
+ if (Array.isArray(cur[field])) {
58
+ const nb = localizeBlocks(cur[field], overlay);
59
+ if (nb !== cur[field]) cur = { ...cur, [field]: nb };
60
+ }
61
+ }
62
+ return cur;
63
+ }
39
64
  function localizeBlocks(blocks, overlay) {
40
65
  if (!overlay || blocks.length === 0) return blocks;
41
66
  let out = blocks;
67
+ const replace = (idx, value) => {
68
+ if (out === blocks) out = [...blocks];
69
+ out[idx] = value;
70
+ };
42
71
  for (const [key, value] of Object.entries(overlay)) {
43
72
  if (typeof value !== "string") continue;
44
73
  const [blockKey, ...rest] = key.split(".");
45
74
  if (rest.length === 0) continue;
46
75
  const idx = out.findIndex((b) => b?._key === blockKey);
47
- if (idx < 0) continue;
48
- const parent = rest.length > 1 ? getPath(out[idx], rest.slice(0, -1)) : out[idx];
49
- if (parent == null || typeof parent !== "object") continue;
50
- const current = parent[rest[rest.length - 1]];
51
- if (current !== void 0 && typeof current !== "string") continue;
52
- if (out === blocks) out = [...blocks];
53
- out[idx] = setPath(out[idx], rest, value);
76
+ if (idx >= 0) {
77
+ const parent = rest.length > 1 ? getPath(out[idx], rest.slice(0, -1)) : out[idx];
78
+ if (parent == null || typeof parent !== "object") continue;
79
+ const current = parent[rest[rest.length - 1]];
80
+ if (current !== void 0 && typeof current !== "string") continue;
81
+ replace(idx, setPath(out[idx], rest, value));
82
+ }
83
+ }
84
+ for (let i = 0; i < out.length; i++) {
85
+ const updated = localizeNestedBlock(out[i], overlay);
86
+ if (updated !== out[i]) replace(i, updated);
54
87
  }
55
88
  return out;
56
89
  }
@@ -66,9 +99,21 @@ function localizePage(page, locale, defaultLocale) {
66
99
  }
67
100
  return out;
68
101
  }
102
+ function collectBlockKeys(blocks, into = /* @__PURE__ */ new Set()) {
103
+ for (const b of blocks ?? []) {
104
+ if (!b || typeof b !== "object") continue;
105
+ if (typeof b._key === "string") into.add(b._key);
106
+ const a = b;
107
+ if (Array.isArray(a.columns)) for (const c of a.columns) collectBlockKeys(c?.blocks, into);
108
+ if (Array.isArray(a.tabs)) for (const t of a.tabs) collectBlockKeys(t?.blocks, into);
109
+ if (Array.isArray(a.main)) collectBlockKeys(a.main, into);
110
+ if (Array.isArray(a.aside)) collectBlockKeys(a.aside, into);
111
+ }
112
+ return into;
113
+ }
69
114
  function pruneTranslations(translations, blocks) {
70
115
  if (!translations) return void 0;
71
- const keys = new Set((blocks ?? []).map((b) => b?._key).filter(Boolean));
116
+ const keys = collectBlockKeys(blocks);
72
117
  const out = {};
73
118
  for (const [locale, overlay] of Object.entries(translations)) {
74
119
  const kept = Object.fromEntries(
@@ -80,6 +125,7 @@ function pruneTranslations(translations, blocks) {
80
125
  }
81
126
  export {
82
127
  TranslationsSchema,
128
+ collectBlockKeys,
83
129
  localizeBlocks,
84
130
  localizePage,
85
131
  localizedPath,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/i18n/overlay.ts"],"sourcesContent":["/**\n * Localisatie (5.0) — primo's locale-per-waarde vertaald naar een ADDITIEVE overlay:\n * de blokkenstructuur blijft canoniek (de default-taal), en per pagina/symbool ligt er\n * een `translations`-map overheen: `{ [locale]: { \"<blokKey>.<veldpad>\": \"vertaalde tekst\" } }`.\n * Speciale sleutels: `title` (paginatitel) en `seo.<veld>` (SEO-overlay). Waarden zijn altijd\n * strings — structuurwijzigingen (blokken toevoegen/verplaatsen) gebeuren alléén canoniek.\n *\n * Server-safe: geen React, geen Firebase — bruikbaar in server/pages.ts én in de editor.\n */\nimport { z } from \"zod\";\n\n/** `{ [locale]: { \"hero1.title\": \"Willkommen\", \"seo.description\": \"…\" } }` */\nexport const TranslationsSchema = z.record(\n z.string().min(2).max(10),\n z.record(z.string().min(1).max(300), z.string().max(20_000)).refine((m) => Object.keys(m).length <= 2000, {\n message: \"te veel vertaalsleutels (max 2000 per taal)\",\n }),\n);\nexport type Translations = z.infer<typeof TranslationsSchema>;\n\nexport interface LocaleConfig {\n /** Alle talen van de site, default-taal eerst (bv. [\"nl\", \"de\", \"en\"]). */\n locales: string[];\n defaultLocale: string;\n}\n\n/** Normaliseert de site-config naar een LocaleConfig (default: alleen `lang` ?? \"nl\"). */\nexport function siteLocales(site: { locales?: string[]; defaultLocale?: string; lang?: string }): LocaleConfig {\n const defaultLocale = site.defaultLocale ?? site.lang ?? \"nl\";\n const locales = [defaultLocale, ...(site.locales ?? []).filter((l) => l !== defaultLocale)];\n return { locales: [...new Set(locales)], defaultLocale };\n}\n\n/**\n * Herkent een taalprefix in Next-catch-all-segmenten: `/de/over-ons` → locale \"de\", pad \"/over-ons\".\n * De default-taal heeft géén prefix (canonieke URL's blijven ongewijzigd).\n */\nexport function parseLocaleSlugs(\n slugs: string[] | undefined,\n cfg: LocaleConfig,\n): { locale: string; slugs: string[] } {\n const list = slugs ?? [];\n const first = list[0];\n if (first && first !== cfg.defaultLocale && cfg.locales.includes(first)) {\n return { locale: first, slugs: list.slice(1) };\n }\n return { locale: cfg.defaultLocale, slugs: list };\n}\n\n/** Pad mét taalprefix voor niet-default talen: (\"/over-ons\", \"de\") → \"/de/over-ons\". */\nexport function localizedPath(path: string, locale: string, defaultLocale: string): string {\n if (locale === defaultLocale) return path;\n return `/${locale}${path === \"/\" ? \"\" : path}`;\n}\n\n/* Zelfde gepunte-pad-helpers als de visuele editor: immutable schrijven op \"items.0.title\". */\nfunction getPath(obj: unknown, keys: string[]): unknown {\n return keys.reduce<unknown>((o, k) => (o == null ? undefined : (o as Record<string, unknown>)[k]), obj);\n}\nfunction setPath<T>(obj: T, keys: string[], value: unknown): T {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const clone = (v: unknown): any => (Array.isArray(v) ? [...v] : { ...(v as object) });\n const root = clone(obj);\n let cur = root;\n for (let i = 0; i < keys.length - 1; i++) {\n cur[keys[i]] = clone(cur[keys[i]]);\n cur = cur[keys[i]];\n }\n cur[keys[keys.length - 1]] = value;\n return root as T;\n}\n\n/**\n * Past één taal-overlay toe op een lijst blokken. Alleen bestaande, tekstuele velden worden\n * overschreven: het doelpad moet oplosbaar zijn en de canonieke waarde moet een string (of leeg)\n * zijn — zo kan een verouderde vertaalsleutel nooit de structuur breken (overlay-drift).\n */\nexport function localizeBlocks<T extends { _key?: string }>(blocks: T[], overlay: Record<string, string> | undefined): T[] {\n if (!overlay || blocks.length === 0) return blocks;\n let out = blocks;\n for (const [key, value] of Object.entries(overlay)) {\n if (typeof value !== \"string\") continue;\n const [blockKey, ...rest] = key.split(\".\");\n if (rest.length === 0) continue;\n const idx = out.findIndex((b) => b?._key === blockKey);\n if (idx < 0) continue;\n const parent = rest.length > 1 ? getPath(out[idx], rest.slice(0, -1)) : out[idx];\n if (parent == null || typeof parent !== \"object\") continue;\n const current = (parent as Record<string, unknown>)[rest[rest.length - 1]];\n if (current !== undefined && typeof current !== \"string\") continue;\n if (out === blocks) out = [...blocks];\n out[idx] = setPath(out[idx], rest, value);\n }\n return out;\n}\n\n/**\n * Past een taal-overlay toe op een hele pagina (titel + seo + blokken). Geeft de pagina\n * ongewijzigd terug voor de default-taal of zonder vertalingen — nul overhead voor\n * ééntalige sites.\n */\nexport function localizePage<\n T extends { title?: string; seo?: Record<string, unknown>; blocks?: unknown[]; translations?: Translations },\n>(page: T, locale: string | undefined, defaultLocale: string): T {\n if (!locale || locale === defaultLocale) return page;\n const overlay = page.translations?.[locale];\n if (!overlay) return page;\n let out: T = { ...page, blocks: localizeBlocks((page.blocks ?? []) as { _key?: string }[], overlay) };\n for (const [key, value] of Object.entries(overlay)) {\n if (typeof value !== \"string\") continue;\n if (key === \"title\") out = { ...out, title: value };\n else if (key.startsWith(\"seo.\")) out = { ...out, seo: { ...(out.seo ?? {}), [key.slice(4)]: value } };\n }\n return out;\n}\n\n/**\n * Ruimt wees-vertalingen op: sleutels die naar een niet (meer) bestaand blok wijzen vervallen\n * (`title` en `seo.*` blijven altijd). Draait bij publiceren zodat de overlay niet oneindig\n * aangroeit wanneer blokken verwijderd worden.\n */\nexport function pruneTranslations(\n translations: Translations | undefined,\n blocks: Array<{ _key?: string }> | undefined,\n): Translations | undefined {\n if (!translations) return undefined;\n const keys = new Set((blocks ?? []).map((b) => b?._key).filter(Boolean));\n const out: Translations = {};\n for (const [locale, overlay] of Object.entries(translations)) {\n const kept = Object.fromEntries(\n Object.entries(overlay).filter(([k]) => k === \"title\" || k.startsWith(\"seo.\") || keys.has(k.split(\".\")[0])),\n );\n if (Object.keys(kept).length > 0) out[locale] = kept;\n }\n return Object.keys(out).length > 0 ? out : undefined;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/i18n/overlay.ts"],"sourcesContent":["/**\n * Localisatie (5.0) — primo's locale-per-waarde vertaald naar een ADDITIEVE overlay:\n * de blokkenstructuur blijft canoniek (de default-taal), en per pagina/symbool ligt er\n * een `translations`-map overheen: `{ [locale]: { \"<blokKey>.<veldpad>\": \"vertaalde tekst\" } }`.\n * Speciale sleutels: `title` (paginatitel) en `seo.<veld>` (SEO-overlay). Waarden zijn altijd\n * strings — structuurwijzigingen (blokken toevoegen/verplaatsen) gebeuren alléén canoniek.\n *\n * Server-safe: geen React, geen Firebase — bruikbaar in server/pages.ts én in de editor.\n */\nimport { z } from \"zod\";\n\n/** `{ [locale]: { \"hero1.title\": \"Willkommen\", \"seo.description\": \"…\" } }` */\nexport const TranslationsSchema = z.record(\n z.string().min(2).max(10),\n z.record(z.string().min(1).max(300), z.string().max(20_000)).refine((m) => Object.keys(m).length <= 2000, {\n message: \"te veel vertaalsleutels (max 2000 per taal)\",\n }),\n);\nexport type Translations = z.infer<typeof TranslationsSchema>;\n\nexport interface LocaleConfig {\n /** Alle talen van de site, default-taal eerst (bv. [\"nl\", \"de\", \"en\"]). */\n locales: string[];\n defaultLocale: string;\n}\n\n/** Normaliseert de site-config naar een LocaleConfig (default: alleen `lang` ?? \"nl\"). */\nexport function siteLocales(site: { locales?: string[]; defaultLocale?: string; lang?: string }): LocaleConfig {\n const defaultLocale = site.defaultLocale ?? site.lang ?? \"nl\";\n const locales = [defaultLocale, ...(site.locales ?? []).filter((l) => l !== defaultLocale)];\n return { locales: [...new Set(locales)], defaultLocale };\n}\n\n/**\n * Herkent een taalprefix in Next-catch-all-segmenten: `/de/over-ons` → locale \"de\", pad \"/over-ons\".\n * De default-taal heeft géén prefix (canonieke URL's blijven ongewijzigd).\n */\nexport function parseLocaleSlugs(\n slugs: string[] | undefined,\n cfg: LocaleConfig,\n): { locale: string; slugs: string[] } {\n const list = slugs ?? [];\n const first = list[0];\n if (first && first !== cfg.defaultLocale && cfg.locales.includes(first)) {\n return { locale: first, slugs: list.slice(1) };\n }\n return { locale: cfg.defaultLocale, slugs: list };\n}\n\n/** Pad mét taalprefix voor niet-default talen: (\"/over-ons\", \"de\") → \"/de/over-ons\". */\nexport function localizedPath(path: string, locale: string, defaultLocale: string): string {\n if (locale === defaultLocale) return path;\n return `/${locale}${path === \"/\" ? \"\" : path}`;\n}\n\n/* Zelfde gepunte-pad-helpers als de visuele editor: immutable schrijven op \"items.0.title\". */\nfunction getPath(obj: unknown, keys: string[]): unknown {\n return keys.reduce<unknown>((o, k) => (o == null ? undefined : (o as Record<string, unknown>)[k]), obj);\n}\nfunction setPath<T>(obj: T, keys: string[], value: unknown): T {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const clone = (v: unknown): any => (Array.isArray(v) ? [...v] : { ...(v as object) });\n const root = clone(obj);\n let cur = root;\n for (let i = 0; i < keys.length - 1; i++) {\n cur[keys[i]] = clone(cur[keys[i]]);\n cur = cur[keys[i]];\n }\n cur[keys[keys.length - 1]] = value;\n return root as T;\n}\n\n/* Geneste blok-containers (columns/withSidebar/tabs) — structureel herkend zodat de overlay ook\n * blokken BINNEN een indelingsblok bereikt. Elke geneste blok heeft z'n eigen unieke `_key`, dus de\n * vertaalsleutel blijft gewoon \"<blokKey>.<veldpad>\"; we hoeven alleen dieper te zoeken. */\ntype AnyBlock = { _key?: string } & Record<string, unknown>;\n\n/** Past de overlay toe op de geneste blok-lijsten van één (indelings)blok. Referentie-behoudend:\n * ongewijzigd → zelfde object terug (nul overhead voor pagina's zonder vertaalde geneste blokken). */\nfunction localizeNestedBlock(b: AnyBlock, overlay: Record<string, string>): AnyBlock {\n let cur = b;\n const localizeSlot = <S extends { blocks?: AnyBlock[] }>(slot: S): S => {\n if (!Array.isArray(slot?.blocks)) return slot;\n const nb = localizeBlocks(slot.blocks, overlay);\n return nb === slot.blocks ? slot : { ...slot, blocks: nb };\n };\n if (Array.isArray(cur.columns)) {\n const cols = cur.columns as Array<{ blocks?: AnyBlock[] }>;\n const next = cols.map(localizeSlot);\n if (next.some((c, i) => c !== cols[i])) cur = { ...cur, columns: next };\n }\n if (Array.isArray(cur.tabs)) {\n const tabs = cur.tabs as Array<{ blocks?: AnyBlock[] }>;\n const next = tabs.map(localizeSlot);\n if (next.some((t, i) => t !== tabs[i])) cur = { ...cur, tabs: next };\n }\n for (const field of [\"main\", \"aside\"] as const) {\n if (Array.isArray(cur[field])) {\n const nb = localizeBlocks(cur[field] as AnyBlock[], overlay);\n if (nb !== cur[field]) cur = { ...cur, [field]: nb };\n }\n }\n return cur;\n}\n\n/**\n * Past één taal-overlay toe op een lijst blokken — inclusief blokken genest in indelingsblokken\n * (columns/withSidebar/tabs). Alleen bestaande, tekstuele velden worden overschreven: het doelpad\n * moet oplosbaar zijn en de canonieke waarde moet een string (of leeg) zijn — zo kan een verouderde\n * vertaalsleutel nooit de structuur breken (overlay-drift).\n */\nexport function localizeBlocks<T extends { _key?: string }>(blocks: T[], overlay: Record<string, string> | undefined): T[] {\n if (!overlay || blocks.length === 0) return blocks;\n let out = blocks;\n const replace = (idx: number, value: T) => {\n if (out === blocks) out = [...blocks];\n out[idx] = value;\n };\n for (const [key, value] of Object.entries(overlay)) {\n if (typeof value !== \"string\") continue;\n const [blockKey, ...rest] = key.split(\".\");\n if (rest.length === 0) continue;\n const idx = out.findIndex((b) => b?._key === blockKey);\n if (idx >= 0) {\n const parent = rest.length > 1 ? getPath(out[idx], rest.slice(0, -1)) : out[idx];\n if (parent == null || typeof parent !== \"object\") continue;\n const current = (parent as Record<string, unknown>)[rest[rest.length - 1]];\n if (current !== undefined && typeof current !== \"string\") continue;\n replace(idx, setPath(out[idx], rest, value));\n }\n }\n // Recursie in indelingsblokken: pas dezelfde overlay toe op geneste blok-lijsten. Alleen bij een\n // daadwerkelijke wijziging (referentie-check) wordt het container-blok gekloond — ongewijzigde\n // pagina's houden nul overhead.\n for (let i = 0; i < out.length; i++) {\n const updated = localizeNestedBlock(out[i] as AnyBlock, overlay);\n if (updated !== (out[i] as AnyBlock)) replace(i, updated as T);\n }\n return out;\n}\n\n/**\n * Past een taal-overlay toe op een hele pagina (titel + seo + blokken). Geeft de pagina\n * ongewijzigd terug voor de default-taal of zonder vertalingen — nul overhead voor\n * ééntalige sites.\n */\nexport function localizePage<\n T extends { title?: string; seo?: Record<string, unknown>; blocks?: unknown[]; translations?: Translations },\n>(page: T, locale: string | undefined, defaultLocale: string): T {\n if (!locale || locale === defaultLocale) return page;\n const overlay = page.translations?.[locale];\n if (!overlay) return page;\n let out: T = { ...page, blocks: localizeBlocks((page.blocks ?? []) as { _key?: string }[], overlay) };\n for (const [key, value] of Object.entries(overlay)) {\n if (typeof value !== \"string\") continue;\n if (key === \"title\") out = { ...out, title: value };\n else if (key.startsWith(\"seo.\")) out = { ...out, seo: { ...(out.seo ?? {}), [key.slice(4)]: value } };\n }\n return out;\n}\n\n/**\n * Verzamelt ALLE blok-keys, inclusief blokken genest in indelingsblokken (columns/withSidebar/tabs).\n * Gedeeld door pruneTranslations hier en sanitizeTranslations in de Cloud Functions — beide moeten\n * geneste vertalingen als geldig zien, anders worden ze bij publiceren als \"wees\" verwijderd.\n */\nexport function collectBlockKeys(blocks: Array<{ _key?: string }> | undefined, into: Set<string> = new Set()): Set<string> {\n for (const b of blocks ?? []) {\n if (!b || typeof b !== \"object\") continue;\n if (typeof b._key === \"string\") into.add(b._key);\n const a = b as AnyBlock;\n if (Array.isArray(a.columns)) for (const c of a.columns as Array<{ blocks?: AnyBlock[] }>) collectBlockKeys(c?.blocks, into);\n if (Array.isArray(a.tabs)) for (const t of a.tabs as Array<{ blocks?: AnyBlock[] }>) collectBlockKeys(t?.blocks, into);\n if (Array.isArray(a.main)) collectBlockKeys(a.main as AnyBlock[], into);\n if (Array.isArray(a.aside)) collectBlockKeys(a.aside as AnyBlock[], into);\n }\n return into;\n}\n\n/**\n * Ruimt wees-vertalingen op: sleutels die naar een niet (meer) bestaand blok wijzen vervallen\n * (`title` en `seo.*` blijven altijd). Draait bij publiceren zodat de overlay niet oneindig\n * aangroeit wanneer blokken verwijderd worden. Geneste blokken tellen als bestaand.\n */\nexport function pruneTranslations(\n translations: Translations | undefined,\n blocks: Array<{ _key?: string }> | undefined,\n): Translations | undefined {\n if (!translations) return undefined;\n const keys = collectBlockKeys(blocks);\n const out: Translations = {};\n for (const [locale, overlay] of Object.entries(translations)) {\n const kept = Object.fromEntries(\n Object.entries(overlay).filter(([k]) => k === \"title\" || k.startsWith(\"seo.\") || keys.has(k.split(\".\")[0])),\n );\n if (Object.keys(kept).length > 0) out[locale] = kept;\n }\n return Object.keys(out).length > 0 ? out : undefined;\n}\n"],"mappings":"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","names":[]}
@@ -5,6 +5,7 @@
5
5
  import "server-only";
6
6
  export { getAdminApp, getAdminAuth, getAdminDb } from "../firebase/admin";
7
7
  export { sanitizeRichText } from "./sanitize";
8
+ export { jsonLdHtml } from "../blocks/builtins/jsonld";
8
9
  export { buildMetadata, buildJsonLd, buildSitemap, buildRobots, siteOrigin, businessJsonLd, breadcrumbListJsonLd, articleJsonLd, productJsonLd, openingHoursJsonLd, galleryJsonLd, videoObjectJsonLd, type BusinessInfo, } from "./seo";
9
10
  export { validateEnv, assertEnv, type EnvReport } from "./env";
10
11
  export { pathFromSlugs, listPublishedPages, getPublishedPageByPath, getPublishedPageByPathExpanded, getSiteSymbols, expandSymbols, getSiteCustomBlocks, getDraftPageById, getDraftPageByPath, type StoredPage, } from "./pages";
@@ -1,6 +1,7 @@
1
1
  import "server-only";
2
2
  import { getAdminApp, getAdminAuth, getAdminDb } from "../firebase/admin";
3
3
  import { sanitizeRichText } from "./sanitize";
4
+ import { jsonLdHtml } from "../blocks/builtins/jsonld";
4
5
  import {
5
6
  buildMetadata,
6
7
  buildJsonLd,
@@ -57,6 +58,7 @@ export {
57
58
  getPublishedPageByPathExpanded,
58
59
  getSiteCustomBlocks,
59
60
  getSiteSymbols,
61
+ jsonLdHtml,
60
62
  listPublishedPages,
61
63
  localizeBlocks,
62
64
  localizePage,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/server/index.ts"],"sourcesContent":["/**\n * @xiwebdesign/xicms-core/server — server-only helpers (firebase-admin, sanitization, SEO,\n * pagina-reads). NOOIT importeren in client-componenten.\n */\nimport \"server-only\";\n\nexport { getAdminApp, getAdminAuth, getAdminDb } from \"../firebase/admin\";\nexport { sanitizeRichText } from \"./sanitize\";\nexport {\n buildMetadata,\n buildJsonLd,\n buildSitemap,\n buildRobots,\n siteOrigin,\n businessJsonLd,\n breadcrumbListJsonLd,\n articleJsonLd,\n productJsonLd,\n openingHoursJsonLd,\n galleryJsonLd,\n videoObjectJsonLd,\n type BusinessInfo,\n} from \"./seo\";\nexport { validateEnv, assertEnv, type EnvReport } from \"./env\";\nexport {\n pathFromSlugs,\n listPublishedPages,\n getPublishedPageByPath,\n getPublishedPageByPathExpanded,\n getSiteSymbols,\n expandSymbols,\n getSiteCustomBlocks,\n getDraftPageById,\n getDraftPageByPath,\n type StoredPage,\n} from \"./pages\";\n// Localisatie (5.0) — server-safe overlay-helpers, ook bruikbaar in route-handlers/layouts.\nexport {\n siteLocales,\n parseLocaleSlugs,\n localizedPath,\n localizePage,\n localizeBlocks,\n pruneTranslations,\n TranslationsSchema,\n type Translations,\n type LocaleConfig,\n} from \"../i18n/overlay\";\n"],"mappings":"AAIA,OAAO;AAEP,SAAS,aAAa,cAAc,kBAAkB;AACtD,SAAS,wBAAwB;AACjC;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,OAEK;AACP,SAAS,aAAa,iBAAiC;AACvD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAEP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;","names":[]}
1
+ {"version":3,"sources":["../../src/server/index.ts"],"sourcesContent":["/**\n * @xiwebdesign/xicms-core/server — server-only helpers (firebase-admin, sanitization, SEO,\n * pagina-reads). NOOIT importeren in client-componenten.\n */\nimport \"server-only\";\n\nexport { getAdminApp, getAdminAuth, getAdminDb } from \"../firebase/admin\";\nexport { sanitizeRichText } from \"./sanitize\";\n// Veilige JSON-LD-serialisatie voor <script type=\"application/ld+json\"> in consumer-routes\n// (escapet `<` zodat klant-tekst met </script> de pagina niet kan breken).\nexport { jsonLdHtml } from \"../blocks/builtins/jsonld\";\nexport {\n buildMetadata,\n buildJsonLd,\n buildSitemap,\n buildRobots,\n siteOrigin,\n businessJsonLd,\n breadcrumbListJsonLd,\n articleJsonLd,\n productJsonLd,\n openingHoursJsonLd,\n galleryJsonLd,\n videoObjectJsonLd,\n type BusinessInfo,\n} from \"./seo\";\nexport { validateEnv, assertEnv, type EnvReport } from \"./env\";\nexport {\n pathFromSlugs,\n listPublishedPages,\n getPublishedPageByPath,\n getPublishedPageByPathExpanded,\n getSiteSymbols,\n expandSymbols,\n getSiteCustomBlocks,\n getDraftPageById,\n getDraftPageByPath,\n type StoredPage,\n} from \"./pages\";\n// Localisatie (5.0) — server-safe overlay-helpers, ook bruikbaar in route-handlers/layouts.\nexport {\n siteLocales,\n parseLocaleSlugs,\n localizedPath,\n localizePage,\n localizeBlocks,\n pruneTranslations,\n TranslationsSchema,\n type Translations,\n type LocaleConfig,\n} from \"../i18n/overlay\";\n"],"mappings":"AAIA,OAAO;AAEP,SAAS,aAAa,cAAc,kBAAkB;AACtD,SAAS,wBAAwB;AAGjC,SAAS,kBAAkB;AAC3B;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,OAEK;AACP,SAAS,aAAa,iBAAiC;AACvD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAEP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;","names":[]}
@@ -14,9 +14,20 @@ function pathFromSlugs(slugs) {
14
14
  }
15
15
  function parse(id, data) {
16
16
  const res = PageSchema.safeParse(data);
17
- if (!res.success) return null;
17
+ if (!res.success) {
18
+ console.warn(`[xicms] pagina "${id}" faalt schema-validatie en wordt overgeslagen:`, res.error.issues[0]);
19
+ return null;
20
+ }
18
21
  return { id, ...res.data };
19
22
  }
23
+ async function safeAdminRead(fallback, what, fn) {
24
+ try {
25
+ return await fn();
26
+ } catch (e) {
27
+ console.warn(`[xicms] Admin-read mislukt (${what}) \u2014 fallback gebruikt:`, e?.message ?? e);
28
+ return fallback;
29
+ }
30
+ }
20
31
  function decode(v) {
21
32
  if (v == null) return null;
22
33
  if ("stringValue" in v) return v.stringValue;
@@ -64,17 +75,21 @@ async function restQuery(siteId, filters, limit) {
64
75
  async function listPublishedPages(siteId) {
65
76
  const adb = getAdminDb();
66
77
  if (adb) {
67
- const snap = await adb.collection(`sites/${siteId}/pages`).where("status", "==", "published").get();
68
- return snap.docs.map((d) => parse(d.id, d.data())).filter((p) => p !== null);
78
+ return safeAdminRead([], `listPublishedPages ${siteId}`, async () => {
79
+ const snap = await adb.collection(`sites/${siteId}/pages`).where("status", "==", "published").get();
80
+ return snap.docs.map((d) => parse(d.id, d.data())).filter((p) => p !== null);
81
+ });
69
82
  }
70
83
  return restQuery(siteId, [eq("status", "published")]);
71
84
  }
72
85
  async function getPublishedPageByPath(siteId, path) {
73
86
  const adb = getAdminDb();
74
87
  if (adb) {
75
- const snap = await adb.collection(`sites/${siteId}/pages`).where("status", "==", "published").where("path", "==", path).limit(1).get();
76
- const d = snap.docs[0];
77
- return d ? parse(d.id, d.data()) : null;
88
+ return safeAdminRead(null, `getPublishedPageByPath ${siteId}${path}`, async () => {
89
+ const snap = await adb.collection(`sites/${siteId}/pages`).where("status", "==", "published").where("path", "==", path).limit(1).get();
90
+ const d = snap.docs[0];
91
+ return d ? parse(d.id, d.data()) : null;
92
+ });
78
93
  }
79
94
  const rows = await restQuery(siteId, [eq("status", "published"), eq("path", path)], 1);
80
95
  return rows[0] ?? null;
@@ -82,21 +97,27 @@ async function getPublishedPageByPath(siteId, path) {
82
97
  async function getDraftPageById(siteId, pageId) {
83
98
  const db = getAdminDb();
84
99
  if (!db) return null;
85
- const d = await db.doc(`sites/${siteId}/drafts/${pageId}`).get();
86
- return d.exists ? parse(d.id, d.data()) : null;
100
+ return safeAdminRead(null, `getDraftPageById ${siteId}/${pageId}`, async () => {
101
+ const d = await db.doc(`sites/${siteId}/drafts/${pageId}`).get();
102
+ return d.exists ? parse(d.id, d.data()) : null;
103
+ });
87
104
  }
88
105
  async function getDraftPageByPath(siteId, path) {
89
106
  const db = getAdminDb();
90
107
  if (!db) return null;
91
- const snap = await db.collection(`sites/${siteId}/drafts`).where("path", "==", path).limit(1).get();
92
- const d = snap.docs[0];
93
- return d ? parse(d.id, d.data()) : null;
108
+ return safeAdminRead(null, `getDraftPageByPath ${siteId}${path}`, async () => {
109
+ const snap = await db.collection(`sites/${siteId}/drafts`).where("path", "==", path).limit(1).get();
110
+ const d = snap.docs[0];
111
+ return d ? parse(d.id, d.data()) : null;
112
+ });
94
113
  }
95
114
  async function readCollection(siteId, collectionId) {
96
115
  const adb = getAdminDb();
97
116
  if (adb) {
98
- const snap = await adb.collection(`sites/${siteId}/${collectionId}`).get();
99
- return snap.docs.map((d) => ({ id: d.id, data: d.data() }));
117
+ return safeAdminRead([], `readCollection ${siteId}/${collectionId}`, async () => {
118
+ const snap = await adb.collection(`sites/${siteId}/${collectionId}`).get();
119
+ return snap.docs.map((d) => ({ id: d.id, data: d.data() }));
120
+ });
100
121
  }
101
122
  const apiKey = process.env.NEXT_PUBLIC_FIREBASE_API_KEY;
102
123
  const projectId = process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/server/pages.ts"],"sourcesContent":["import \"server-only\";\n\nimport { getAdminDb } from \"../firebase/admin\";\nimport { PageSchema, type Page } from \"../schema/pages\";\nimport { SymbolSchema, type StoredSymbol } from \"../schema/symbols\";\nimport { CustomBlockSchema, type CustomBlock } from \"../schema/customBlocks\";\nimport { localizePage, localizeBlocks } from \"../i18n/overlay\";\n\nexport type StoredPage = Page & { id: string };\n\nfunction toPath(slugs: string[] | undefined): string {\n const clean = (slugs ?? []).filter(Boolean);\n return \"/\" + clean.join(\"/\");\n}\n\n/** Pad uit Next catch-all params (`[[...slug]]`). \"/\" voor de root. */\nexport function pathFromSlugs(slugs: string[] | undefined): string {\n const p = toPath(slugs);\n return p === \"/\" ? \"/\" : p.replace(/\\/$/, \"\");\n}\n\nfunction parse(id: string, data: unknown): StoredPage | null {\n const res = PageSchema.safeParse(data);\n if (!res.success) return null;\n return { id, ...res.data };\n}\n\n/* ---------------- keyless public reads via Firestore REST (alleen apiKey) ----------------\n * Gepubliceerde pagina's zijn publiek leesbaar (rules). De Firestore REST-API honoreert die\n * rules met enkel de web-apiKey (geen Admin-credentials, geen client-SDK), dus dit werkt\n * server-side op keyless deployments (org-policy zonder service-account-keys).\n */\ntype FsValue = Record<string, unknown>;\nfunction decode(v: FsValue): unknown {\n if (v == null) return null;\n if (\"stringValue\" in v) return v.stringValue;\n if (\"integerValue\" in v) return Number(v.integerValue);\n if (\"doubleValue\" in v) return v.doubleValue;\n if (\"booleanValue\" in v) return v.booleanValue;\n if (\"nullValue\" in v) return null;\n if (\"timestampValue\" in v) return v.timestampValue;\n if (\"referenceValue\" in v) return v.referenceValue;\n if (\"geoPointValue\" in v) return v.geoPointValue;\n if (\"arrayValue\" in v) return (((v.arrayValue as FsValue)?.values as FsValue[]) ?? []).map(decode);\n if (\"mapValue\" in v) return decodeFields(((v.mapValue as FsValue)?.fields as Record<string, FsValue>) ?? {});\n return null;\n}\nfunction decodeFields(fields: Record<string, FsValue>): Record<string, unknown> {\n return Object.fromEntries(Object.entries(fields).map(([k, x]) => [k, decode(x)]));\n}\n\ninterface Filter { fieldFilter: { field: { fieldPath: string }; op: \"EQUAL\"; value: { stringValue: string } } }\nfunction eq(fieldPath: string, value: string): Filter {\n return { fieldFilter: { field: { fieldPath }, op: \"EQUAL\", value: { stringValue: value } } };\n}\n\nasync function restQuery(siteId: string, filters: Filter[], limit?: number): Promise<StoredPage[]> {\n const apiKey = process.env.NEXT_PUBLIC_FIREBASE_API_KEY;\n const projectId = process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID;\n if (!apiKey || !projectId) return [];\n const url = `https://firestore.googleapis.com/v1/projects/${projectId}/databases/(default)/documents/sites/${siteId}:runQuery?key=${apiKey}`;\n const where = filters.length === 1 ? filters[0] : { compositeFilter: { op: \"AND\", filters } };\n const structuredQuery: Record<string, unknown> = { from: [{ collectionId: \"pages\" }], where };\n if (limit) structuredQuery.limit = limit;\n // Netwerk-/parsefouten mogen een publieke pagina nooit laten crashen (500) — dan liever een\n // lege lijst (Next toont dan een 404 of de fallback, en de volgende revalidate probeert opnieuw).\n try {\n const res = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ structuredQuery }),\n // Pagina's zijn ISR-gecached; next-cache mag dit hergebruiken.\n next: { revalidate: 300 },\n });\n if (!res.ok) return [];\n const rows = (await res.json()) as { document?: { name: string; fields: Record<string, FsValue> } }[];\n return rows\n .filter((r) => r.document)\n .map((r) => parse(r.document!.name.split(\"/\").pop()!, decodeFields(r.document!.fields)))\n .filter((p): p is StoredPage => p !== null);\n } catch (err) {\n console.error(\"[xicms] Firestore REST-query mislukt:\", err);\n return [];\n }\n}\n\n/** Alle gepubliceerde pagina's (sitemap / generateStaticParams). Keyless via REST als geen Admin. */\nexport async function listPublishedPages(siteId: string): Promise<StoredPage[]> {\n const adb = getAdminDb();\n if (adb) {\n const snap = await adb.collection(`sites/${siteId}/pages`).where(\"status\", \"==\", \"published\").get();\n return snap.docs.map((d) => parse(d.id, d.data())).filter((p): p is StoredPage => p !== null);\n }\n return restQuery(siteId, [eq(\"status\", \"published\")]);\n}\n\n/** Eén gepubliceerde pagina op pad. Keyless via REST als geen Admin. */\nexport async function getPublishedPageByPath(siteId: string, path: string): Promise<StoredPage | null> {\n const adb = getAdminDb();\n if (adb) {\n const snap = await adb\n .collection(`sites/${siteId}/pages`)\n .where(\"status\", \"==\", \"published\")\n .where(\"path\", \"==\", path)\n .limit(1)\n .get();\n const d = snap.docs[0];\n return d ? parse(d.id, d.data()) : null;\n }\n const rows = await restQuery(siteId, [eq(\"status\", \"published\"), eq(\"path\", path)], 1);\n return rows[0] ?? null;\n}\n\n/** Draft-versie (voor preview achter auth). */\nexport async function getDraftPageById(siteId: string, pageId: string): Promise<StoredPage | null> {\n const db = getAdminDb();\n if (!db) return null;\n const d = await db.doc(`sites/${siteId}/drafts/${pageId}`).get();\n return d.exists ? parse(d.id, d.data()) : null;\n}\n\n/** Draft op pad (preview). */\nexport async function getDraftPageByPath(siteId: string, path: string): Promise<StoredPage | null> {\n const db = getAdminDb();\n if (!db) return null;\n const snap = await db.collection(`sites/${siteId}/drafts`).where(\"path\", \"==\", path).limit(1).get();\n const d = snap.docs[0];\n return d ? parse(d.id, d.data()) : null;\n}\n\n/* ---------------- symbols & custom blocks (5.0) ---------------- */\n\n/** Generieke publieke collectie-read onder sites/{siteId}/ — Admin als beschikbaar, anders REST. */\nasync function readCollection(siteId: string, collectionId: string): Promise<{ id: string; data: Record<string, unknown> }[]> {\n const adb = getAdminDb();\n if (adb) {\n const snap = await adb.collection(`sites/${siteId}/${collectionId}`).get();\n return snap.docs.map((d) => ({ id: d.id, data: d.data() as Record<string, unknown> }));\n }\n const apiKey = process.env.NEXT_PUBLIC_FIREBASE_API_KEY;\n const projectId = process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID;\n if (!apiKey || !projectId) return [];\n const url = `https://firestore.googleapis.com/v1/projects/${projectId}/databases/(default)/documents/sites/${siteId}:runQuery?key=${apiKey}`;\n try {\n const res = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ structuredQuery: { from: [{ collectionId }] } }),\n next: { revalidate: 300 },\n });\n if (!res.ok) return [];\n const rows = (await res.json()) as { document?: { name: string; fields: Record<string, FsValue> } }[];\n return rows\n .filter((r) => r.document)\n .map((r) => ({ id: r.document!.name.split(\"/\").pop()!, data: decodeFields(r.document!.fields ?? {}) }));\n } catch (err) {\n console.error(`[xicms] Firestore REST-read van ${collectionId} mislukt:`, err);\n return [];\n }\n}\n\n/** Alle symbolen van een site (gevalideerd; ongeldige overgeslagen met waarschuwing). */\nexport async function getSiteSymbols(siteId: string): Promise<StoredSymbol[]> {\n const rows = await readCollection(siteId, \"symbols\");\n const out: StoredSymbol[] = [];\n for (const r of rows) {\n const res = SymbolSchema.safeParse(r.data);\n if (res.success) out.push({ id: r.id, ...res.data });\n else console.warn(`[xicms] symbool \"${r.id}\" ongeldig — overgeslagen.`);\n }\n return out;\n}\n\n/**\n * Vervangt symbolRef-blokken door de blokken van hun symbool (één niveau — symbolen kunnen zelf\n * geen symbolRef bevatten). `_key`s van de ingevoegde blokken worden geprefixt met de ref-key\n * zodat ze uniek blijven wanneer hetzelfde symbool vaker op een pagina staat. Verweesde refs\n * verdwijnen stil (de pagina mag nooit breken op een verwijderd symbool).\n */\nexport function expandSymbols<T extends { blocks?: unknown[] }>(page: T, symbols: StoredSymbol[]): T {\n const byId = new Map(symbols.map((s) => [s.id, s]));\n const blocks = (page.blocks ?? []).flatMap((b) => {\n const blk = b as { _type?: string; _key?: string; symbolId?: string };\n if (blk?._type !== \"symbolRef\") return [b];\n const sym = blk.symbolId ? byId.get(blk.symbolId) : undefined;\n if (!sym) return [];\n return sym.blocks.map((sb, i) => ({ ...(sb as Record<string, unknown>), _key: `${blk._key ?? \"sym\"}-${(sb as { _key?: string })._key ?? i}` }));\n });\n return { ...page, blocks };\n}\n\n/**\n * Gepubliceerde pagina op pad mét symbolRef-expansie (SEO: symboolinhoud zit in de SSR-HTML).\n * Geef `locale` mee (een NIET-default taal) om de vertaal-overlay toe te passen — op de pagina\n * én op de ingevoegde symbolen (elk symbool draagt zijn eigen `translations`).\n */\nexport async function getPublishedPageByPathExpanded(\n siteId: string,\n path: string,\n locale?: string,\n): Promise<StoredPage | null> {\n let page = await getPublishedPageByPath(siteId, path);\n if (!page) return null;\n // De caller geeft alléén een niet-default taal door; \"\" als default matcht dus nooit.\n if (locale) page = localizePage(page, locale, \"\");\n if (!(page.blocks ?? []).some((b) => (b as { _type?: string })._type === \"symbolRef\")) return page;\n let symbols = await getSiteSymbols(siteId);\n if (locale) {\n symbols = symbols.map((s) => ({\n ...s,\n blocks: localizeBlocks(s.blocks as Array<{ _key?: string }>, s.translations?.[locale]) as typeof s.blocks,\n }));\n }\n return expandSymbols(page, symbols);\n}\n\n/** Alle custom blocks van een site (gevalideerd) — voor SSR-registratie via XiCMSProvider. */\nexport async function getSiteCustomBlocks(siteId: string): Promise<CustomBlock[]> {\n const rows = await readCollection(siteId, \"customBlocks\");\n const out: CustomBlock[] = [];\n for (const r of rows) {\n const res = CustomBlockSchema.safeParse({ ...r.data, type: r.data.type ?? r.id });\n if (res.success) out.push(res.data);\n else console.warn(`[xicms] custom block \"${r.id}\" ongeldig — overgeslagen.`);\n }\n return out;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/server/pages.ts"],"sourcesContent":["import \"server-only\";\n\nimport { getAdminDb } from \"../firebase/admin\";\nimport { PageSchema, type Page } from \"../schema/pages\";\nimport { SymbolSchema, type StoredSymbol } from \"../schema/symbols\";\nimport { CustomBlockSchema, type CustomBlock } from \"../schema/customBlocks\";\nimport { localizePage, localizeBlocks } from \"../i18n/overlay\";\n\nexport type StoredPage = Page & { id: string };\n\nfunction toPath(slugs: string[] | undefined): string {\n const clean = (slugs ?? []).filter(Boolean);\n return \"/\" + clean.join(\"/\");\n}\n\n/** Pad uit Next catch-all params (`[[...slug]]`). \"/\" voor de root. */\nexport function pathFromSlugs(slugs: string[] | undefined): string {\n const p = toPath(slugs);\n return p === \"/\" ? \"/\" : p.replace(/\\/$/, \"\");\n}\n\nfunction parse(id: string, data: unknown): StoredPage | null {\n const res = PageSchema.safeParse(data);\n if (!res.success) {\n // Niet stil 404'en: een gepubliceerde pagina die het schema niet haalt is een diagnose-signaal\n // (schema-evolutie zonder default, of corrupte write) — zelfde patroon als symbols/customBlocks.\n console.warn(`[xicms] pagina \"${id}\" faalt schema-validatie en wordt overgeslagen:`, res.error.issues[0]);\n return null;\n }\n return { id, ...res.data };\n}\n\n/**\n * Admin-SDK-reads mogen een PUBLIEKE pagina nooit laten 500'en — zelfde belofte als het REST-pad\n * (dat al overal try/catch → lege fallback heeft). Een Firestore-hikje wordt gelogd en levert de\n * fallback op; de route rendert dan 404/fallback i.p.v. een servererror.\n */\nasync function safeAdminRead<T>(fallback: T, what: string, fn: () => Promise<T>): Promise<T> {\n try {\n return await fn();\n } catch (e) {\n console.warn(`[xicms] Admin-read mislukt (${what}) — fallback gebruikt:`, (e as Error)?.message ?? e);\n return fallback;\n }\n}\n\n/* ---------------- keyless public reads via Firestore REST (alleen apiKey) ----------------\n * Gepubliceerde pagina's zijn publiek leesbaar (rules). De Firestore REST-API honoreert die\n * rules met enkel de web-apiKey (geen Admin-credentials, geen client-SDK), dus dit werkt\n * server-side op keyless deployments (org-policy zonder service-account-keys).\n */\ntype FsValue = Record<string, unknown>;\nfunction decode(v: FsValue): unknown {\n if (v == null) return null;\n if (\"stringValue\" in v) return v.stringValue;\n if (\"integerValue\" in v) return Number(v.integerValue);\n if (\"doubleValue\" in v) return v.doubleValue;\n if (\"booleanValue\" in v) return v.booleanValue;\n if (\"nullValue\" in v) return null;\n if (\"timestampValue\" in v) return v.timestampValue;\n if (\"referenceValue\" in v) return v.referenceValue;\n if (\"geoPointValue\" in v) return v.geoPointValue;\n if (\"arrayValue\" in v) return (((v.arrayValue as FsValue)?.values as FsValue[]) ?? []).map(decode);\n if (\"mapValue\" in v) return decodeFields(((v.mapValue as FsValue)?.fields as Record<string, FsValue>) ?? {});\n return null;\n}\nfunction decodeFields(fields: Record<string, FsValue>): Record<string, unknown> {\n return Object.fromEntries(Object.entries(fields).map(([k, x]) => [k, decode(x)]));\n}\n\ninterface Filter { fieldFilter: { field: { fieldPath: string }; op: \"EQUAL\"; value: { stringValue: string } } }\nfunction eq(fieldPath: string, value: string): Filter {\n return { fieldFilter: { field: { fieldPath }, op: \"EQUAL\", value: { stringValue: value } } };\n}\n\nasync function restQuery(siteId: string, filters: Filter[], limit?: number): Promise<StoredPage[]> {\n const apiKey = process.env.NEXT_PUBLIC_FIREBASE_API_KEY;\n const projectId = process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID;\n if (!apiKey || !projectId) return [];\n const url = `https://firestore.googleapis.com/v1/projects/${projectId}/databases/(default)/documents/sites/${siteId}:runQuery?key=${apiKey}`;\n const where = filters.length === 1 ? filters[0] : { compositeFilter: { op: \"AND\", filters } };\n const structuredQuery: Record<string, unknown> = { from: [{ collectionId: \"pages\" }], where };\n if (limit) structuredQuery.limit = limit;\n // Netwerk-/parsefouten mogen een publieke pagina nooit laten crashen (500) — dan liever een\n // lege lijst (Next toont dan een 404 of de fallback, en de volgende revalidate probeert opnieuw).\n try {\n const res = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ structuredQuery }),\n // Pagina's zijn ISR-gecached; next-cache mag dit hergebruiken.\n next: { revalidate: 300 },\n });\n if (!res.ok) return [];\n const rows = (await res.json()) as { document?: { name: string; fields: Record<string, FsValue> } }[];\n return rows\n .filter((r) => r.document)\n .map((r) => parse(r.document!.name.split(\"/\").pop()!, decodeFields(r.document!.fields)))\n .filter((p): p is StoredPage => p !== null);\n } catch (err) {\n console.error(\"[xicms] Firestore REST-query mislukt:\", err);\n return [];\n }\n}\n\n/** Alle gepubliceerde pagina's (sitemap / generateStaticParams). Keyless via REST als geen Admin. */\nexport async function listPublishedPages(siteId: string): Promise<StoredPage[]> {\n const adb = getAdminDb();\n if (adb) {\n return safeAdminRead([], `listPublishedPages ${siteId}`, async () => {\n const snap = await adb.collection(`sites/${siteId}/pages`).where(\"status\", \"==\", \"published\").get();\n return snap.docs.map((d) => parse(d.id, d.data())).filter((p): p is StoredPage => p !== null);\n });\n }\n return restQuery(siteId, [eq(\"status\", \"published\")]);\n}\n\n/** Eén gepubliceerde pagina op pad. Keyless via REST als geen Admin. */\nexport async function getPublishedPageByPath(siteId: string, path: string): Promise<StoredPage | null> {\n const adb = getAdminDb();\n if (adb) {\n return safeAdminRead(null, `getPublishedPageByPath ${siteId}${path}`, async () => {\n const snap = await adb\n .collection(`sites/${siteId}/pages`)\n .where(\"status\", \"==\", \"published\")\n .where(\"path\", \"==\", path)\n .limit(1)\n .get();\n const d = snap.docs[0];\n return d ? parse(d.id, d.data()) : null;\n });\n }\n const rows = await restQuery(siteId, [eq(\"status\", \"published\"), eq(\"path\", path)], 1);\n return rows[0] ?? null;\n}\n\n/** Draft-versie (voor preview achter auth). */\nexport async function getDraftPageById(siteId: string, pageId: string): Promise<StoredPage | null> {\n const db = getAdminDb();\n if (!db) return null;\n return safeAdminRead(null, `getDraftPageById ${siteId}/${pageId}`, async () => {\n const d = await db.doc(`sites/${siteId}/drafts/${pageId}`).get();\n return d.exists ? parse(d.id, d.data()) : null;\n });\n}\n\n/** Draft op pad (preview). */\nexport async function getDraftPageByPath(siteId: string, path: string): Promise<StoredPage | null> {\n const db = getAdminDb();\n if (!db) return null;\n return safeAdminRead(null, `getDraftPageByPath ${siteId}${path}`, async () => {\n const snap = await db.collection(`sites/${siteId}/drafts`).where(\"path\", \"==\", path).limit(1).get();\n const d = snap.docs[0];\n return d ? parse(d.id, d.data()) : null;\n });\n}\n\n/* ---------------- symbols & custom blocks (5.0) ---------------- */\n\n/** Generieke publieke collectie-read onder sites/{siteId}/ — Admin als beschikbaar, anders REST. */\nasync function readCollection(siteId: string, collectionId: string): Promise<{ id: string; data: Record<string, unknown> }[]> {\n const adb = getAdminDb();\n if (adb) {\n return safeAdminRead([], `readCollection ${siteId}/${collectionId}`, async () => {\n const snap = await adb.collection(`sites/${siteId}/${collectionId}`).get();\n return snap.docs.map((d) => ({ id: d.id, data: d.data() as Record<string, unknown> }));\n });\n }\n const apiKey = process.env.NEXT_PUBLIC_FIREBASE_API_KEY;\n const projectId = process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID;\n if (!apiKey || !projectId) return [];\n const url = `https://firestore.googleapis.com/v1/projects/${projectId}/databases/(default)/documents/sites/${siteId}:runQuery?key=${apiKey}`;\n try {\n const res = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ structuredQuery: { from: [{ collectionId }] } }),\n next: { revalidate: 300 },\n });\n if (!res.ok) return [];\n const rows = (await res.json()) as { document?: { name: string; fields: Record<string, FsValue> } }[];\n return rows\n .filter((r) => r.document)\n .map((r) => ({ id: r.document!.name.split(\"/\").pop()!, data: decodeFields(r.document!.fields ?? {}) }));\n } catch (err) {\n console.error(`[xicms] Firestore REST-read van ${collectionId} mislukt:`, err);\n return [];\n }\n}\n\n/** Alle symbolen van een site (gevalideerd; ongeldige overgeslagen met waarschuwing). */\nexport async function getSiteSymbols(siteId: string): Promise<StoredSymbol[]> {\n const rows = await readCollection(siteId, \"symbols\");\n const out: StoredSymbol[] = [];\n for (const r of rows) {\n const res = SymbolSchema.safeParse(r.data);\n if (res.success) out.push({ id: r.id, ...res.data });\n else console.warn(`[xicms] symbool \"${r.id}\" ongeldig — overgeslagen.`);\n }\n return out;\n}\n\n/**\n * Vervangt symbolRef-blokken door de blokken van hun symbool (één niveau — symbolen kunnen zelf\n * geen symbolRef bevatten). `_key`s van de ingevoegde blokken worden geprefixt met de ref-key\n * zodat ze uniek blijven wanneer hetzelfde symbool vaker op een pagina staat. Verweesde refs\n * verdwijnen stil (de pagina mag nooit breken op een verwijderd symbool).\n */\nexport function expandSymbols<T extends { blocks?: unknown[] }>(page: T, symbols: StoredSymbol[]): T {\n const byId = new Map(symbols.map((s) => [s.id, s]));\n const blocks = (page.blocks ?? []).flatMap((b) => {\n const blk = b as { _type?: string; _key?: string; symbolId?: string };\n if (blk?._type !== \"symbolRef\") return [b];\n const sym = blk.symbolId ? byId.get(blk.symbolId) : undefined;\n if (!sym) return [];\n return sym.blocks.map((sb, i) => ({ ...(sb as Record<string, unknown>), _key: `${blk._key ?? \"sym\"}-${(sb as { _key?: string })._key ?? i}` }));\n });\n return { ...page, blocks };\n}\n\n/**\n * Gepubliceerde pagina op pad mét symbolRef-expansie (SEO: symboolinhoud zit in de SSR-HTML).\n * Geef `locale` mee (een NIET-default taal) om de vertaal-overlay toe te passen — op de pagina\n * én op de ingevoegde symbolen (elk symbool draagt zijn eigen `translations`).\n */\nexport async function getPublishedPageByPathExpanded(\n siteId: string,\n path: string,\n locale?: string,\n): Promise<StoredPage | null> {\n let page = await getPublishedPageByPath(siteId, path);\n if (!page) return null;\n // De caller geeft alléén een niet-default taal door; \"\" als default matcht dus nooit.\n if (locale) page = localizePage(page, locale, \"\");\n if (!(page.blocks ?? []).some((b) => (b as { _type?: string })._type === \"symbolRef\")) return page;\n let symbols = await getSiteSymbols(siteId);\n if (locale) {\n symbols = symbols.map((s) => ({\n ...s,\n blocks: localizeBlocks(s.blocks as Array<{ _key?: string }>, s.translations?.[locale]) as typeof s.blocks,\n }));\n }\n return expandSymbols(page, symbols);\n}\n\n/** Alle custom blocks van een site (gevalideerd) — voor SSR-registratie via XiCMSProvider. */\nexport async function getSiteCustomBlocks(siteId: string): Promise<CustomBlock[]> {\n const rows = await readCollection(siteId, \"customBlocks\");\n const out: CustomBlock[] = [];\n for (const r of rows) {\n const res = CustomBlockSchema.safeParse({ ...r.data, type: r.data.type ?? r.id });\n if (res.success) out.push(res.data);\n else console.warn(`[xicms] custom block \"${r.id}\" ongeldig — overgeslagen.`);\n }\n return out;\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiwebdesign/xicms-core",
3
- "version": "5.0.6",
3
+ "version": "5.1.0",
4
4
  "description": "XiCMS engine: gestructureerde content, pagina's, draft/publish, admin (TipTap+Radix), SEO en beveiliging voor klantsites.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {