@xiwebdesign/xicms-core 4.18.0 → 5.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/XiCMSProvider.d.ts +7 -0
- package/dist/XiCMSProvider.js +29 -6
- package/dist/XiCMSProvider.js.map +1 -1
- package/dist/admin/blockEditors/GenericEditor.d.ts +22 -0
- package/dist/admin/blockEditors/GenericEditor.js +201 -0
- package/dist/admin/blockEditors/GenericEditor.js.map +1 -0
- package/dist/admin/blockEditors/SymbolRefEditor.d.ts +2 -0
- package/dist/admin/blockEditors/SymbolRefEditor.js +55 -0
- package/dist/admin/blockEditors/SymbolRefEditor.js.map +1 -0
- package/dist/admin/blockEditors/index.d.ts +11 -6
- package/dist/admin/blockEditors/index.js +4 -608
- package/dist/admin/blockEditors/index.js.map +1 -1
- package/dist/admin/customBlocks/CustomBlocksPanel.d.ts +3 -0
- package/dist/admin/customBlocks/CustomBlocksPanel.js +145 -0
- package/dist/admin/customBlocks/CustomBlocksPanel.js.map +1 -0
- package/dist/admin/lib/symbolsClient.d.ts +13 -0
- package/dist/admin/lib/symbolsClient.js +51 -0
- package/dist/admin/lib/symbolsClient.js.map +1 -0
- package/dist/admin/lib/zodFields.d.ts +18 -0
- package/dist/admin/lib/zodFields.js +65 -2
- package/dist/admin/lib/zodFields.js.map +1 -1
- package/dist/admin/panelRegistry.js +3 -0
- package/dist/admin/panelRegistry.js.map +1 -1
- package/dist/admin/settings/SettingsPanel.js +2 -0
- package/dist/admin/settings/SettingsPanel.js.map +1 -1
- package/dist/admin/settings/SnapshotsCard.d.ts +3 -0
- package/dist/admin/settings/SnapshotsCard.js +98 -0
- package/dist/admin/settings/SnapshotsCard.js.map +1 -0
- package/dist/admin/ui/dialog.js +11 -4
- package/dist/admin/ui/dialog.js.map +1 -1
- package/dist/admin/visual/TranslationPanel.d.ts +13 -0
- package/dist/admin/visual/TranslationPanel.js +73 -0
- package/dist/admin/visual/TranslationPanel.js.map +1 -0
- package/dist/admin/visual/VisualEditor.js +142 -17
- package/dist/admin/visual/VisualEditor.js.map +1 -1
- package/dist/admin.css +1 -1
- package/dist/agentFiles/catalog.d.ts +21 -0
- package/dist/agentFiles/catalog.js +78 -0
- package/dist/agentFiles/catalog.js.map +1 -0
- package/dist/agentFiles/files.d.ts +59 -0
- package/dist/agentFiles/files.js +109 -0
- package/dist/agentFiles/files.js.map +1 -0
- package/dist/blocks/builtins/BeforeAfter.js +1 -0
- package/dist/blocks/builtins/BeforeAfter.js.map +1 -1
- package/dist/blocks/builtins/Cards.js +1 -0
- package/dist/blocks/builtins/Cards.js.map +1 -1
- package/dist/blocks/builtins/Cta.js +1 -0
- package/dist/blocks/builtins/Cta.js.map +1 -1
- package/dist/blocks/builtins/Elements.js +1 -0
- package/dist/blocks/builtins/Elements.js.map +1 -1
- package/dist/blocks/builtins/Hero.js +2 -2
- package/dist/blocks/builtins/Hero.js.map +1 -1
- package/dist/blocks/builtins/Jobs.js +1 -0
- package/dist/blocks/builtins/Jobs.js.map +1 -1
- package/dist/blocks/builtins/Sections.js +1 -0
- package/dist/blocks/builtins/Sections.js.map +1 -1
- package/dist/blocks/builtins/SymbolRef.d.ts +2 -0
- package/dist/blocks/builtins/SymbolRef.js +61 -0
- package/dist/blocks/builtins/SymbolRef.js.map +1 -0
- package/dist/blocks/customTemplate/definition.d.ts +7 -0
- package/dist/blocks/customTemplate/definition.js +34 -0
- package/dist/blocks/customTemplate/definition.js.map +1 -0
- package/dist/blocks/customTemplate/interpret.d.ts +10 -0
- package/dist/blocks/customTemplate/interpret.js +136 -0
- package/dist/blocks/customTemplate/interpret.js.map +1 -0
- package/dist/blocks/registry.d.ts +8 -0
- package/dist/blocks/registry.js.map +1 -1
- package/dist/i18n/overlay.d.ts +61 -0
- package/dist/i18n/overlay.js +90 -0
- package/dist/i18n/overlay.js.map +1 -0
- package/dist/index.d.ts +10 -8
- package/dist/index.js +32 -9
- package/dist/index.js.map +1 -1
- package/dist/modules/builtins.js +3 -2
- package/dist/modules/builtins.js.map +1 -1
- package/dist/schema/blocks.d.ts +159 -0
- package/dist/schema/blocks.js +8 -0
- package/dist/schema/blocks.js.map +1 -1
- package/dist/schema/customBlocks.d.ts +47 -0
- package/dist/schema/customBlocks.js +32 -0
- package/dist/schema/customBlocks.js.map +1 -0
- package/dist/schema/fields.d.ts +122 -0
- package/dist/schema/fields.js +120 -0
- package/dist/schema/fields.js.map +1 -0
- package/dist/schema/forms.d.ts +6 -6
- package/dist/schema/pages.d.ts +15 -1
- package/dist/schema/pages.js +6 -2
- package/dist/schema/pages.js.map +1 -1
- package/dist/schema/siteConfig.d.ts +14 -8
- package/dist/schema/siteConfig.js +2 -1
- package/dist/schema/siteConfig.js.map +1 -1
- package/dist/schema/symbols.d.ts +205 -0
- package/dist/schema/symbols.js +18 -0
- package/dist/schema/symbols.js.map +1 -0
- package/dist/server/index.d.ts +2 -1
- package/dist/server/index.js +24 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/pages.d.ts +21 -0
- package/dist/server/pages.js +77 -0
- package/dist/server/pages.js.map +1 -1
- package/dist/server/seo.d.ts +10 -3
- package/dist/server/seo.js +22 -10
- package/dist/server/seo.js.map +1 -1
- package/dist/styles.css +27 -0
- package/package.json +9 -9
- package/dist/components/EditableImage.d.ts +0 -18
- package/dist/components/EditableImage.js +0 -62
- package/dist/components/EditableImage.js.map +0 -1
- package/dist/components/EditableRichText.d.ts +0 -14
- package/dist/components/EditableRichText.js +0 -26
- package/dist/components/EditableRichText.js.map +0 -1
- package/dist/components/EditableText.d.ts +0 -21
- package/dist/components/EditableText.js +0 -25
- package/dist/components/EditableText.js.map +0 -1
- package/dist/content/ContentProvider.d.ts +0 -23
- package/dist/content/ContentProvider.js +0 -86
- package/dist/content/ContentProvider.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/blocks/customTemplate/definition.tsx"],"sourcesContent":["/**\n * Bouwt een volwaardige BlockDefinition uit een opgeslagen custom block (5.0). Server-safe:\n * de Render interpreteert de veilige template naar HTML (SSR-vriendelijk, geen hooks); de\n * waarden zijn gevalideerd via `fieldsToZod` — runtime-gedefinieerde blokken krijgen dus\n * dezelfde harde validatie als code-blokken. De editor is de generieke FieldsForm: GenericEditor\n * herkent `customFields` op de definitie (zie GenericEditor.tsx).\n */\nimport { z } from \"zod\";\nimport { blockBase } from \"../../schema/blocks\";\nimport { fieldsToZod, fieldsDefaults, type Field } from \"../../schema/fields\";\nimport type { CustomBlock } from \"../../schema/customBlocks\";\nimport { defineBlock, type BlockDefinition, type BlockRenderProps } from \"../registry\";\nimport { interpretTemplate, scopeCss } from \"./interpret\";\n\nfunction CustomBlockRender(def: CustomBlock) {\n const scopedCss = def.css ? scopeCss(def.css, def.type) : \"\";\n return function Render({ block }: BlockRenderProps<Record<string, unknown>>) {\n const html = interpretTemplate(def.template, block);\n return (\n <div data-xicms-cb={def.type} className={`xicms-custom xicms-${def.type}`}>\n {scopedCss ? <style dangerouslySetInnerHTML={{ __html: scopedCss }} /> : null}\n {/* Template is opslag-tijd gesanitized; interpolaties zijn render-tijd ge-escaped. */}\n <div dangerouslySetInnerHTML={{ __html: html }} />\n </div>\n );\n };\n}\n\n/** Custom block-document → BlockDefinition (voor mergeRegistry, server én client). */\nexport function customBlockToDefinition(def: CustomBlock): BlockDefinition & { customFields: Field[] } {\n const valueSchema = z.object({ ...blockBase, _type: z.literal(def.type) }).merge(fieldsToZod(def.fields));\n const definition = defineBlock({\n type: def.type,\n schema: valueSchema,\n Render: CustomBlockRender(def),\n label: def.label,\n icon: def.icon || \"🧩\",\n category: \"content\",\n defaults: () => fieldsDefaults(def.fields),\n }) as BlockDefinition & { customFields: Field[] };\n // De opgeslagen Field-IR meegeven zodat de GenericEditor die direct rendert (incl. showIf/date/\n // repeater-metadata die uit een Zod-schema niet volledig terug te winnen is).\n definition.customFields = def.fields;\n return definition;\n}\n"],"mappings":"AAmBM,SACe,KADf;AAZN,SAAS,SAAS;AAClB,SAAS,iBAAiB;AAC1B,SAAS,aAAa,sBAAkC;AAExD,SAAS,mBAAgE;AACzE,SAAS,mBAAmB,gBAAgB;AAE5C,SAAS,kBAAkB,KAAkB;AAC3C,QAAM,YAAY,IAAI,MAAM,SAAS,IAAI,KAAK,IAAI,IAAI,IAAI;AAC1D,SAAO,SAAS,OAAO,EAAE,MAAM,GAA8C;AAC3E,UAAM,OAAO,kBAAkB,IAAI,UAAU,KAAK;AAClD,WACE,qBAAC,SAAI,iBAAe,IAAI,MAAM,WAAW,sBAAsB,IAAI,IAAI,IACpE;AAAA,kBAAY,oBAAC,WAAM,yBAAyB,EAAE,QAAQ,UAAU,GAAG,IAAK;AAAA,MAEzE,oBAAC,SAAI,yBAAyB,EAAE,QAAQ,KAAK,GAAG;AAAA,OAClD;AAAA,EAEJ;AACF;AAGO,SAAS,wBAAwB,KAA+D;AACrG,QAAM,cAAc,EAAE,OAAO,EAAE,GAAG,WAAW,OAAO,EAAE,QAAQ,IAAI,IAAI,EAAE,CAAC,EAAE,MAAM,YAAY,IAAI,MAAM,CAAC;AACxG,QAAM,aAAa,YAAY;AAAA,IAC7B,MAAM,IAAI;AAAA,IACV,QAAQ;AAAA,IACR,QAAQ,kBAAkB,GAAG;AAAA,IAC7B,OAAO,IAAI;AAAA,IACX,MAAM,IAAI,QAAQ;AAAA,IAClB,UAAU;AAAA,IACV,UAAU,MAAM,eAAe,IAAI,MAAM;AAAA,EAC3C,CAAC;AAGD,aAAW,eAAe,IAAI;AAC9B,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function escapeHtml(s: string): string;
|
|
2
|
+
/** Rendert een custom-block-template met de blokwaarden. Output = HTML-string (SSR-safe). */
|
|
3
|
+
export declare function interpretTemplate(template: string, values: Record<string, unknown>): string;
|
|
4
|
+
/**
|
|
5
|
+
* Scopet CSS op het custom block: elke top-level selector wordt geprefixt met
|
|
6
|
+
* `[data-xicms-cb="<type>"]`, at-rules met blokken (@media/@supports) recursief. Gevaarlijke
|
|
7
|
+
* constructies (@import, expression(), url(javascript:…)) worden gestript. Bewust simpel en
|
|
8
|
+
* fail-safe: liever een kale stijl dan een lek.
|
|
9
|
+
*/
|
|
10
|
+
export declare function scopeCss(css: string, type: string): string;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { isRichTextKey } from "../../schema/fields";
|
|
2
|
+
const ESC = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" };
|
|
3
|
+
function escapeHtml(s) {
|
|
4
|
+
return s.replace(/[&<>"']/g, (c) => ESC[c]);
|
|
5
|
+
}
|
|
6
|
+
function neutralizeUrlSchemes(s) {
|
|
7
|
+
return /^\s*(javascript|vbscript|data)\s*:/i.test(s) ? "#" : s;
|
|
8
|
+
}
|
|
9
|
+
function lookup(scope, path) {
|
|
10
|
+
return path.split(".").reduce((o, k) => o == null ? void 0 : o[k], scope);
|
|
11
|
+
}
|
|
12
|
+
function truthy(v) {
|
|
13
|
+
if (Array.isArray(v)) return v.length > 0;
|
|
14
|
+
return Boolean(v);
|
|
15
|
+
}
|
|
16
|
+
function parse(tpl) {
|
|
17
|
+
const tokens = tpl.split(/(\{\{\{[^}]+\}\}\}|\{\{[^}]+\}\})/g);
|
|
18
|
+
const root = [];
|
|
19
|
+
const stack = [{ list: root }];
|
|
20
|
+
const top = () => stack[stack.length - 1];
|
|
21
|
+
const push = (n) => {
|
|
22
|
+
const f = top();
|
|
23
|
+
if (f.node?.t === "if" && f.inElse) f.node.elseBody.push(n);
|
|
24
|
+
else f.list.push(n);
|
|
25
|
+
};
|
|
26
|
+
for (const tok of tokens) {
|
|
27
|
+
if (!tok) continue;
|
|
28
|
+
const triple = /^\{\{\{\s*([\w.]+)\s*\}\}\}$/.exec(tok);
|
|
29
|
+
if (triple) {
|
|
30
|
+
push({ t: "var", path: triple[1], raw: true });
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const m = /^\{\{\s*([#/]?)\s*([\w.@]+)?(?:\s+([\w.]+))?\s*\}\}$/.exec(tok);
|
|
34
|
+
if (!m) {
|
|
35
|
+
push({ t: "text", s: tok });
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const [, sigil, word, arg] = m;
|
|
39
|
+
if (sigil === "#" && word === "each" && arg) {
|
|
40
|
+
const node = { t: "each", path: arg, body: [] };
|
|
41
|
+
push(node);
|
|
42
|
+
stack.push({ list: node.body, node });
|
|
43
|
+
} else if (sigil === "#" && word === "if" && arg) {
|
|
44
|
+
const node = { t: "if", path: arg, body: [], elseBody: [] };
|
|
45
|
+
push(node);
|
|
46
|
+
stack.push({ list: node.body, node });
|
|
47
|
+
} else if (word === "else" && !sigil) {
|
|
48
|
+
const f = top();
|
|
49
|
+
if (f.node?.t === "if") f.inElse = true;
|
|
50
|
+
else push({ t: "text", s: tok });
|
|
51
|
+
} else if (sigil === "/" && (word === "each" || word === "if")) {
|
|
52
|
+
if (stack.length > 1) stack.pop();
|
|
53
|
+
} else if (word && !sigil) {
|
|
54
|
+
push({ t: "var", path: word, raw: false });
|
|
55
|
+
} else {
|
|
56
|
+
push({ t: "text", s: tok });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return root;
|
|
60
|
+
}
|
|
61
|
+
function render(nodes, scope) {
|
|
62
|
+
let out = "";
|
|
63
|
+
for (const n of nodes) {
|
|
64
|
+
if (n.t === "text") out += n.s;
|
|
65
|
+
else if (n.t === "var") {
|
|
66
|
+
const leaf = n.path.split(".").pop() ?? n.path;
|
|
67
|
+
const v = lookup(scope, n.path);
|
|
68
|
+
const s = v == null ? "" : typeof v === "object" ? "" : String(v);
|
|
69
|
+
if (n.raw) {
|
|
70
|
+
out += isRichTextKey(leaf) ? s : escapeHtml(s);
|
|
71
|
+
} else {
|
|
72
|
+
out += escapeHtml(neutralizeUrlSchemes(s));
|
|
73
|
+
}
|
|
74
|
+
} else if (n.t === "each") {
|
|
75
|
+
const arr = lookup(scope, n.path);
|
|
76
|
+
if (Array.isArray(arr)) {
|
|
77
|
+
arr.forEach((item, i) => {
|
|
78
|
+
const child = item && typeof item === "object" ? { ...item } : { value: item };
|
|
79
|
+
child["@index"] = i + 1;
|
|
80
|
+
out += render(n.body, child);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
} else if (n.t === "if") {
|
|
84
|
+
out += render(truthy(lookup(scope, n.path)) ? n.body : n.elseBody, scope);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
function interpretTemplate(template, values) {
|
|
90
|
+
return render(parse(template), values);
|
|
91
|
+
}
|
|
92
|
+
function scopeCss(css, type) {
|
|
93
|
+
const prefix = `[data-xicms-cb="${type}"]`;
|
|
94
|
+
const cleaned = css.replace(/@import[^;]*;?/gi, "").replace(/@charset[^;]*;?/gi, "").replace(/expression\s*\(/gi, "invalid(").replace(/url\(\s*['"]?\s*(javascript|vbscript|data)\s*:[^)]*\)/gi, "url()");
|
|
95
|
+
return scopeBlock(cleaned, prefix);
|
|
96
|
+
}
|
|
97
|
+
function scopeBlock(css, prefix) {
|
|
98
|
+
let out = "";
|
|
99
|
+
let i = 0;
|
|
100
|
+
while (i < css.length) {
|
|
101
|
+
const brace = css.indexOf("{", i);
|
|
102
|
+
if (brace < 0) break;
|
|
103
|
+
const header = css.slice(i, brace).trim();
|
|
104
|
+
let depth = 1;
|
|
105
|
+
let j = brace + 1;
|
|
106
|
+
while (j < css.length && depth > 0) {
|
|
107
|
+
if (css[j] === "{") depth++;
|
|
108
|
+
else if (css[j] === "}") depth--;
|
|
109
|
+
j++;
|
|
110
|
+
}
|
|
111
|
+
const body = css.slice(brace + 1, j - 1);
|
|
112
|
+
if (header.startsWith("@")) {
|
|
113
|
+
if (/^@(media|supports|container)/i.test(header)) {
|
|
114
|
+
out += `${header}{${scopeBlock(body, prefix)}}`;
|
|
115
|
+
} else if (/^@(keyframes|font-face)/i.test(header)) {
|
|
116
|
+
out += `${header}{${body}}`;
|
|
117
|
+
}
|
|
118
|
+
} else if (header) {
|
|
119
|
+
const scoped = header.split(",").map((sel) => {
|
|
120
|
+
const s = sel.trim();
|
|
121
|
+
if (!s) return "";
|
|
122
|
+
if (/^(:root|html|body)$/i.test(s)) return prefix;
|
|
123
|
+
return `${prefix} ${s}`;
|
|
124
|
+
}).filter(Boolean).join(", ");
|
|
125
|
+
out += `${scoped}{${body}}`;
|
|
126
|
+
}
|
|
127
|
+
i = j;
|
|
128
|
+
}
|
|
129
|
+
return out;
|
|
130
|
+
}
|
|
131
|
+
export {
|
|
132
|
+
escapeHtml,
|
|
133
|
+
interpretTemplate,
|
|
134
|
+
scopeCss
|
|
135
|
+
};
|
|
136
|
+
//# sourceMappingURL=interpret.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/blocks/customTemplate/interpret.ts"],"sourcesContent":["/**\n * Veilige template-interpreter voor custom blocks (5.0) — een bewuste mustache-SUBSET, géén\n * JavaScript, geen expressies, geen helpers. Dubbele verdediging tegen XSS:\n *\n * 1. **Opslag-tijd** (saveCustomBlock-callable): de template zelf wordt gesanitized met een\n * strikte allowlist (geen <script>/<style>-tags, geen on*-attributen, alleen http(s)/mailto/\n * tel/relative URL's).\n * 2. **Render-tijd** (hier): élke `{{veld}}`-interpolatie wordt HTML-escaped. Alleen\n * `{{{veldHtml}}}` (triple, en uitsluitend voor sleutels die op de sanitizer-conventie\n * `html`/`*Html` matchen) injecteert HTML — en die waarden zijn bij publiceren al\n * server-side gesanitized door `sanitizeBlocks`. In attributen worden bovendien\n * `javascript:`-achtige URL-schema's geneutraliseerd.\n *\n * Syntax:\n * {{veld}} → escaped waarde (ook dotted: {{cta.label}})\n * {{{veldHtml}}} → rauwe (gesanitizede) HTML — alleen voor *Html-sleutels\n * {{#each items}} … {{/each}} → herhaal over een repeater (binnen: velden van het item;\n * {{@index}} = 1-based nummer)\n * {{#if veld}} … {{else}} … {{/if}} → conditioneel op truthy (lege string/0/false = falsy)\n *\n * Server-safe (geen React, geen DOM) — apart getest, incl. XSS-pogingen.\n */\nimport { isRichTextKey } from \"../../schema/fields\";\n\nconst ESC: Record<string, string> = { \"&\": \"&\", \"<\": \"<\", \">\": \">\", '\"': \""\", \"'\": \"'\" };\nexport function escapeHtml(s: string): string {\n return s.replace(/[&<>\"']/g, (c) => ESC[c]);\n}\n\n/** Neutraliseert gevaarlijke URL-schema's in een geïnterpoleerde waarde (voor href/src-posities). */\nfunction neutralizeUrlSchemes(s: string): string {\n return /^\\s*(javascript|vbscript|data)\\s*:/i.test(s) ? \"#\" : s;\n}\n\nfunction lookup(scope: Record<string, unknown>, path: string): unknown {\n return path.split(\".\").reduce<unknown>((o, k) => (o == null ? undefined : (o as Record<string, unknown>)[k]), scope);\n}\n\nfunction truthy(v: unknown): boolean {\n if (Array.isArray(v)) return v.length > 0;\n return Boolean(v);\n}\n\ntype Node =\n | { t: \"text\"; s: string }\n | { t: \"var\"; path: string; raw: boolean }\n | { t: \"each\"; path: string; body: Node[] }\n | { t: \"if\"; path: string; body: Node[]; elseBody: Node[] };\n\n/** Parseert de template naar een boom. Onbekende/kapotte tags worden als tekst behandeld. */\nfunction parse(tpl: string): Node[] {\n const tokens = tpl.split(/(\\{\\{\\{[^}]+\\}\\}\\}|\\{\\{[^}]+\\}\\})/g);\n const root: Node[] = [];\n // Stack van open blokken; elk frame houdt de node-lijst bij waar we aan toevoegen.\n const stack: { list: Node[]; node?: Extract<Node, { t: \"each\" | \"if\" }>; inElse?: boolean }[] = [{ list: root }];\n const top = () => stack[stack.length - 1];\n const push = (n: Node) => {\n const f = top();\n if (f.node?.t === \"if\" && f.inElse) f.node.elseBody.push(n);\n else f.list.push(n);\n };\n for (const tok of tokens) {\n if (!tok) continue;\n const triple = /^\\{\\{\\{\\s*([\\w.]+)\\s*\\}\\}\\}$/.exec(tok);\n if (triple) {\n push({ t: \"var\", path: triple[1], raw: true });\n continue;\n }\n const m = /^\\{\\{\\s*([#/]?)\\s*([\\w.@]+)?(?:\\s+([\\w.]+))?\\s*\\}\\}$/.exec(tok);\n if (!m) {\n push({ t: \"text\", s: tok });\n continue;\n }\n const [, sigil, word, arg] = m;\n if (sigil === \"#\" && word === \"each\" && arg) {\n const node: Node = { t: \"each\", path: arg, body: [] };\n push(node);\n stack.push({ list: node.body, node });\n } else if (sigil === \"#\" && word === \"if\" && arg) {\n const node: Node = { t: \"if\", path: arg, body: [], elseBody: [] };\n push(node);\n stack.push({ list: node.body, node });\n } else if (word === \"else\" && !sigil) {\n const f = top();\n if (f.node?.t === \"if\") f.inElse = true;\n else push({ t: \"text\", s: tok });\n } else if (sigil === \"/\" && (word === \"each\" || word === \"if\")) {\n if (stack.length > 1) stack.pop();\n // ongebalanceerde sluittag: negeren (fail-soft)\n } else if (word && !sigil) {\n push({ t: \"var\", path: word, raw: false });\n } else {\n push({ t: \"text\", s: tok });\n }\n }\n return root;\n}\n\nfunction render(nodes: Node[], scope: Record<string, unknown>): string {\n let out = \"\";\n for (const n of nodes) {\n if (n.t === \"text\") out += n.s;\n else if (n.t === \"var\") {\n const leaf = n.path.split(\".\").pop() ?? n.path;\n const v = lookup(scope, n.path);\n const s = v == null ? \"\" : typeof v === \"object\" ? \"\" : String(v);\n if (n.raw) {\n // Triple-stache: alléén voor rich-text-sleutels (waarde is server-side gesanitized).\n out += isRichTextKey(leaf) ? s : escapeHtml(s);\n } else {\n out += escapeHtml(neutralizeUrlSchemes(s));\n }\n } else if (n.t === \"each\") {\n const arr = lookup(scope, n.path);\n if (Array.isArray(arr)) {\n arr.forEach((item, i) => {\n const child = item && typeof item === \"object\" ? { ...(item as Record<string, unknown>) } : { value: item };\n (child as Record<string, unknown>)[\"@index\"] = i + 1;\n out += render(n.body, child as Record<string, unknown>);\n });\n }\n } else if (n.t === \"if\") {\n out += render(truthy(lookup(scope, n.path)) ? n.body : n.elseBody, scope);\n }\n }\n return out;\n}\n\n/** Rendert een custom-block-template met de blokwaarden. Output = HTML-string (SSR-safe). */\nexport function interpretTemplate(template: string, values: Record<string, unknown>): string {\n return render(parse(template), values);\n}\n\n/**\n * Scopet CSS op het custom block: elke top-level selector wordt geprefixt met\n * `[data-xicms-cb=\"<type>\"]`, at-rules met blokken (@media/@supports) recursief. Gevaarlijke\n * constructies (@import, expression(), url(javascript:…)) worden gestript. Bewust simpel en\n * fail-safe: liever een kale stijl dan een lek.\n */\nexport function scopeCss(css: string, type: string): string {\n const prefix = `[data-xicms-cb=\"${type}\"]`;\n const cleaned = css\n .replace(/@import[^;]*;?/gi, \"\")\n .replace(/@charset[^;]*;?/gi, \"\")\n .replace(/expression\\s*\\(/gi, \"invalid(\")\n .replace(/url\\(\\s*['\"]?\\s*(javascript|vbscript|data)\\s*:[^)]*\\)/gi, \"url()\");\n return scopeBlock(cleaned, prefix);\n}\n\nfunction scopeBlock(css: string, prefix: string): string {\n let out = \"\";\n let i = 0;\n while (i < css.length) {\n const brace = css.indexOf(\"{\", i);\n if (brace < 0) break;\n const header = css.slice(i, brace).trim();\n // Vind de bijbehorende sluit-brace (genest tellen).\n let depth = 1;\n let j = brace + 1;\n while (j < css.length && depth > 0) {\n if (css[j] === \"{\") depth++;\n else if (css[j] === \"}\") depth--;\n j++;\n }\n const body = css.slice(brace + 1, j - 1);\n if (header.startsWith(\"@\")) {\n if (/^@(media|supports|container)/i.test(header)) {\n out += `${header}{${scopeBlock(body, prefix)}}`;\n } else if (/^@(keyframes|font-face)/i.test(header)) {\n // keyframes/font-face niet prefixen (selectors zijn geen elementen).\n out += `${header}{${body}}`;\n }\n // overige at-rules: weglaten (fail-safe)\n } else if (header) {\n const scoped = header\n .split(\",\")\n .map((sel) => {\n const s = sel.trim();\n if (!s) return \"\";\n // `:root`/`html`/`body` binnen een blok-scope slaat nergens op → scope zelf.\n if (/^(:root|html|body)$/i.test(s)) return prefix;\n return `${prefix} ${s}`;\n })\n .filter(Boolean)\n .join(\", \");\n out += `${scoped}{${body}}`;\n }\n i = j;\n }\n return out;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import type { ComponentType } from "react";
|
|
9
9
|
import type { ZodTypeAny, z } from "zod";
|
|
10
10
|
import type { BlockBase } from "../schema/blocks";
|
|
11
|
+
import type { FieldHints } from "../schema/fields";
|
|
11
12
|
export interface BlockRenderProps<T = unknown> {
|
|
12
13
|
block: T;
|
|
13
14
|
siteId: string;
|
|
@@ -42,6 +43,13 @@ export interface BlockDefinition<S extends ZodTypeAny = ZodTypeAny> {
|
|
|
42
43
|
category?: BlockCategory;
|
|
43
44
|
/** Defaultwaarden voor een nieuw block (zonder _key/_type). */
|
|
44
45
|
defaults?: () => Record<string, unknown>;
|
|
46
|
+
/**
|
|
47
|
+
* Optionele hints voor het automatisch gegenereerde formulier (GenericEditor 2.0, 5.0):
|
|
48
|
+
* per (dotted) veldsleutel een verfijning van de uit het Zod-schema afgeleide Field-IR —
|
|
49
|
+
* bv. `{ "items.date": { type: "date" }, cta: { showIf: { field: "showCta" } } }`.
|
|
50
|
+
* Zie `introspectToFields` (admin/lib/zodFields.ts) en `schema/fields.ts`.
|
|
51
|
+
*/
|
|
52
|
+
fields?: FieldHints;
|
|
45
53
|
}
|
|
46
54
|
/**
|
|
47
55
|
* Registreert een block-type.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/blocks/registry.ts"],"sourcesContent":["/**\n * Block-registry: koppelt een `_type` aan zijn Zod-schema, een (server-safe) Render-component\n * en een optionele (client/admin) Editor. Klant-blokken worden hier ingebracht via `defineBlock`\n * — zo blijft de core klant-onafhankelijk: de klant levert de render-component, core de structuur.\n *\n * Pure logica + types (geen \"use client\"): bruikbaar op server en client.\n */\nimport type { ComponentType } from \"react\";\nimport type { ZodTypeAny, z } from \"zod\";\nimport type { BlockBase } from \"../schema/blocks\";\n\nexport interface BlockRenderProps<T = unknown> {\n block: T;\n siteId: string;\n /** \"view\" = publiek; \"preview\" = draft-weergave in de editor. */\n mode?: \"view\" | \"preview\";\n}\n\nexport interface BlockEditorProps<T = unknown> {\n block: T;\n siteId: string;\n onChange: (next: T) => void;\n}\n\n/**\n * Indeling van een block in het toevoeg-menu:\n * - `content` (default): een gewoon blok dat je op de pagina plaatst.\n * - `widget`: een compacte variant die je naast een blok (in een zijbalk/kolom) zet — haalt z'n\n * gegevens uit dezelfde module als de volwaardige versie (bv. \"laatste nieuws\", \"komende agenda\").\n * - `layout`: een indelingsblok dat andere blokken bevat (kolommen, zijbalk, tabs).\n */\nexport type BlockCategory = \"content\" | \"widget\" | \"layout\";\n\nexport interface BlockDefinition<S extends ZodTypeAny = ZodTypeAny> {\n type: string;\n schema: S;\n Render: ComponentType<BlockRenderProps<z.infer<S>>>;\n /**\n * Admin-editor (lazy/client). Optioneel; ontbreekt = automatische schema-editor (GenericEditor):\n * die genereert een formulier uit het Zod-schema, dus een blok zonder eigen editor blijft bewerkbaar.\n */\n Editor?: ComponentType<BlockEditorProps<z.infer<S>>>;\n label: string;\n icon?: string;\n /** Groep in het toevoeg-menu (content/widget/layout). Standaard \"content\". */\n category?: BlockCategory;\n /** Defaultwaarden voor een nieuw block (zonder _key/_type). */\n defaults?: () => Record<string, unknown>;\n}\n\n/**\n * Registreert een block-type.\n *\n * UPDATE-VEILIGHEID: voeg in een nieuwe versie nooit een VERPLICHT veld toe aan een bestaand\n * block-schema zonder `.default()` of `.optional()`. Anders faalt validatie op al opgeslagen\n * content en wordt die overgeslagen. Met defaults/optioneel blijft oude content gewoon werken.\n */\nexport function defineBlock<S extends ZodTypeAny>(def: BlockDefinition<S>): BlockDefinition {\n // Type-erased return: het schema-type wordt alleen gebruikt om Render/Editor-props te\n // inferreren bij definitie. Daarna behandelen we blokken generiek in de registry.\n return def as unknown as BlockDefinition;\n}\n\nexport type BlockRegistry = Map<string, BlockDefinition>;\n\n/**\n * Bouwt de opties voor het \"blok toevoegen\"-menu, gegroepeerd op categorie: eerst gewone blokken,\n * dan indeling (kolommen/zijbalk/tabs), dan widgets. Widgets/indeling krijgen een suffix zodat de\n * groep duidelijk is in een platte keuzelijst.\n */\nexport function buildBlockAddOptions(defs: Iterable<BlockDefinition>): { value: string; label: string }[] {\n const order: Record<BlockCategory, number> = { content: 0, layout: 1, widget: 2 };\n const suffix: Record<BlockCategory, string> = { content: \"\", layout: \" · indeling\", widget: \" · widget\" };\n return [...defs]\n .map((d) => ({ def: d, cat: d.category ?? \"content\" }))\n .sort((a, b) => order[a.cat] - order[b.cat])\n .map(({ def, cat }) => ({ value: def.type, label: `${def.icon ?? \"▫\"} ${def.label}${suffix[cat]}` }));\n}\n\nexport function createRegistry(defs: BlockDefinition[]): BlockRegistry {\n const m: BlockRegistry = new Map();\n for (const d of defs) m.set(d.type, d);\n return m;\n}\n\nexport function mergeRegistry(base: BlockRegistry, extra: BlockDefinition[]): BlockRegistry {\n const m: BlockRegistry = new Map(base);\n for (const d of extra) m.set(d.type, d);\n return m;\n}\n\n/**\n * Strikte per-type validatie van een blocks-array tegen de registry. Onbekende types en\n * blocks die hun schema niet halen worden weggelaten (defensief — nooit half-rendered content).\n * Gebruikt bij publiceren (server) en vóór renderen.\n *\n * Recursief: geneste blokken in de indelingsblokken (kolommen, \"Met zijbalk\", tabs) worden óók\n * per-type gevalideerd. Hun schema's valideren kinderen bewust alleen als `BlockBaseSchema`\n * (passthrough) zodat klant-blokken erin mogen; de strikte controle gebeurt hier.\n */\nexport function validateBlocks(blocks: unknown[] | undefined, registry: BlockRegistry): BlockBase[] {\n const out: BlockBase[] = [];\n for (const raw of blocks ?? []) {\n const type = (raw as { _type?: unknown } | null)?._type;\n const def = typeof type === \"string\" ? registry.get(type) : undefined;\n if (!def) {\n warn(`onbekend block-type \"${String(type)}\" overgeslagen (geen registratie).`);\n continue;\n }\n const res = def.schema.safeParse(raw);\n if (res.success) out.push(validateNestedBlocks(res.data as BlockBase, registry));\n else warn(`block \"${type}\" voldoet niet aan zijn schema en is overgeslagen.`, res.error.issues);\n }\n return out;\n}\n\n/** Valideert de geneste blok-lijsten van de bekende indelingsvormen (immutable). */\nfunction validateNestedBlocks(block: BlockBase, registry: BlockRegistry): BlockBase {\n const b = block as BlockBase & Record<string, unknown>;\n if (b._type === \"columns\" && Array.isArray(b.columns)) {\n return {\n ...b,\n columns: b.columns.map((col) =>\n col && typeof col === \"object\" && Array.isArray((col as { blocks?: unknown }).blocks)\n ? { ...col, blocks: validateBlocks((col as { blocks: unknown[] }).blocks, registry) }\n : col,\n ),\n };\n }\n if (b._type === \"withSidebar\" && (Array.isArray(b.main) || Array.isArray(b.aside))) {\n return {\n ...b,\n main: Array.isArray(b.main) ? validateBlocks(b.main, registry) : b.main,\n aside: Array.isArray(b.aside) ? validateBlocks(b.aside, registry) : b.aside,\n };\n }\n if (b._type === \"tabs\" && Array.isArray(b.tabs)) {\n return {\n ...b,\n tabs: b.tabs.map((tab) =>\n tab && typeof tab === \"object\" && Array.isArray((tab as { blocks?: unknown }).blocks)\n ? { ...tab, blocks: validateBlocks((tab as { blocks: unknown[] }).blocks, registry) }\n : tab,\n ),\n };\n }\n return block;\n}\n\nfunction warn(msg: string, detail?: unknown): void {\n // Geen stil verlies: log waarom een block niet rendert (helpt bij schema-wijzigingen).\n if (typeof console !== \"undefined\") console.warn(`[xicms] ${msg}`, detail ?? \"\");\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../src/blocks/registry.ts"],"sourcesContent":["/**\n * Block-registry: koppelt een `_type` aan zijn Zod-schema, een (server-safe) Render-component\n * en een optionele (client/admin) Editor. Klant-blokken worden hier ingebracht via `defineBlock`\n * — zo blijft de core klant-onafhankelijk: de klant levert de render-component, core de structuur.\n *\n * Pure logica + types (geen \"use client\"): bruikbaar op server en client.\n */\nimport type { ComponentType } from \"react\";\nimport type { ZodTypeAny, z } from \"zod\";\nimport type { BlockBase } from \"../schema/blocks\";\nimport type { FieldHints } from \"../schema/fields\";\n\nexport interface BlockRenderProps<T = unknown> {\n block: T;\n siteId: string;\n /** \"view\" = publiek; \"preview\" = draft-weergave in de editor. */\n mode?: \"view\" | \"preview\";\n}\n\nexport interface BlockEditorProps<T = unknown> {\n block: T;\n siteId: string;\n onChange: (next: T) => void;\n}\n\n/**\n * Indeling van een block in het toevoeg-menu:\n * - `content` (default): een gewoon blok dat je op de pagina plaatst.\n * - `widget`: een compacte variant die je naast een blok (in een zijbalk/kolom) zet — haalt z'n\n * gegevens uit dezelfde module als de volwaardige versie (bv. \"laatste nieuws\", \"komende agenda\").\n * - `layout`: een indelingsblok dat andere blokken bevat (kolommen, zijbalk, tabs).\n */\nexport type BlockCategory = \"content\" | \"widget\" | \"layout\";\n\nexport interface BlockDefinition<S extends ZodTypeAny = ZodTypeAny> {\n type: string;\n schema: S;\n Render: ComponentType<BlockRenderProps<z.infer<S>>>;\n /**\n * Admin-editor (lazy/client). Optioneel; ontbreekt = automatische schema-editor (GenericEditor):\n * die genereert een formulier uit het Zod-schema, dus een blok zonder eigen editor blijft bewerkbaar.\n */\n Editor?: ComponentType<BlockEditorProps<z.infer<S>>>;\n label: string;\n icon?: string;\n /** Groep in het toevoeg-menu (content/widget/layout). Standaard \"content\". */\n category?: BlockCategory;\n /** Defaultwaarden voor een nieuw block (zonder _key/_type). */\n defaults?: () => Record<string, unknown>;\n /**\n * Optionele hints voor het automatisch gegenereerde formulier (GenericEditor 2.0, 5.0):\n * per (dotted) veldsleutel een verfijning van de uit het Zod-schema afgeleide Field-IR —\n * bv. `{ \"items.date\": { type: \"date\" }, cta: { showIf: { field: \"showCta\" } } }`.\n * Zie `introspectToFields` (admin/lib/zodFields.ts) en `schema/fields.ts`.\n */\n fields?: FieldHints;\n}\n\n/**\n * Registreert een block-type.\n *\n * UPDATE-VEILIGHEID: voeg in een nieuwe versie nooit een VERPLICHT veld toe aan een bestaand\n * block-schema zonder `.default()` of `.optional()`. Anders faalt validatie op al opgeslagen\n * content en wordt die overgeslagen. Met defaults/optioneel blijft oude content gewoon werken.\n */\nexport function defineBlock<S extends ZodTypeAny>(def: BlockDefinition<S>): BlockDefinition {\n // Type-erased return: het schema-type wordt alleen gebruikt om Render/Editor-props te\n // inferreren bij definitie. Daarna behandelen we blokken generiek in de registry.\n return def as unknown as BlockDefinition;\n}\n\nexport type BlockRegistry = Map<string, BlockDefinition>;\n\n/**\n * Bouwt de opties voor het \"blok toevoegen\"-menu, gegroepeerd op categorie: eerst gewone blokken,\n * dan indeling (kolommen/zijbalk/tabs), dan widgets. Widgets/indeling krijgen een suffix zodat de\n * groep duidelijk is in een platte keuzelijst.\n */\nexport function buildBlockAddOptions(defs: Iterable<BlockDefinition>): { value: string; label: string }[] {\n const order: Record<BlockCategory, number> = { content: 0, layout: 1, widget: 2 };\n const suffix: Record<BlockCategory, string> = { content: \"\", layout: \" · indeling\", widget: \" · widget\" };\n return [...defs]\n .map((d) => ({ def: d, cat: d.category ?? \"content\" }))\n .sort((a, b) => order[a.cat] - order[b.cat])\n .map(({ def, cat }) => ({ value: def.type, label: `${def.icon ?? \"▫\"} ${def.label}${suffix[cat]}` }));\n}\n\nexport function createRegistry(defs: BlockDefinition[]): BlockRegistry {\n const m: BlockRegistry = new Map();\n for (const d of defs) m.set(d.type, d);\n return m;\n}\n\nexport function mergeRegistry(base: BlockRegistry, extra: BlockDefinition[]): BlockRegistry {\n const m: BlockRegistry = new Map(base);\n for (const d of extra) m.set(d.type, d);\n return m;\n}\n\n/**\n * Strikte per-type validatie van een blocks-array tegen de registry. Onbekende types en\n * blocks die hun schema niet halen worden weggelaten (defensief — nooit half-rendered content).\n * Gebruikt bij publiceren (server) en vóór renderen.\n *\n * Recursief: geneste blokken in de indelingsblokken (kolommen, \"Met zijbalk\", tabs) worden óók\n * per-type gevalideerd. Hun schema's valideren kinderen bewust alleen als `BlockBaseSchema`\n * (passthrough) zodat klant-blokken erin mogen; de strikte controle gebeurt hier.\n */\nexport function validateBlocks(blocks: unknown[] | undefined, registry: BlockRegistry): BlockBase[] {\n const out: BlockBase[] = [];\n for (const raw of blocks ?? []) {\n const type = (raw as { _type?: unknown } | null)?._type;\n const def = typeof type === \"string\" ? registry.get(type) : undefined;\n if (!def) {\n warn(`onbekend block-type \"${String(type)}\" overgeslagen (geen registratie).`);\n continue;\n }\n const res = def.schema.safeParse(raw);\n if (res.success) out.push(validateNestedBlocks(res.data as BlockBase, registry));\n else warn(`block \"${type}\" voldoet niet aan zijn schema en is overgeslagen.`, res.error.issues);\n }\n return out;\n}\n\n/** Valideert de geneste blok-lijsten van de bekende indelingsvormen (immutable). */\nfunction validateNestedBlocks(block: BlockBase, registry: BlockRegistry): BlockBase {\n const b = block as BlockBase & Record<string, unknown>;\n if (b._type === \"columns\" && Array.isArray(b.columns)) {\n return {\n ...b,\n columns: b.columns.map((col) =>\n col && typeof col === \"object\" && Array.isArray((col as { blocks?: unknown }).blocks)\n ? { ...col, blocks: validateBlocks((col as { blocks: unknown[] }).blocks, registry) }\n : col,\n ),\n };\n }\n if (b._type === \"withSidebar\" && (Array.isArray(b.main) || Array.isArray(b.aside))) {\n return {\n ...b,\n main: Array.isArray(b.main) ? validateBlocks(b.main, registry) : b.main,\n aside: Array.isArray(b.aside) ? validateBlocks(b.aside, registry) : b.aside,\n };\n }\n if (b._type === \"tabs\" && Array.isArray(b.tabs)) {\n return {\n ...b,\n tabs: b.tabs.map((tab) =>\n tab && typeof tab === \"object\" && Array.isArray((tab as { blocks?: unknown }).blocks)\n ? { ...tab, blocks: validateBlocks((tab as { blocks: unknown[] }).blocks, registry) }\n : tab,\n ),\n };\n }\n return block;\n}\n\nfunction warn(msg: string, detail?: unknown): void {\n // Geen stil verlies: log waarom een block niet rendert (helpt bij schema-wijzigingen).\n if (typeof console !== \"undefined\") console.warn(`[xicms] ${msg}`, detail ?? \"\");\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Localisatie (5.0) — primo's locale-per-waarde vertaald naar een ADDITIEVE overlay:
|
|
3
|
+
* de blokkenstructuur blijft canoniek (de default-taal), en per pagina/symbool ligt er
|
|
4
|
+
* een `translations`-map overheen: `{ [locale]: { "<blokKey>.<veldpad>": "vertaalde tekst" } }`.
|
|
5
|
+
* Speciale sleutels: `title` (paginatitel) en `seo.<veld>` (SEO-overlay). Waarden zijn altijd
|
|
6
|
+
* strings — structuurwijzigingen (blokken toevoegen/verplaatsen) gebeuren alléén canoniek.
|
|
7
|
+
*
|
|
8
|
+
* Server-safe: geen React, geen Firebase — bruikbaar in server/pages.ts én in de editor.
|
|
9
|
+
*/
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
/** `{ [locale]: { "hero1.title": "Willkommen", "seo.description": "…" } }` */
|
|
12
|
+
export declare const TranslationsSchema: z.ZodRecord<z.ZodString, z.ZodEffects<z.ZodRecord<z.ZodString, z.ZodString>, Record<string, string>, Record<string, string>>>;
|
|
13
|
+
export type Translations = z.infer<typeof TranslationsSchema>;
|
|
14
|
+
export interface LocaleConfig {
|
|
15
|
+
/** Alle talen van de site, default-taal eerst (bv. ["nl", "de", "en"]). */
|
|
16
|
+
locales: string[];
|
|
17
|
+
defaultLocale: string;
|
|
18
|
+
}
|
|
19
|
+
/** Normaliseert de site-config naar een LocaleConfig (default: alleen `lang` ?? "nl"). */
|
|
20
|
+
export declare function siteLocales(site: {
|
|
21
|
+
locales?: string[];
|
|
22
|
+
defaultLocale?: string;
|
|
23
|
+
lang?: string;
|
|
24
|
+
}): LocaleConfig;
|
|
25
|
+
/**
|
|
26
|
+
* Herkent een taalprefix in Next-catch-all-segmenten: `/de/over-ons` → locale "de", pad "/over-ons".
|
|
27
|
+
* De default-taal heeft géén prefix (canonieke URL's blijven ongewijzigd).
|
|
28
|
+
*/
|
|
29
|
+
export declare function parseLocaleSlugs(slugs: string[] | undefined, cfg: LocaleConfig): {
|
|
30
|
+
locale: string;
|
|
31
|
+
slugs: string[];
|
|
32
|
+
};
|
|
33
|
+
/** Pad mét taalprefix voor niet-default talen: ("/over-ons", "de") → "/de/over-ons". */
|
|
34
|
+
export declare function localizedPath(path: string, locale: string, defaultLocale: string): string;
|
|
35
|
+
/**
|
|
36
|
+
* Past één taal-overlay toe op een lijst blokken. Alleen bestaande, tekstuele velden worden
|
|
37
|
+
* overschreven: het doelpad moet oplosbaar zijn en de canonieke waarde moet een string (of leeg)
|
|
38
|
+
* zijn — zo kan een verouderde vertaalsleutel nooit de structuur breken (overlay-drift).
|
|
39
|
+
*/
|
|
40
|
+
export declare function localizeBlocks<T extends {
|
|
41
|
+
_key?: string;
|
|
42
|
+
}>(blocks: T[], overlay: Record<string, string> | undefined): T[];
|
|
43
|
+
/**
|
|
44
|
+
* Past een taal-overlay toe op een hele pagina (titel + seo + blokken). Geeft de pagina
|
|
45
|
+
* ongewijzigd terug voor de default-taal of zonder vertalingen — nul overhead voor
|
|
46
|
+
* ééntalige sites.
|
|
47
|
+
*/
|
|
48
|
+
export declare function localizePage<T extends {
|
|
49
|
+
title?: string;
|
|
50
|
+
seo?: Record<string, unknown>;
|
|
51
|
+
blocks?: unknown[];
|
|
52
|
+
translations?: Translations;
|
|
53
|
+
}>(page: T, locale: string | undefined, defaultLocale: string): T;
|
|
54
|
+
/**
|
|
55
|
+
* Ruimt wees-vertalingen op: sleutels die naar een niet (meer) bestaand blok wijzen vervallen
|
|
56
|
+
* (`title` en `seo.*` blijven altijd). Draait bij publiceren zodat de overlay niet oneindig
|
|
57
|
+
* aangroeit wanneer blokken verwijderd worden.
|
|
58
|
+
*/
|
|
59
|
+
export declare function pruneTranslations(translations: Translations | undefined, blocks: Array<{
|
|
60
|
+
_key?: string;
|
|
61
|
+
}> | undefined): Translations | undefined;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const TranslationsSchema = z.record(
|
|
3
|
+
z.string().min(2).max(10),
|
|
4
|
+
z.record(z.string().min(1).max(300), z.string().max(2e4)).refine((m) => Object.keys(m).length <= 2e3, {
|
|
5
|
+
message: "te veel vertaalsleutels (max 2000 per taal)"
|
|
6
|
+
})
|
|
7
|
+
);
|
|
8
|
+
function siteLocales(site) {
|
|
9
|
+
const defaultLocale = site.defaultLocale ?? site.lang ?? "nl";
|
|
10
|
+
const locales = [defaultLocale, ...(site.locales ?? []).filter((l) => l !== defaultLocale)];
|
|
11
|
+
return { locales: [...new Set(locales)], defaultLocale };
|
|
12
|
+
}
|
|
13
|
+
function parseLocaleSlugs(slugs, cfg) {
|
|
14
|
+
const list = slugs ?? [];
|
|
15
|
+
const first = list[0];
|
|
16
|
+
if (first && first !== cfg.defaultLocale && cfg.locales.includes(first)) {
|
|
17
|
+
return { locale: first, slugs: list.slice(1) };
|
|
18
|
+
}
|
|
19
|
+
return { locale: cfg.defaultLocale, slugs: list };
|
|
20
|
+
}
|
|
21
|
+
function localizedPath(path, locale, defaultLocale) {
|
|
22
|
+
if (locale === defaultLocale) return path;
|
|
23
|
+
return `/${locale}${path === "/" ? "" : path}`;
|
|
24
|
+
}
|
|
25
|
+
function getPath(obj, keys) {
|
|
26
|
+
return keys.reduce((o, k) => o == null ? void 0 : o[k], obj);
|
|
27
|
+
}
|
|
28
|
+
function setPath(obj, keys, value) {
|
|
29
|
+
const clone = (v) => Array.isArray(v) ? [...v] : { ...v };
|
|
30
|
+
const root = clone(obj);
|
|
31
|
+
let cur = root;
|
|
32
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
33
|
+
cur[keys[i]] = clone(cur[keys[i]]);
|
|
34
|
+
cur = cur[keys[i]];
|
|
35
|
+
}
|
|
36
|
+
cur[keys[keys.length - 1]] = value;
|
|
37
|
+
return root;
|
|
38
|
+
}
|
|
39
|
+
function localizeBlocks(blocks, overlay) {
|
|
40
|
+
if (!overlay || blocks.length === 0) return blocks;
|
|
41
|
+
let out = blocks;
|
|
42
|
+
for (const [key, value] of Object.entries(overlay)) {
|
|
43
|
+
if (typeof value !== "string") continue;
|
|
44
|
+
const [blockKey, ...rest] = key.split(".");
|
|
45
|
+
if (rest.length === 0) continue;
|
|
46
|
+
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);
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
function localizePage(page, locale, defaultLocale) {
|
|
58
|
+
if (!locale || locale === defaultLocale) return page;
|
|
59
|
+
const overlay = page.translations?.[locale];
|
|
60
|
+
if (!overlay) return page;
|
|
61
|
+
let out = { ...page, blocks: localizeBlocks(page.blocks ?? [], overlay) };
|
|
62
|
+
for (const [key, value] of Object.entries(overlay)) {
|
|
63
|
+
if (typeof value !== "string") continue;
|
|
64
|
+
if (key === "title") out = { ...out, title: value };
|
|
65
|
+
else if (key.startsWith("seo.")) out = { ...out, seo: { ...out.seo ?? {}, [key.slice(4)]: value } };
|
|
66
|
+
}
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
function pruneTranslations(translations, blocks) {
|
|
70
|
+
if (!translations) return void 0;
|
|
71
|
+
const keys = new Set((blocks ?? []).map((b) => b?._key).filter(Boolean));
|
|
72
|
+
const out = {};
|
|
73
|
+
for (const [locale, overlay] of Object.entries(translations)) {
|
|
74
|
+
const kept = Object.fromEntries(
|
|
75
|
+
Object.entries(overlay).filter(([k]) => k === "title" || k.startsWith("seo.") || keys.has(k.split(".")[0]))
|
|
76
|
+
);
|
|
77
|
+
if (Object.keys(kept).length > 0) out[locale] = kept;
|
|
78
|
+
}
|
|
79
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
80
|
+
}
|
|
81
|
+
export {
|
|
82
|
+
TranslationsSchema,
|
|
83
|
+
localizeBlocks,
|
|
84
|
+
localizePage,
|
|
85
|
+
localizedPath,
|
|
86
|
+
parseLocaleSlugs,
|
|
87
|
+
pruneTranslations,
|
|
88
|
+
siteLocales
|
|
89
|
+
};
|
|
90
|
+
//# sourceMappingURL=overlay.js.map
|
|
@@ -0,0 +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":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -20,6 +20,16 @@ export { definePattern, instantiatePattern, collectPatternTypes, builtinPatterns
|
|
|
20
20
|
export { builtinThemePresets, type ThemePreset } from "./theme/presets";
|
|
21
21
|
export { defineSiteConfig } from "./schema/siteConfig";
|
|
22
22
|
export type { SiteConfig, SiteMode } from "./schema/siteConfig";
|
|
23
|
+
export { FieldSchema, FieldsSchema, fieldsToZod, fieldsDefaults, isFieldVisible, isRichTextKey } from "./schema/fields";
|
|
24
|
+
export type { Field, FieldHints, ShowIf } from "./schema/fields";
|
|
25
|
+
export { SymbolSchema } from "./schema/symbols";
|
|
26
|
+
export type { Symbol, StoredSymbol } from "./schema/symbols";
|
|
27
|
+
export { CustomBlockSchema } from "./schema/customBlocks";
|
|
28
|
+
export type { CustomBlock, CustomBlock as CustomBlockData } from "./schema/customBlocks";
|
|
29
|
+
export { customBlockToDefinition } from "./blocks/customTemplate/definition";
|
|
30
|
+
export { interpretTemplate, scopeCss } from "./blocks/customTemplate/interpret";
|
|
31
|
+
export { siteLocales, parseLocaleSlugs, localizedPath, localizePage, localizeBlocks, pruneTranslations, TranslationsSchema, } from "./i18n/overlay";
|
|
32
|
+
export type { Translations, LocaleConfig } from "./i18n/overlay";
|
|
23
33
|
export type { ThemeTokens, NavItem, SiteSettings, FormsSender } from "./schema/settings";
|
|
24
34
|
export type { Seo } from "./schema/seo";
|
|
25
35
|
export type { Page, PageStatus } from "./schema/pages";
|
|
@@ -27,15 +37,7 @@ export type { FormDefinition, FormField } from "./schema/forms";
|
|
|
27
37
|
export { AuthProvider, useAuth } from "./auth/AuthProvider";
|
|
28
38
|
export { LoginForm } from "./auth/LoginForm";
|
|
29
39
|
export { roleForSite, canEdit, canManageSite, isSuperadmin, type Role, type XiClaims, } from "./auth/roles";
|
|
30
|
-
/** @deprecated Gebruik het block-model (pagina's/drafts) + de visuele editor i.p.v. `cms/content`. */
|
|
31
|
-
export { ContentProvider, useContent } from "./content/ContentProvider";
|
|
32
40
|
export type { NewsArticle, MediaItem, MediaVariant } from "./content/types";
|
|
33
|
-
/** @deprecated Gebruik een block met `data-xicms-edit` — inline bewerken loopt via de visuele editor. */
|
|
34
|
-
export { EditableText } from "./components/EditableText";
|
|
35
|
-
/** @deprecated Gebruik een richText-block — inline bewerken loopt via de visuele editor. */
|
|
36
|
-
export { EditableRichText } from "./components/EditableRichText";
|
|
37
|
-
/** @deprecated Gebruik een image-block (inline vervangen via de mediakiezer in de visuele editor). */
|
|
38
|
-
export { EditableImage } from "./components/EditableImage";
|
|
39
41
|
export { EditBar } from "./components/EditBar";
|
|
40
42
|
export { ResponsiveImage } from "./components/ResponsiveImage";
|
|
41
43
|
export { PageView } from "./components/PageView";
|
package/dist/index.js
CHANGED
|
@@ -19,6 +19,20 @@ import {
|
|
|
19
19
|
} from "./patterns";
|
|
20
20
|
import { builtinThemePresets } from "./theme/presets";
|
|
21
21
|
import { defineSiteConfig } from "./schema/siteConfig";
|
|
22
|
+
import { FieldSchema, FieldsSchema, fieldsToZod, fieldsDefaults, isFieldVisible, isRichTextKey } from "./schema/fields";
|
|
23
|
+
import { SymbolSchema } from "./schema/symbols";
|
|
24
|
+
import { CustomBlockSchema } from "./schema/customBlocks";
|
|
25
|
+
import { customBlockToDefinition } from "./blocks/customTemplate/definition";
|
|
26
|
+
import { interpretTemplate, scopeCss } from "./blocks/customTemplate/interpret";
|
|
27
|
+
import {
|
|
28
|
+
siteLocales,
|
|
29
|
+
parseLocaleSlugs,
|
|
30
|
+
localizedPath,
|
|
31
|
+
localizePage,
|
|
32
|
+
localizeBlocks,
|
|
33
|
+
pruneTranslations,
|
|
34
|
+
TranslationsSchema
|
|
35
|
+
} from "./i18n/overlay";
|
|
22
36
|
import { AuthProvider, useAuth } from "./auth/AuthProvider";
|
|
23
37
|
import { LoginForm } from "./auth/LoginForm";
|
|
24
38
|
import {
|
|
@@ -27,10 +41,6 @@ import {
|
|
|
27
41
|
canManageSite,
|
|
28
42
|
isSuperadmin
|
|
29
43
|
} from "./auth/roles";
|
|
30
|
-
import { ContentProvider, useContent } from "./content/ContentProvider";
|
|
31
|
-
import { EditableText } from "./components/EditableText";
|
|
32
|
-
import { EditableRichText } from "./components/EditableRichText";
|
|
33
|
-
import { EditableImage } from "./components/EditableImage";
|
|
34
44
|
import { EditBar } from "./components/EditBar";
|
|
35
45
|
import { ResponsiveImage } from "./components/ResponsiveImage";
|
|
36
46
|
import { PageView } from "./components/PageView";
|
|
@@ -92,15 +102,14 @@ export {
|
|
|
92
102
|
ConsentEmbed,
|
|
93
103
|
ConsentGate,
|
|
94
104
|
ConsentReopenLink,
|
|
95
|
-
|
|
105
|
+
CustomBlockSchema,
|
|
96
106
|
DEMO_EVENT,
|
|
97
107
|
DemoLock,
|
|
98
108
|
DemoStudio,
|
|
99
109
|
EditBar,
|
|
100
|
-
EditableImage,
|
|
101
|
-
EditableRichText,
|
|
102
|
-
EditableText,
|
|
103
110
|
EditorProvider,
|
|
111
|
+
FieldSchema,
|
|
112
|
+
FieldsSchema,
|
|
104
113
|
ImageProvider,
|
|
105
114
|
Img,
|
|
106
115
|
InlineImageEditProvider,
|
|
@@ -116,6 +125,8 @@ export {
|
|
|
116
125
|
ResponsiveImage,
|
|
117
126
|
SiteFooter,
|
|
118
127
|
SiteSettingsProvider,
|
|
128
|
+
SymbolSchema,
|
|
129
|
+
TranslationsSchema,
|
|
119
130
|
XiCMSProvider,
|
|
120
131
|
applyBlockOverrides,
|
|
121
132
|
assertModuleContract,
|
|
@@ -128,24 +139,35 @@ export {
|
|
|
128
139
|
canManageSite,
|
|
129
140
|
collectPatternTypes,
|
|
130
141
|
createRegistry,
|
|
142
|
+
customBlockToDefinition,
|
|
131
143
|
defineBlock,
|
|
132
144
|
defineModule,
|
|
133
145
|
definePattern,
|
|
134
146
|
defineSiteConfig,
|
|
135
147
|
defineTheme,
|
|
136
148
|
demoIdForPath,
|
|
149
|
+
fieldsDefaults,
|
|
150
|
+
fieldsToZod,
|
|
137
151
|
getBucket,
|
|
138
152
|
getDb,
|
|
139
153
|
getFirebaseApp,
|
|
140
154
|
getFirebaseAuth,
|
|
141
155
|
initAppCheck,
|
|
142
156
|
instantiatePattern,
|
|
157
|
+
interpretTemplate,
|
|
158
|
+
isFieldVisible,
|
|
143
159
|
isFirebaseConfigured,
|
|
144
160
|
isModuleActive,
|
|
161
|
+
isRichTextKey,
|
|
145
162
|
isSuperadmin,
|
|
146
163
|
loadDemoDraft,
|
|
164
|
+
localizeBlocks,
|
|
165
|
+
localizePage,
|
|
166
|
+
localizedPath,
|
|
147
167
|
mergeRegistry,
|
|
148
168
|
moduleIdForBlockType,
|
|
169
|
+
parseLocaleSlugs,
|
|
170
|
+
pruneTranslations,
|
|
149
171
|
registerDemoForm,
|
|
150
172
|
registerDemoMedia,
|
|
151
173
|
registerDemoNews,
|
|
@@ -153,9 +175,10 @@ export {
|
|
|
153
175
|
resolveDemoPage,
|
|
154
176
|
resolveModules,
|
|
155
177
|
roleForSite,
|
|
178
|
+
scopeCss,
|
|
179
|
+
siteLocales,
|
|
156
180
|
useAuth,
|
|
157
181
|
useConsent,
|
|
158
|
-
useContent,
|
|
159
182
|
useEditor,
|
|
160
183
|
useImageComponent,
|
|
161
184
|
useLinkComponent,
|
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\";\nexport
|
|
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":[]}
|