meno-core 1.0.48 → 1.0.49
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/build-static.js +7 -7
- package/dist/chunks/{chunk-UUA5LEWF.js → chunk-6IVUG7FY.js} +138 -7
- package/dist/chunks/chunk-6IVUG7FY.js.map +7 -0
- package/dist/chunks/{chunk-B2RTLDXY.js → chunk-AZQYF6KE.js} +132 -1
- package/dist/chunks/chunk-AZQYF6KE.js.map +7 -0
- package/dist/chunks/{chunk-NKUV77SR.js → chunk-CHD5UCFF.js} +21 -9
- package/dist/chunks/{chunk-NKUV77SR.js.map → chunk-CHD5UCFF.js.map} +2 -2
- package/dist/chunks/{chunk-TPQ7APVQ.js → chunk-EQYDSPBB.js} +418 -62
- package/dist/chunks/chunk-EQYDSPBB.js.map +7 -0
- package/dist/chunks/{chunk-RQSTH2BS.js → chunk-H4JSCDNW.js} +2 -2
- package/dist/chunks/{chunk-EK4KESLU.js → chunk-J23ZX5AP.js} +8 -2
- package/dist/chunks/{chunk-EK4KESLU.js.map → chunk-J23ZX5AP.js.map} +2 -2
- package/dist/chunks/{chunk-D5E3OKSL.js → chunk-JER5NQVM.js} +5 -5
- package/dist/chunks/{chunk-BJRKEPMP.js → chunk-KPU2XHOS.js} +5 -2
- package/dist/chunks/{chunk-BJRKEPMP.js.map → chunk-KPU2XHOS.js.map} +2 -2
- package/dist/chunks/{chunk-NP76N4HQ.js → chunk-LKAGAQ3M.js} +2 -2
- package/dist/chunks/{chunk-3FHJUHAS.js → chunk-S2CX6HFM.js} +260 -25
- package/dist/chunks/chunk-S2CX6HFM.js.map +7 -0
- package/dist/chunks/{configService-IGJEC3MC.js → configService-CCA6AIDI.js} +3 -3
- package/dist/entries/server-router.js +9 -9
- package/dist/entries/server-router.js.map +2 -2
- package/dist/lib/client/index.js +54 -20
- package/dist/lib/client/index.js.map +3 -3
- package/dist/lib/server/index.js +9 -9
- package/dist/lib/shared/index.js +46 -10
- package/dist/lib/shared/index.js.map +3 -3
- package/entries/server-router.tsx +6 -2
- package/lib/client/core/ComponentBuilder.ts +8 -1
- package/lib/client/core/builders/embedBuilder.ts +15 -2
- package/lib/client/core/builders/linkNodeBuilder.ts +15 -2
- package/lib/client/core/builders/localeListBuilder.ts +17 -6
- package/lib/client/styles/StyleInjector.ts +3 -2
- package/lib/client/theme.ts +4 -4
- package/lib/server/cssGenerator.test.ts +64 -1
- package/lib/server/cssGenerator.ts +48 -9
- package/lib/server/providers/fileSystemCMSProvider.test.ts +163 -0
- package/lib/server/providers/fileSystemCMSProvider.ts +200 -11
- package/lib/server/routes/index.ts +1 -1
- package/lib/server/routes/pages.ts +23 -1
- package/lib/server/services/cmsService.test.ts +246 -0
- package/lib/server/services/cmsService.ts +122 -5
- package/lib/server/services/configService.ts +5 -0
- package/lib/server/ssr/attributeBuilder.ts +41 -0
- package/lib/server/ssr/htmlGenerator.test.ts +113 -0
- package/lib/server/ssr/htmlGenerator.ts +51 -4
- package/lib/server/ssr/liveReloadIntegration.test.ts +209 -0
- package/lib/server/ssr/ssrRenderer.test.ts +306 -0
- package/lib/server/ssr/ssrRenderer.ts +182 -44
- package/lib/shared/cssGeneration.test.ts +267 -1
- package/lib/shared/cssGeneration.ts +240 -18
- package/lib/shared/cssProperties.test.ts +247 -1
- package/lib/shared/cssProperties.ts +196 -6
- package/lib/shared/interfaces/contentProvider.ts +39 -6
- package/lib/shared/pathSecurity.ts +16 -0
- package/lib/shared/responsiveScaling.test.ts +143 -0
- package/lib/shared/responsiveScaling.ts +253 -2
- package/lib/shared/themeDefaults.test.ts +3 -3
- package/lib/shared/themeDefaults.ts +3 -3
- package/lib/shared/types/cms.ts +28 -3
- package/lib/shared/types/index.ts +1 -0
- package/lib/shared/utilityClassConfig.ts +3 -0
- package/lib/shared/utilityClassMapper.test.ts +123 -0
- package/lib/shared/utilityClassMapper.ts +179 -8
- package/lib/shared/validation/schemas.ts +15 -1
- package/lib/shared/validation/validators.ts +26 -1
- package/package.json +1 -1
- package/dist/chunks/chunk-3FHJUHAS.js.map +0 -7
- package/dist/chunks/chunk-B2RTLDXY.js.map +0 -7
- package/dist/chunks/chunk-TPQ7APVQ.js.map +0 -7
- package/dist/chunks/chunk-UUA5LEWF.js.map +0 -7
- /package/dist/chunks/{chunk-RQSTH2BS.js.map → chunk-H4JSCDNW.js.map} +0 -0
- /package/dist/chunks/{chunk-D5E3OKSL.js.map → chunk-JER5NQVM.js.map} +0 -0
- /package/dist/chunks/{chunk-NP76N4HQ.js.map → chunk-LKAGAQ3M.js.map} +0 -0
- /package/dist/chunks/{configService-IGJEC3MC.js.map → configService-CCA6AIDI.js.map} +0 -0
|
@@ -43,6 +43,7 @@ mock.module('./imageMetadata', () => ({
|
|
|
43
43
|
mock.module('../services/configService', () => ({
|
|
44
44
|
configService: {
|
|
45
45
|
getImageFormat: () => mockImageFormat,
|
|
46
|
+
getResponsiveScales: () => ({ enabled: false, baseReference: 16 }),
|
|
46
47
|
load: async () => {},
|
|
47
48
|
isLoaded: () => true,
|
|
48
49
|
reset: () => {},
|
|
@@ -3109,6 +3110,203 @@ describe('ssrRenderer', () => {
|
|
|
3109
3110
|
expect(html).toContain('Author a1');
|
|
3110
3111
|
expect(html).toContain('Author a2');
|
|
3111
3112
|
});
|
|
3113
|
+
|
|
3114
|
+
test('inner list items dotted-ref resolves against host component prop', async () => {
|
|
3115
|
+
// {{config.0.tagIds}} is a dotted template — processStructure leaves it literal
|
|
3116
|
+
// (hasItemTemplates returns true for `\w+\.\w+`). The SSR items resolver must
|
|
3117
|
+
// see the host component's `config` prop in its scope.
|
|
3118
|
+
const calledWith: string[][] = [];
|
|
3119
|
+
const mockCmsService = {
|
|
3120
|
+
getItemsByIds: async (_collection: string, ids: string[]) => {
|
|
3121
|
+
calledWith.push(ids);
|
|
3122
|
+
return ids.map(id => ({ _id: id, name: `Tag ${id}` }));
|
|
3123
|
+
},
|
|
3124
|
+
getSchema: () => undefined,
|
|
3125
|
+
};
|
|
3126
|
+
|
|
3127
|
+
const Card: ComponentDefinition = {
|
|
3128
|
+
component: {
|
|
3129
|
+
interface: {
|
|
3130
|
+
config: { type: 'list' as any, default: [] },
|
|
3131
|
+
},
|
|
3132
|
+
structure: {
|
|
3133
|
+
type: 'list',
|
|
3134
|
+
sourceType: 'collection',
|
|
3135
|
+
source: 'tags',
|
|
3136
|
+
itemAs: 'tag',
|
|
3137
|
+
items: '{{config.0.tagIds}}',
|
|
3138
|
+
children: [
|
|
3139
|
+
{ type: 'node', tag: 'span', children: '{{tag.name}}' },
|
|
3140
|
+
],
|
|
3141
|
+
},
|
|
3142
|
+
},
|
|
3143
|
+
};
|
|
3144
|
+
|
|
3145
|
+
const node = {
|
|
3146
|
+
type: 'component',
|
|
3147
|
+
component: 'Card',
|
|
3148
|
+
props: {
|
|
3149
|
+
config: [{ tagIds: ['t1', 't2'] }],
|
|
3150
|
+
},
|
|
3151
|
+
};
|
|
3152
|
+
|
|
3153
|
+
const html = await render(node, {
|
|
3154
|
+
globalComponents: { Card },
|
|
3155
|
+
cmsService: mockCmsService,
|
|
3156
|
+
});
|
|
3157
|
+
|
|
3158
|
+
expect(calledWith).toEqual([['t1', 't2']]);
|
|
3159
|
+
expect(html).toContain('Tag t1');
|
|
3160
|
+
expect(html).toContain('Tag t2');
|
|
3161
|
+
});
|
|
3162
|
+
|
|
3163
|
+
test('inner list filter dotted-ref resolves against host component prop', async () => {
|
|
3164
|
+
// {{config.0.collection}} is a dotted template — processStructure leaves it literal.
|
|
3165
|
+
// SSR's resolveFilterTemplates must see the host component's `config` prop.
|
|
3166
|
+
const queries: any[] = [];
|
|
3167
|
+
const mockCmsService = {
|
|
3168
|
+
queryItems: async (q: any) => {
|
|
3169
|
+
queries.push(q);
|
|
3170
|
+
return [{ _id: 'p1', title: 'Hello' }];
|
|
3171
|
+
},
|
|
3172
|
+
getSchema: () => undefined,
|
|
3173
|
+
};
|
|
3174
|
+
|
|
3175
|
+
const Card: ComponentDefinition = {
|
|
3176
|
+
component: {
|
|
3177
|
+
interface: {
|
|
3178
|
+
config: { type: 'list' as any, default: [] },
|
|
3179
|
+
},
|
|
3180
|
+
structure: {
|
|
3181
|
+
type: 'list',
|
|
3182
|
+
sourceType: 'collection',
|
|
3183
|
+
source: 'posts',
|
|
3184
|
+
itemAs: 'post',
|
|
3185
|
+
filter: { category: '{{config.0.collection}}' },
|
|
3186
|
+
children: [
|
|
3187
|
+
{ type: 'node', tag: 'div', children: '{{post.title}}' },
|
|
3188
|
+
],
|
|
3189
|
+
},
|
|
3190
|
+
},
|
|
3191
|
+
};
|
|
3192
|
+
|
|
3193
|
+
const node = {
|
|
3194
|
+
type: 'component',
|
|
3195
|
+
component: 'Card',
|
|
3196
|
+
props: {
|
|
3197
|
+
config: [{ collection: 'news' }],
|
|
3198
|
+
},
|
|
3199
|
+
};
|
|
3200
|
+
|
|
3201
|
+
const html = await render(node, {
|
|
3202
|
+
globalComponents: { Card },
|
|
3203
|
+
cmsService: mockCmsService,
|
|
3204
|
+
});
|
|
3205
|
+
|
|
3206
|
+
expect(queries.length).toBe(1);
|
|
3207
|
+
expect(queries[0].filter).toEqual({ category: 'news' });
|
|
3208
|
+
expect(html).toContain('Hello');
|
|
3209
|
+
});
|
|
3210
|
+
|
|
3211
|
+
test('parent list loop variable wins over a colliding host component prop', async () => {
|
|
3212
|
+
// Outer CMS list iterates with itemAs: 'tag', producing a templateContext entry
|
|
3213
|
+
// named `tag` set to the current item. The host Card also receives a prop named
|
|
3214
|
+
// `tag` set to a fake value. The inner list's items: {{tag.id}} must resolve to
|
|
3215
|
+
// the parent loop variable's id (precedence: parent loop variable > component props).
|
|
3216
|
+
const calledWith: string[][] = [];
|
|
3217
|
+
const mockCmsService = {
|
|
3218
|
+
// Outer iteration: itemAs: 'tag' makes templateContext.tag = item, so the
|
|
3219
|
+
// inner template `{{tag.id}}` looks up item.id directly.
|
|
3220
|
+
queryItems: async () => [
|
|
3221
|
+
{ _id: 'p1', id: 'real-1' },
|
|
3222
|
+
{ _id: 'p2', id: 'real-2' },
|
|
3223
|
+
],
|
|
3224
|
+
getItemsByIds: async (_collection: string, ids: string[]) => {
|
|
3225
|
+
calledWith.push(ids);
|
|
3226
|
+
return ids.map(id => ({ _id: id, name: `Item ${id}` }));
|
|
3227
|
+
},
|
|
3228
|
+
getSchema: () => undefined,
|
|
3229
|
+
};
|
|
3230
|
+
|
|
3231
|
+
const Card: ComponentDefinition = {
|
|
3232
|
+
component: {
|
|
3233
|
+
interface: {
|
|
3234
|
+
// type: 'list' so the array prop value passes validation. The prop
|
|
3235
|
+
// `tag` will land in componentResolvedProps; the parent loop var
|
|
3236
|
+
// `tag` (set to the current outer item) will land in templateContext.
|
|
3237
|
+
tag: { type: 'list' as any, default: [] },
|
|
3238
|
+
},
|
|
3239
|
+
structure: {
|
|
3240
|
+
type: 'list',
|
|
3241
|
+
sourceType: 'collection',
|
|
3242
|
+
source: 'tags',
|
|
3243
|
+
itemAs: 'inner',
|
|
3244
|
+
items: '{{tag.id}}',
|
|
3245
|
+
children: [
|
|
3246
|
+
{ type: 'node', tag: 'span', children: '{{inner.name}}' },
|
|
3247
|
+
],
|
|
3248
|
+
},
|
|
3249
|
+
},
|
|
3250
|
+
};
|
|
3251
|
+
|
|
3252
|
+
const node = {
|
|
3253
|
+
type: 'list',
|
|
3254
|
+
sourceType: 'collection',
|
|
3255
|
+
source: 'posts',
|
|
3256
|
+
itemAs: 'tag',
|
|
3257
|
+
children: [
|
|
3258
|
+
{
|
|
3259
|
+
type: 'component',
|
|
3260
|
+
component: 'Card',
|
|
3261
|
+
props: {
|
|
3262
|
+
// Static fake value for the colliding prop; the inner template
|
|
3263
|
+
// must resolve to the parent loop var, not this.
|
|
3264
|
+
tag: [{ id: 'fake-prop-id' }],
|
|
3265
|
+
},
|
|
3266
|
+
},
|
|
3267
|
+
],
|
|
3268
|
+
};
|
|
3269
|
+
|
|
3270
|
+
await render(node, {
|
|
3271
|
+
globalComponents: { Card },
|
|
3272
|
+
cmsService: mockCmsService,
|
|
3273
|
+
});
|
|
3274
|
+
|
|
3275
|
+
expect(calledWith).toEqual([['real-1'], ['real-2']]);
|
|
3276
|
+
});
|
|
3277
|
+
|
|
3278
|
+
test('page-root filter does not silently resolve {{cms.X}} (regression)', async () => {
|
|
3279
|
+
// Without a host component, ctx.componentResolvedProps is undefined and
|
|
3280
|
+
// ctx.templateContext is undefined → buildListResolutionScope returns
|
|
3281
|
+
// undefined → resolveFilterTemplates leaves filter values literal.
|
|
3282
|
+
// This pins down the documented behavior: the cmsContext namespace does
|
|
3283
|
+
// NOT silently merge into filter scope.
|
|
3284
|
+
const queries: any[] = [];
|
|
3285
|
+
const mockCmsService = {
|
|
3286
|
+
queryItems: async (q: any) => {
|
|
3287
|
+
queries.push(q);
|
|
3288
|
+
return [];
|
|
3289
|
+
},
|
|
3290
|
+
getSchema: () => undefined,
|
|
3291
|
+
};
|
|
3292
|
+
|
|
3293
|
+
const node = {
|
|
3294
|
+
type: 'list',
|
|
3295
|
+
sourceType: 'collection',
|
|
3296
|
+
source: 'posts',
|
|
3297
|
+
filter: { category: '{{cms.something}}' },
|
|
3298
|
+
children: [{ type: 'node', tag: 'div', children: '{{post.title}}' }],
|
|
3299
|
+
};
|
|
3300
|
+
|
|
3301
|
+
// cmsContext is provided, but page-root filter must not pull from it.
|
|
3302
|
+
await render(node, {
|
|
3303
|
+
cmsService: mockCmsService,
|
|
3304
|
+
cmsContext: { cms: { something: 'shouldNotBeUsed' } },
|
|
3305
|
+
});
|
|
3306
|
+
|
|
3307
|
+
expect(queries.length).toBe(1);
|
|
3308
|
+
expect(queries[0].filter).toEqual({ category: '{{cms.something}}' });
|
|
3309
|
+
});
|
|
3112
3310
|
});
|
|
3113
3311
|
|
|
3114
3312
|
// -----------------------------------------------------------------------
|
|
@@ -4019,4 +4217,112 @@ describe('ssrRenderer', () => {
|
|
|
4019
4217
|
expect(html).toContain('href="/fr/a-propos"');
|
|
4020
4218
|
});
|
|
4021
4219
|
});
|
|
4220
|
+
|
|
4221
|
+
// Editor attrs are emitted only when buildComponentHTML's injectEditorAttrs flag is on.
|
|
4222
|
+
// These attributes (data-element-path, data-cms-item-index, data-cms-context,
|
|
4223
|
+
// data-component-root, data-parent-component, data-component-context) drive
|
|
4224
|
+
// XRayOverlay and the click-to-select handler in static (SSR) preview mode.
|
|
4225
|
+
describe('buildComponentHTML - editor attrs (preview-only)', () => {
|
|
4226
|
+
test('omits data-element-path by default', async () => {
|
|
4227
|
+
const node = { type: 'node', tag: 'div', children: ['hello'] };
|
|
4228
|
+
const result = await buildComponentHTML(node as any);
|
|
4229
|
+
expect(result.html).not.toContain('data-element-path');
|
|
4230
|
+
});
|
|
4231
|
+
|
|
4232
|
+
test('emits data-element-path on every element when injectEditorAttrs is true', async () => {
|
|
4233
|
+
const node = {
|
|
4234
|
+
type: 'node',
|
|
4235
|
+
tag: 'div',
|
|
4236
|
+
children: [
|
|
4237
|
+
{ type: 'node', tag: 'span', children: ['hi'] },
|
|
4238
|
+
{ type: 'node', tag: 'a', attributes: { href: '/x' }, children: ['link'] },
|
|
4239
|
+
],
|
|
4240
|
+
};
|
|
4241
|
+
const result = await buildComponentHTML(
|
|
4242
|
+
node as any,
|
|
4243
|
+
{}, {}, undefined, undefined, undefined, undefined, undefined, undefined, undefined, true,
|
|
4244
|
+
);
|
|
4245
|
+
expect(result.html).toContain('data-element-path="0"');
|
|
4246
|
+
expect(result.html).toContain('data-element-path="0,0"');
|
|
4247
|
+
expect(result.html).toContain('data-element-path="0,1"');
|
|
4248
|
+
});
|
|
4249
|
+
|
|
4250
|
+
test('emits data-cms-item-index and data-cms-context for elements inside a list', async () => {
|
|
4251
|
+
// List in prop mode under a component resolves items from componentResolvedProps.
|
|
4252
|
+
const Card: ComponentDefinition = {
|
|
4253
|
+
component: {
|
|
4254
|
+
interface: {
|
|
4255
|
+
items: { type: 'list', default: [{ label: 'A' }, { label: 'B' }] } as any,
|
|
4256
|
+
},
|
|
4257
|
+
structure: {
|
|
4258
|
+
type: 'node',
|
|
4259
|
+
tag: 'div',
|
|
4260
|
+
children: [{
|
|
4261
|
+
type: 'list',
|
|
4262
|
+
sourceType: 'prop',
|
|
4263
|
+
source: 'items',
|
|
4264
|
+
children: [{ type: 'node', tag: 'p', children: '{{item.label}}' }],
|
|
4265
|
+
}],
|
|
4266
|
+
},
|
|
4267
|
+
},
|
|
4268
|
+
};
|
|
4269
|
+
const result = await buildComponentHTML(
|
|
4270
|
+
{ type: 'component', component: 'Card' } as any,
|
|
4271
|
+
{ Card },
|
|
4272
|
+
{}, undefined, undefined, undefined, undefined, undefined, undefined, undefined, true,
|
|
4273
|
+
);
|
|
4274
|
+
expect(result.html).toContain('data-cms-item-index="0"');
|
|
4275
|
+
expect(result.html).toContain('data-cms-item-index="1"');
|
|
4276
|
+
expect(result.html).toContain('data-cms-context=');
|
|
4277
|
+
});
|
|
4278
|
+
|
|
4279
|
+
test('marks the component instance root with data-component-root', async () => {
|
|
4280
|
+
const Card: ComponentDefinition = {
|
|
4281
|
+
component: {
|
|
4282
|
+
interface: {},
|
|
4283
|
+
structure: { type: 'node', tag: 'div', children: ['inner'] },
|
|
4284
|
+
},
|
|
4285
|
+
};
|
|
4286
|
+
const result = await buildComponentHTML(
|
|
4287
|
+
{ type: 'component', component: 'Card' } as any,
|
|
4288
|
+
{ Card },
|
|
4289
|
+
{}, undefined, undefined, undefined, undefined, undefined, undefined, undefined, true,
|
|
4290
|
+
);
|
|
4291
|
+
expect(result.html).toContain('data-component-root="true"');
|
|
4292
|
+
expect(result.html).toContain('data-component-context="Card"');
|
|
4293
|
+
});
|
|
4294
|
+
|
|
4295
|
+
test('emits page-absolute data-element-path for elements inside a component', async () => {
|
|
4296
|
+
// Component has a single wrapper <div> with a <span> child. The page hosts
|
|
4297
|
+
// the component as the only child of its root <div>, so:
|
|
4298
|
+
// page root <div> → [0]
|
|
4299
|
+
// Card root <div> → [0,0]
|
|
4300
|
+
// inner <span> → [0,0,0]
|
|
4301
|
+
// Before the fix SSR reset elementPath inside components, so the inner
|
|
4302
|
+
// span landed at [0] (component-relative) and XRay couldn't find it.
|
|
4303
|
+
const Card: ComponentDefinition = {
|
|
4304
|
+
component: {
|
|
4305
|
+
interface: {},
|
|
4306
|
+
structure: {
|
|
4307
|
+
type: 'node',
|
|
4308
|
+
tag: 'div',
|
|
4309
|
+
children: [{ type: 'node', tag: 'span', children: 'inner' }],
|
|
4310
|
+
},
|
|
4311
|
+
},
|
|
4312
|
+
};
|
|
4313
|
+
const page = {
|
|
4314
|
+
type: 'node',
|
|
4315
|
+
tag: 'div',
|
|
4316
|
+
children: [{ type: 'component', component: 'Card' }],
|
|
4317
|
+
};
|
|
4318
|
+
const result = await buildComponentHTML(
|
|
4319
|
+
page as any,
|
|
4320
|
+
{ Card },
|
|
4321
|
+
{}, undefined, undefined, undefined, undefined, undefined, undefined, undefined, true,
|
|
4322
|
+
);
|
|
4323
|
+
expect(result.html).toContain('data-element-path="0"');
|
|
4324
|
+
expect(result.html).toContain('data-element-path="0,0"');
|
|
4325
|
+
expect(result.html).toContain('data-element-path="0,0,0"');
|
|
4326
|
+
});
|
|
4327
|
+
});
|
|
4022
4328
|
});
|