meno-core 1.0.21 → 1.0.23
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/build-static.test.ts +424 -0
- package/build-static.ts +100 -13
- package/lib/client/ClientInitializer.ts +4 -0
- package/lib/client/core/ComponentBuilder.ts +155 -16
- package/lib/client/core/builders/embedBuilder.ts +48 -6
- package/lib/client/core/builders/linkBuilder.ts +2 -2
- package/lib/client/core/builders/linkNodeBuilder.ts +45 -5
- package/lib/client/core/builders/listBuilder.ts +12 -3
- package/lib/client/routing/Router.tsx +8 -1
- package/lib/client/templateEngine.ts +89 -98
- package/lib/server/__integration__/api-routes.test.ts +148 -0
- package/lib/server/__integration__/cms-integration.test.ts +161 -0
- package/lib/server/__integration__/server-lifecycle.test.ts +101 -0
- package/lib/server/__integration__/ssr-rendering.test.ts +131 -0
- package/lib/server/__integration__/static-assets.test.ts +80 -0
- package/lib/server/__integration__/test-helpers.ts +205 -0
- package/lib/server/ab/generateFunctions.ts +346 -0
- package/lib/server/ab/trackingScript.ts +45 -0
- package/lib/server/index.ts +2 -2
- package/lib/server/jsonLoader.ts +124 -46
- package/lib/server/routes/api/cms.ts +3 -2
- package/lib/server/routes/api/components.ts +13 -2
- package/lib/server/services/cmsService.ts +0 -5
- package/lib/server/services/componentService.ts +255 -29
- package/lib/server/services/configService.test.ts +950 -0
- package/lib/server/services/configService.ts +39 -0
- package/lib/server/services/index.ts +1 -1
- package/lib/server/ssr/htmlGenerator.test.ts +992 -0
- package/lib/server/ssr/htmlGenerator.ts +3 -3
- package/lib/server/ssr/imageMetadata.test.ts +168 -0
- package/lib/server/ssr/imageMetadata.ts +58 -0
- package/lib/server/ssr/jsCollector.test.ts +287 -0
- package/lib/server/ssr/ssrRenderer.test.ts +3702 -0
- package/lib/server/ssr/ssrRenderer.ts +131 -15
- package/lib/shared/constants.ts +3 -0
- package/lib/shared/fontLoader.test.ts +335 -0
- package/lib/shared/i18n.test.ts +106 -0
- package/lib/shared/i18n.ts +17 -11
- package/lib/shared/index.ts +3 -0
- package/lib/shared/itemTemplateUtils.ts +43 -1
- package/lib/shared/libraryLoader.test.ts +392 -0
- package/lib/shared/linkUtils.ts +24 -0
- package/lib/shared/nodeUtils.test.ts +100 -0
- package/lib/shared/nodeUtils.ts +43 -0
- package/lib/shared/registry/NodeTypeDefinition.ts +2 -2
- package/lib/shared/registry/nodeTypes/ListNodeType.ts +20 -2
- package/lib/shared/richtext/htmlToTiptap.test.ts +948 -0
- package/lib/shared/richtext/htmlToTiptap.ts +46 -2
- package/lib/shared/richtext/tiptapToHtml.ts +65 -0
- package/lib/shared/richtext/types.ts +4 -1
- package/lib/shared/types/cms.ts +2 -0
- package/lib/shared/types/components.ts +12 -3
- package/lib/shared/types/experiments.ts +55 -0
- package/lib/shared/types/index.ts +10 -0
- package/lib/shared/utils.ts +2 -6
- package/lib/shared/validation/propValidator.test.ts +50 -0
- package/lib/shared/validation/propValidator.ts +2 -2
- package/lib/shared/validation/schemas.ts +10 -2
- package/package.json +1 -1
|
@@ -102,6 +102,9 @@ function isBlockNode(type: string): boolean {
|
|
|
102
102
|
'tableRow',
|
|
103
103
|
'tableCell',
|
|
104
104
|
'tableHeader',
|
|
105
|
+
'iframe',
|
|
106
|
+
'rawHtml',
|
|
107
|
+
'menoComponent',
|
|
105
108
|
].includes(type);
|
|
106
109
|
}
|
|
107
110
|
|
|
@@ -169,6 +172,8 @@ function parseNode(node: Node): TiptapNode | TiptapNode[] | null {
|
|
|
169
172
|
return { type: 'hardBreak' };
|
|
170
173
|
case 'img':
|
|
171
174
|
return parseImage(element);
|
|
175
|
+
case 'iframe':
|
|
176
|
+
return parseIframe(element);
|
|
172
177
|
case 'table':
|
|
173
178
|
return parseTable(element);
|
|
174
179
|
|
|
@@ -192,8 +197,19 @@ function parseNode(node: Node): TiptapNode | TiptapNode[] | null {
|
|
|
192
197
|
case 'span':
|
|
193
198
|
return parseSpan(element);
|
|
194
199
|
|
|
195
|
-
// Container elements -
|
|
196
|
-
case 'div':
|
|
200
|
+
// Container elements - check for special data attributes first
|
|
201
|
+
case 'div': {
|
|
202
|
+
// Raw HTML block
|
|
203
|
+
if (element.hasAttribute('data-raw-html')) {
|
|
204
|
+
return { type: 'rawHtml', attrs: { content: element.innerHTML } };
|
|
205
|
+
}
|
|
206
|
+
// Meno component embed
|
|
207
|
+
if (element.hasAttribute('data-meno-component')) {
|
|
208
|
+
return parseMenoComponent(element);
|
|
209
|
+
}
|
|
210
|
+
// Regular div - parse children
|
|
211
|
+
return parseChildren(element);
|
|
212
|
+
}
|
|
197
213
|
case 'article':
|
|
198
214
|
case 'section':
|
|
199
215
|
case 'main':
|
|
@@ -343,6 +359,34 @@ function parseImage(element: HTMLElement): TiptapNode {
|
|
|
343
359
|
return { type: 'image', attrs };
|
|
344
360
|
}
|
|
345
361
|
|
|
362
|
+
function parseIframe(element: HTMLElement): TiptapNode {
|
|
363
|
+
const src = element.getAttribute('src') || '';
|
|
364
|
+
const width = element.getAttribute('width') || '100%';
|
|
365
|
+
const height = element.getAttribute('height') || '315';
|
|
366
|
+
|
|
367
|
+
return {
|
|
368
|
+
type: 'iframe',
|
|
369
|
+
attrs: { src, width, height },
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function parseMenoComponent(element: HTMLElement): TiptapNode {
|
|
374
|
+
const component = element.getAttribute('data-meno-component') || '';
|
|
375
|
+
let props: Record<string, unknown> = {};
|
|
376
|
+
const propsStr = element.getAttribute('data-meno-props');
|
|
377
|
+
if (propsStr) {
|
|
378
|
+
try {
|
|
379
|
+
props = JSON.parse(propsStr);
|
|
380
|
+
} catch {
|
|
381
|
+
// ignore parse errors
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return {
|
|
385
|
+
type: 'menoComponent',
|
|
386
|
+
attrs: { component, props },
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
|
|
346
390
|
function parseTable(element: HTMLElement): TiptapNode {
|
|
347
391
|
const rows: TiptapNode[] = [];
|
|
348
392
|
|
|
@@ -47,6 +47,9 @@ export const RICH_TEXT_ALLOWED_TAGS = [
|
|
|
47
47
|
'hr',
|
|
48
48
|
'figure',
|
|
49
49
|
'figcaption',
|
|
50
|
+
// Embeds
|
|
51
|
+
'iframe',
|
|
52
|
+
'div',
|
|
50
53
|
];
|
|
51
54
|
|
|
52
55
|
export const RICH_TEXT_ALLOWED_ATTRS = [
|
|
@@ -68,6 +71,14 @@ export const RICH_TEXT_ALLOWED_ATTRS = [
|
|
|
68
71
|
'data-language',
|
|
69
72
|
// Alignment (via style)
|
|
70
73
|
'style',
|
|
74
|
+
// Iframe attributes
|
|
75
|
+
'frameborder',
|
|
76
|
+
'allowfullscreen',
|
|
77
|
+
'allow',
|
|
78
|
+
// Raw HTML / Component attributes
|
|
79
|
+
'data-raw-html',
|
|
80
|
+
'data-meno-component',
|
|
81
|
+
'data-meno-props',
|
|
71
82
|
];
|
|
72
83
|
|
|
73
84
|
/**
|
|
@@ -184,6 +195,15 @@ function renderNode(node: TiptapNode): string {
|
|
|
184
195
|
case 'tableHeader':
|
|
185
196
|
return renderTableCell(node, 'th');
|
|
186
197
|
|
|
198
|
+
case 'iframe':
|
|
199
|
+
return renderIframe(node);
|
|
200
|
+
|
|
201
|
+
case 'rawHtml':
|
|
202
|
+
return renderRawHtml(node);
|
|
203
|
+
|
|
204
|
+
case 'menoComponent':
|
|
205
|
+
return renderMenoComponent(node);
|
|
206
|
+
|
|
187
207
|
default:
|
|
188
208
|
// Unknown node type - try to render content
|
|
189
209
|
if (node.content) {
|
|
@@ -299,6 +319,51 @@ function renderTableCell(node: TiptapNode, tag: 'td' | 'th'): string {
|
|
|
299
319
|
return `<${tag}${attrStr}>${content}</${tag}>`;
|
|
300
320
|
}
|
|
301
321
|
|
|
322
|
+
/**
|
|
323
|
+
* Render iframe embed
|
|
324
|
+
*/
|
|
325
|
+
function renderIframe(node: TiptapNode): string {
|
|
326
|
+
const src = node.attrs?.src as string | undefined;
|
|
327
|
+
if (!src) return '';
|
|
328
|
+
|
|
329
|
+
// Only allow https:// URLs for security
|
|
330
|
+
if (!src.startsWith('https://')) return '';
|
|
331
|
+
|
|
332
|
+
const width = (node.attrs?.width as string | undefined) || '100%';
|
|
333
|
+
const height = (node.attrs?.height as string | undefined) || '315';
|
|
334
|
+
|
|
335
|
+
return `<iframe src="${escapeAttr(src)}" width="${escapeAttr(String(width))}" height="${escapeAttr(String(height))}" frameborder="0" allowfullscreen></iframe>`;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Render raw HTML block
|
|
340
|
+
*/
|
|
341
|
+
function renderRawHtml(node: TiptapNode): string {
|
|
342
|
+
const content = node.attrs?.content as string | undefined;
|
|
343
|
+
if (!content) return '';
|
|
344
|
+
|
|
345
|
+
// Strip script tags and event handlers for safety
|
|
346
|
+
const sanitized = content
|
|
347
|
+
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
|
|
348
|
+
.replace(/\son\w+\s*=\s*(['"])[^'"]*\1/gi, '')
|
|
349
|
+
.replace(/\son\w+\s*=\s*[^\s>]*/gi, '');
|
|
350
|
+
|
|
351
|
+
return `<div data-raw-html="true">${sanitized}</div>`;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Render Meno component marker for SSR expansion
|
|
356
|
+
*/
|
|
357
|
+
function renderMenoComponent(node: TiptapNode): string {
|
|
358
|
+
const component = node.attrs?.component as string | undefined;
|
|
359
|
+
if (!component) return '';
|
|
360
|
+
|
|
361
|
+
const props = node.attrs?.props as Record<string, unknown> | undefined;
|
|
362
|
+
const propsJson = props ? escapeAttr(JSON.stringify(props)) : '{}';
|
|
363
|
+
|
|
364
|
+
return `<div data-meno-component="${escapeAttr(component)}" data-meno-props="${propsJson}"></div>`;
|
|
365
|
+
}
|
|
366
|
+
|
|
302
367
|
/**
|
|
303
368
|
* Render text node with marks
|
|
304
369
|
*/
|
package/lib/shared/types/cms.ts
CHANGED
|
@@ -11,6 +11,7 @@ export type CMSFieldType =
|
|
|
11
11
|
| 'number' // Numeric value
|
|
12
12
|
| 'boolean' // True/false
|
|
13
13
|
| 'image' // Image URL/path
|
|
14
|
+
| 'file' // File URL/path (any file type)
|
|
14
15
|
| 'date' // ISO date string
|
|
15
16
|
| 'select' // Single selection from options
|
|
16
17
|
| 'reference' // Reference to another collection item
|
|
@@ -25,6 +26,7 @@ export interface CMSFieldDefinition {
|
|
|
25
26
|
label?: string; // Display label in editor
|
|
26
27
|
description?: string; // Help text
|
|
27
28
|
options?: string[]; // For 'select' type
|
|
29
|
+
accept?: string; // For 'file' type - MIME filter (e.g. "application/pdf", "*/*")
|
|
28
30
|
collection?: string; // For 'reference' type - target collection ID
|
|
29
31
|
multiple?: boolean; // For 'reference' type - allows array of IDs
|
|
30
32
|
}
|
|
@@ -10,13 +10,20 @@ import type { ComponentNode } from './nodes';
|
|
|
10
10
|
*/
|
|
11
11
|
export type PropType = 'string' | 'select' | 'boolean' | 'number' | 'link' | 'file' | 'rich-text' | 'list';
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Project-level enum configuration
|
|
15
|
+
* Keys are enum names, values are arrays of options
|
|
16
|
+
*/
|
|
17
|
+
export type EnumsConfig = Record<string, readonly string[]>;
|
|
18
|
+
|
|
13
19
|
/**
|
|
14
20
|
* Internationalization (i18n) value object
|
|
15
21
|
* Keys are locale codes (e.g., 'en', 'pl', 'de')
|
|
22
|
+
* Values can be strings (for string props) or arrays (for list props)
|
|
16
23
|
*/
|
|
17
24
|
export interface I18nValue {
|
|
18
25
|
_i18n: true;
|
|
19
|
-
[locale: string]: string | true; // true is for the _i18n marker
|
|
26
|
+
[locale: string]: string | unknown[] | true; // true is for the _i18n marker, arrays for list props
|
|
20
27
|
}
|
|
21
28
|
|
|
22
29
|
/**
|
|
@@ -58,7 +65,8 @@ export interface LinkPropValue {
|
|
|
58
65
|
export interface BasePropDefinition {
|
|
59
66
|
type: Exclude<PropType, 'list'>;
|
|
60
67
|
default?: string | number | boolean | I18nValue | LinkPropValue;
|
|
61
|
-
options?: readonly string[]; // Required for "select" type
|
|
68
|
+
options?: readonly string[]; // Required for "select" type (inline options)
|
|
69
|
+
enumName?: string; // For "select" type: reference to project-level enum
|
|
62
70
|
accept?: string; // For "file" type: MIME pattern like "image/*", "video/*"
|
|
63
71
|
}
|
|
64
72
|
|
|
@@ -70,8 +78,9 @@ export type ListItemSchema = Record<string, BasePropDefinition>;
|
|
|
70
78
|
|
|
71
79
|
/**
|
|
72
80
|
* List item value type
|
|
81
|
+
* Supports i18n values for localized string fields
|
|
73
82
|
*/
|
|
74
|
-
export type ListItemValue = Record<string, string | number | boolean | LinkPropValue | null>;
|
|
83
|
+
export type ListItemValue = Record<string, string | number | boolean | LinkPropValue | I18nValue | null>;
|
|
75
84
|
|
|
76
85
|
/**
|
|
77
86
|
* List prop definition - for array/list data
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A/B Testing Experiment Types
|
|
3
|
+
* Defines experiment configuration, variants, and conversion goals
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface ConversionGoal {
|
|
7
|
+
id: string
|
|
8
|
+
name: string
|
|
9
|
+
type: 'click' | 'navigation' | 'custom'
|
|
10
|
+
/** CSS selector for click goals */
|
|
11
|
+
selector?: string
|
|
12
|
+
/** URL pattern for navigation goals */
|
|
13
|
+
url?: string
|
|
14
|
+
/** Custom event name */
|
|
15
|
+
event?: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ExperimentVariant {
|
|
19
|
+
branch: string
|
|
20
|
+
/** Percentage of traffic (0-100) */
|
|
21
|
+
weight: number
|
|
22
|
+
/** Cloudflare Pages preview deploy URL, e.g. "https://variant-blue.project.pages.dev" */
|
|
23
|
+
deployUrl: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface Experiment {
|
|
27
|
+
id: string
|
|
28
|
+
name: string
|
|
29
|
+
status: 'draft' | 'running' | 'paused' | 'completed'
|
|
30
|
+
controlBranch: string
|
|
31
|
+
variants: ExperimentVariant[]
|
|
32
|
+
/** Percentage of traffic for control (0-100) */
|
|
33
|
+
controlWeight: number
|
|
34
|
+
conversionGoals: ConversionGoal[]
|
|
35
|
+
createdAt: string
|
|
36
|
+
startedAt?: string
|
|
37
|
+
endedAt?: string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface ExperimentResults {
|
|
41
|
+
experimentId: string
|
|
42
|
+
variants: VariantResult[]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface VariantResult {
|
|
46
|
+
variant: string
|
|
47
|
+
visitors: number
|
|
48
|
+
conversions: number
|
|
49
|
+
conversionRate: number
|
|
50
|
+
/** Lift percentage compared to control */
|
|
51
|
+
lift?: number
|
|
52
|
+
/** p-value from z-test */
|
|
53
|
+
pValue?: number
|
|
54
|
+
isSignificant: boolean
|
|
55
|
+
}
|
|
@@ -34,6 +34,7 @@ export type {
|
|
|
34
34
|
I18nConfig,
|
|
35
35
|
LocaleConfig,
|
|
36
36
|
LinkPropValue,
|
|
37
|
+
EnumsConfig,
|
|
37
38
|
} from './components';
|
|
38
39
|
|
|
39
40
|
export {
|
|
@@ -139,5 +140,14 @@ export { DEFAULT_PREFETCH_CONFIG } from './prefetch';
|
|
|
139
140
|
// Export config types
|
|
140
141
|
export type { CSPConfig } from './config';
|
|
141
142
|
|
|
143
|
+
// Export experiment types
|
|
144
|
+
export type {
|
|
145
|
+
ConversionGoal,
|
|
146
|
+
ExperimentVariant,
|
|
147
|
+
Experiment,
|
|
148
|
+
ExperimentResults,
|
|
149
|
+
VariantResult,
|
|
150
|
+
} from './experiments';
|
|
151
|
+
|
|
142
152
|
// Note: Path types are exported from ../paths/index.ts, not from here
|
|
143
153
|
|
package/lib/shared/utils.ts
CHANGED
|
@@ -7,16 +7,12 @@ import { stringToPath, pathToLegacyString, getParentPath, isRootPath as isRootPa
|
|
|
7
7
|
import { ROOT_0_STRING } from './pathArrayUtils';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* Deep clone an object using
|
|
10
|
+
* Deep clone an object using structuredClone
|
|
11
11
|
* @param obj - The object to clone
|
|
12
12
|
* @returns A deep clone of the object
|
|
13
13
|
*/
|
|
14
14
|
export function deepClone<T>(obj: T): T {
|
|
15
|
-
|
|
16
|
-
return JSON.parse(JSON.stringify(obj)) as T;
|
|
17
|
-
} catch (error) {
|
|
18
|
-
throw error;
|
|
19
|
-
}
|
|
15
|
+
return structuredClone(obj);
|
|
20
16
|
}
|
|
21
17
|
|
|
22
18
|
/**
|
|
@@ -181,6 +181,56 @@ describe('Prop Validator', () => {
|
|
|
181
181
|
expect(result.valid).toBe(true);
|
|
182
182
|
expect(result.props.text).toBe('<b>Bold text</b>');
|
|
183
183
|
});
|
|
184
|
+
|
|
185
|
+
test('list prop with I18nValue passes validation', () => {
|
|
186
|
+
const propDefs: Record<string, PropDefinition> = {
|
|
187
|
+
items: {
|
|
188
|
+
type: 'list',
|
|
189
|
+
itemSchema: {
|
|
190
|
+
title: { type: 'string', default: '' },
|
|
191
|
+
text: { type: 'string', default: '' },
|
|
192
|
+
},
|
|
193
|
+
default: [],
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
const passedProps = {
|
|
197
|
+
items: {
|
|
198
|
+
_i18n: true,
|
|
199
|
+
en: [{ title: 'English', text: 'Text' }],
|
|
200
|
+
pl: [{ title: 'Polish', text: 'Tekst' }],
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const result = validateComponentProps(propDefs, passedProps);
|
|
205
|
+
|
|
206
|
+
expect(result.valid).toBe(true);
|
|
207
|
+
// I18nValue should be preserved (resolved later by resolveI18nInProps)
|
|
208
|
+
expect(result.props.items).toEqual({
|
|
209
|
+
_i18n: true,
|
|
210
|
+
en: [{ title: 'English', text: 'Text' }],
|
|
211
|
+
pl: [{ title: 'Polish', text: 'Tekst' }],
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test('list prop with plain array passes validation', () => {
|
|
216
|
+
const propDefs: Record<string, PropDefinition> = {
|
|
217
|
+
items: {
|
|
218
|
+
type: 'list',
|
|
219
|
+
itemSchema: {
|
|
220
|
+
title: { type: 'string', default: '' },
|
|
221
|
+
},
|
|
222
|
+
default: [],
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
const passedProps = {
|
|
226
|
+
items: [{ title: 'Item 1' }, { title: 'Item 2' }],
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const result = validateComponentProps(propDefs, passedProps);
|
|
230
|
+
|
|
231
|
+
expect(result.valid).toBe(true);
|
|
232
|
+
expect(result.props.items).toEqual([{ title: 'Item 1' }, { title: 'Item 2' }]);
|
|
233
|
+
});
|
|
184
234
|
});
|
|
185
235
|
|
|
186
236
|
describe('Backward compatibility maintained', () => {
|
|
@@ -167,9 +167,9 @@ function validateSingleProp(
|
|
|
167
167
|
break;
|
|
168
168
|
|
|
169
169
|
case 'list':
|
|
170
|
-
// List type accepts arrays
|
|
170
|
+
// List type accepts arrays and i18n values (i18n values will be resolved later)
|
|
171
171
|
coercedValue = value;
|
|
172
|
-
typeValid = Array.isArray(value);
|
|
172
|
+
typeValid = Array.isArray(value) || isI18nValue(value);
|
|
173
173
|
break;
|
|
174
174
|
|
|
175
175
|
default:
|
|
@@ -29,9 +29,16 @@ export const BasePropDefinitionSchema = z.object({
|
|
|
29
29
|
type: BasePropTypeSchema,
|
|
30
30
|
default: z.union([z.string(), z.number(), z.boolean(), z.object({ href: z.string(), target: z.string().optional() })]).optional(),
|
|
31
31
|
options: z.array(z.string()).readonly().optional(),
|
|
32
|
+
enumName: z.string().optional(), // For 'select' type: reference to project-level enum
|
|
32
33
|
accept: z.string().optional(), // For 'file' type: MIME pattern like "image/*", "video/*"
|
|
33
34
|
}).passthrough();
|
|
34
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Project-level enums configuration schema
|
|
38
|
+
* Keys are enum names, values are arrays of options
|
|
39
|
+
*/
|
|
40
|
+
export const EnumsConfigSchema = z.record(z.string(), z.array(z.string()));
|
|
41
|
+
|
|
35
42
|
/**
|
|
36
43
|
* List item schema - defines structure of each item in a list prop
|
|
37
44
|
*/
|
|
@@ -269,7 +276,7 @@ export const ListNodeSchemaBasic: z.ZodType<any> = z.lazy(() => z.object({
|
|
|
269
276
|
sourceType: z.enum(['prop', 'collection']).optional(), // defaults to 'prop'
|
|
270
277
|
source: z.string().optional(), // Source prop name or collection name
|
|
271
278
|
collection: z.string().optional(), // Legacy field for cms-list migration
|
|
272
|
-
tag: z.string().optional(), // Wrapper element tag, defaults to 'div'
|
|
279
|
+
tag: z.union([z.string(), z.literal(false)]).optional(), // Wrapper element tag, defaults to 'div'. Set to false for no container.
|
|
273
280
|
label: z.string().optional(),
|
|
274
281
|
if: IfConditionSchema.optional(),
|
|
275
282
|
style: StyleValueSchema.optional(),
|
|
@@ -476,7 +483,7 @@ export const PageDataSchema = z.union([
|
|
|
476
483
|
* CMS field type schema
|
|
477
484
|
*/
|
|
478
485
|
export const CMSFieldTypeSchema = z.enum([
|
|
479
|
-
'string', 'text', 'rich-text', 'number', 'boolean', 'image', 'date', 'select', 'reference', 'i18n', 'i18n-text'
|
|
486
|
+
'string', 'text', 'rich-text', 'number', 'boolean', 'image', 'file', 'date', 'select', 'reference', 'i18n', 'i18n-text'
|
|
480
487
|
]);
|
|
481
488
|
|
|
482
489
|
/**
|
|
@@ -489,6 +496,7 @@ export const CMSFieldDefinitionSchema = z.object({
|
|
|
489
496
|
label: z.string().optional(),
|
|
490
497
|
description: z.string().optional(),
|
|
491
498
|
options: z.array(z.string()).optional(),
|
|
499
|
+
accept: z.string().optional(),
|
|
492
500
|
collection: z.string().optional(),
|
|
493
501
|
multiple: z.boolean().optional(),
|
|
494
502
|
}).passthrough();
|