@lightspeed/crane 2.0.4 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -0
- package/UPGRADE.md +96 -0
- package/dist/cli.mjs +44 -25
- package/package.json +6 -3
- package/template/blank/sections/blank-section/BlankSection.vue +9 -0
- package/template/blank/sections/blank-section/assets/blank_section_showcase_1_preview.jpg +0 -0
- package/template/blank/sections/blank-section/client.ts +6 -0
- package/template/blank/sections/blank-section/server.ts +6 -0
- package/template/blank/sections/blank-section/settings/content.ts +2 -0
- package/template/blank/sections/blank-section/settings/design.ts +2 -0
- package/template/blank/sections/blank-section/settings/layout.ts +2 -0
- package/template/blank/sections/blank-section/settings/translations.ts +8 -0
- package/template/blank/sections/blank-section/showcases/1.ts +15 -0
- package/template/blank/sections/blank-section/showcases/translations.ts +10 -0
- package/template/blank/sections/blank-section/type.ts +5 -0
- package/template/collections/assets/collection_cover_image.png +0 -0
- package/template/collections/example-collection/configuration.ts +21 -0
- package/template/crane.config.json +1 -1
- package/template/footers/example-footer/ExampleFooter.vue +4 -4
- package/template/footers/example-footer/component/LegalLinks.vue +1 -1
- package/template/footers/example-footer/component/MadeWith.vue +1 -1
- package/template/footers/example-footer/component/ReportAbuse.vue +1 -1
- package/template/footers/example-footer/settings/design.ts +5 -4
- package/template/footers/example-footer/settings/translations.ts +4 -3
- package/template/footers/example-footer/showcases/1.ts +4 -2
- package/template/footers/example-footer/showcases/translations.ts +4 -2
- package/template/headers/example-header/ExampleHeader.vue +1 -1
- package/template/headers/example-header/component/Account.vue +1 -1
- package/template/headers/example-header/component/CategoriesDropdown.vue +1 -1
- package/template/headers/example-header/component/Logo.vue +7 -7
- package/template/headers/example-header/component/NavigationMenu.vue +1 -1
- package/template/headers/example-header/settings/content.ts +8 -1
- package/template/headers/example-header/settings/design.ts +7 -1
- package/template/headers/example-header/settings/layout.ts +1 -1
- package/template/headers/example-header/settings/translations.ts +4 -2
- package/template/headers/example-header/showcases/1.ts +15 -11
- package/template/headers/example-header/showcases/2.ts +12 -8
- package/template/headers/example-header/showcases/translations.ts +4 -2
- package/template/layouts/catalog/example-catalog/components/Icon.vue +14 -14
- package/template/layouts/catalog/example-catalog/slots/custom-bottom-bar/CustomBottomBar.vue +1 -1
- package/template/package.json +1 -0
- package/template/page-templates/example-template/configuration.ts +8 -10
- package/template/page-templates/example-template/pages/catalog.ts +3 -6
- package/template/page-templates/example-template/pages/category.ts +3 -6
- package/template/page-templates/example-template/pages/home.ts +42 -57
- package/template/page-templates/example-template/pages/product.ts +3 -6
- package/template/preview/sections/preview.html +10 -6
- package/template/preview/shared/api-routes.ts +235 -102
- package/template/preview/shared/logger.ts +9 -0
- package/template/preview/shared/preview.ts +150 -69
- package/template/preview/shared/utils.ts +63 -43
- package/template/preview/ssr-server.ts +1 -1
- package/template/reference/sections/about-us/AboutUs.vue +20 -22
- package/template/reference/sections/about-us/component/Image.vue +18 -18
- package/template/reference/sections/about-us/component/Stats.vue +42 -42
- package/template/reference/sections/about-us/component/Title.vue +1 -1
- package/template/reference/sections/about-us/settings/content.ts +15 -19
- package/template/reference/sections/about-us/settings/design.ts +14 -18
- package/template/reference/sections/about-us/settings/layout.ts +7 -5
- package/template/reference/sections/about-us/settings/translations.ts +4 -2
- package/template/reference/sections/about-us/showcases/1.ts +48 -62
- package/template/reference/sections/about-us/showcases/2.ts +44 -56
- package/template/reference/sections/about-us/showcases/translations.ts +4 -2
- package/template/reference/sections/featured-products/FeaturedProducts.vue +12 -6
- package/template/reference/sections/featured-products/component/ProductItem.vue +18 -1
- package/template/reference/sections/featured-products/component/ProductPlaceholder.vue +42 -0
- package/template/reference/sections/featured-products/component/Title.vue +1 -1
- package/template/reference/sections/featured-products/settings/content.ts +8 -10
- package/template/reference/sections/featured-products/settings/design.ts +7 -7
- package/template/reference/sections/featured-products/settings/translations.ts +4 -2
- package/template/reference/sections/featured-products/showcases/1.ts +8 -12
- package/template/reference/sections/featured-products/showcases/translations.ts +4 -2
- package/template/reference/sections/intro-slider/IntroSlider.vue +6 -6
- package/template/reference/sections/intro-slider/component/Slider.vue +43 -44
- package/template/reference/sections/intro-slider/component/Title.vue +9 -9
- package/template/reference/sections/intro-slider/settings/content.ts +36 -22
- package/template/reference/sections/intro-slider/settings/design.ts +17 -22
- package/template/reference/sections/intro-slider/settings/layout.ts +6 -4
- package/template/reference/sections/intro-slider/settings/translations.ts +4 -2
- package/template/reference/sections/intro-slider/showcases/1.ts +52 -75
- package/template/reference/sections/intro-slider/showcases/2.ts +50 -72
- package/template/reference/sections/intro-slider/showcases/translations.ts +4 -2
- package/template/reference/sections/tag-lines/TagLines.vue +41 -47
- package/template/reference/sections/tag-lines/component/HighlightedText.vue +2 -2
- package/template/reference/sections/tag-lines/component/SectionImage.vue +18 -18
- package/template/reference/sections/tag-lines/component/Title.vue +2 -2
- package/template/reference/sections/tag-lines/settings/content.ts +53 -19
- package/template/reference/sections/tag-lines/settings/design.ts +15 -19
- package/template/reference/sections/tag-lines/settings/layout.ts +6 -4
- package/template/reference/sections/tag-lines/settings/translations.ts +4 -2
- package/template/reference/sections/tag-lines/showcases/1.ts +40 -50
- package/template/reference/sections/tag-lines/showcases/2.ts +40 -50
- package/template/reference/sections/tag-lines/showcases/translations.ts +4 -2
- package/template/reference/sections/trending-categories/TrendingCategories.vue +1 -1
- package/template/reference/sections/trending-categories/component/CategoryItem.vue +18 -1
- package/template/reference/sections/trending-categories/component/Title.vue +2 -2
- package/template/reference/sections/trending-categories/settings/content.ts +8 -10
- package/template/reference/sections/trending-categories/settings/design.ts +7 -7
- package/template/reference/sections/trending-categories/settings/translations.ts +4 -2
- package/template/reference/sections/trending-categories/showcases/1.ts +14 -15
- package/template/reference/sections/trending-categories/showcases/translations.ts +4 -2
- package/template/reference/shared/components/Button.vue +6 -6
- package/template/reference/shared/components/SectionWrapper.vue +5 -5
- package/template/reference/shared/components/Tagline.vue +12 -11
- package/template/reference/templates/reference-template-apparel/configuration.ts +8 -8
- package/template/reference/templates/reference-template-apparel/pages/catalog.ts +3 -6
- package/template/reference/templates/reference-template-apparel/pages/category.ts +3 -6
- package/template/reference/templates/reference-template-apparel/pages/home.ts +14 -18
- package/template/reference/templates/reference-template-apparel/pages/product.ts +3 -6
- package/template/reference/templates/reference-template-bike/configuration.ts +9 -9
- package/template/reference/templates/reference-template-bike/pages/catalog.ts +3 -6
- package/template/reference/templates/reference-template-bike/pages/category.ts +3 -6
- package/template/reference/templates/reference-template-bike/pages/home.ts +14 -18
- package/template/reference/templates/reference-template-bike/pages/product.ts +3 -6
- package/template/sections/example-section/ExampleSection.vue +3 -5
- package/template/sections/example-section/component/button/Button.vue +1 -1
- package/template/sections/example-section/component/image/Image.vue +43 -43
- package/template/sections/example-section/component/image/ImagesGrid.vue +21 -32
- package/template/sections/example-section/component/selectbox/Selectbox.vue +1 -1
- package/template/sections/example-section/component/title/Title.vue +1 -1
- package/template/sections/example-section/component/toggle/Toggle.vue +4 -4
- package/template/sections/example-section/settings/content.ts +25 -34
- package/template/sections/example-section/settings/design.ts +15 -19
- package/template/sections/example-section/settings/layout.ts +15 -14
- package/template/sections/example-section/settings/translations.ts +4 -2
- package/template/sections/example-section/showcases/1.ts +52 -79
- package/template/sections/example-section/showcases/2.ts +46 -62
- package/template/sections/example-section/showcases/3.ts +50 -76
- package/template/sections/example-section/showcases/translations.ts +4 -2
- package/template/shared/components/LanguageSelector.vue +1 -1
- package/template/shared/components/SectionWrapper.vue +5 -5
|
@@ -2,8 +2,18 @@
|
|
|
2
2
|
import { getExternalContentMock } from './mock';
|
|
3
3
|
import { loadModule, renderServerModule } from './utils';
|
|
4
4
|
|
|
5
|
+
export type BlockType = 'sections' | 'headers' | 'footers';
|
|
6
|
+
export const BLOCK_TYPES: BlockType[] = ['sections', 'headers', 'footers'];
|
|
7
|
+
|
|
8
|
+
const BLOCK_TYPE_LABELS: Record<BlockType, string> = {
|
|
9
|
+
sections: 'Section',
|
|
10
|
+
headers: 'Header',
|
|
11
|
+
footers: 'Footer',
|
|
12
|
+
};
|
|
13
|
+
|
|
5
14
|
let distFolderPath: string | null = null;
|
|
6
15
|
let currentApp: any = null;
|
|
16
|
+
let currentBlockKey: string | null = null;
|
|
7
17
|
|
|
8
18
|
export function setDistFolderPath(path: string): void {
|
|
9
19
|
distFolderPath = path;
|
|
@@ -16,8 +26,8 @@ export function setDistFolderPath(path: string): void {
|
|
|
16
26
|
/**
|
|
17
27
|
* Loads all required modules for a showcase.
|
|
18
28
|
*/
|
|
19
|
-
async function loadShowcaseModules(
|
|
20
|
-
const basePath = `${distFolder}
|
|
29
|
+
async function loadShowcaseModules(blockType: BlockType, blockName: string, showcaseId: string, distFolder: string): Promise<[any, any, any, any, any]> {
|
|
30
|
+
const basePath = `${distFolder}/${blockType}/${blockName}`;
|
|
21
31
|
return Promise.all([
|
|
22
32
|
loadModule(`${basePath}/js/settings/content.mjs`),
|
|
23
33
|
loadModule(`${basePath}/js/settings/translations.mjs`),
|
|
@@ -229,10 +239,10 @@ export function mergeDesign(
|
|
|
229
239
|
return result;
|
|
230
240
|
}
|
|
231
241
|
|
|
232
|
-
export function designTransformer(design: Record<string, any>, showCaseDesign: Record<string, any
|
|
242
|
+
export function designTransformer(design: Record<string, any>, showCaseDesign: Record<string, any>, isApiRender: boolean = false): Record<string, any> {
|
|
233
243
|
const parsedDesign: Record<string, any> = {};
|
|
234
244
|
const parsedShowcaseDesign: Record<string, any> = {};
|
|
235
|
-
Object.entries(design).forEach(([key, comp]) => {
|
|
245
|
+
Object.entries(design || {}).forEach(([key, comp]) => {
|
|
236
246
|
// Skip DIVIDER elements - they're UI-only and don't have runtime data
|
|
237
247
|
if (comp?.type === 'DIVIDER') {
|
|
238
248
|
return;
|
|
@@ -245,7 +255,7 @@ export function designTransformer(design: Record<string, any>, showCaseDesign: R
|
|
|
245
255
|
parsedDesign[key] = comp?.defaults || {};
|
|
246
256
|
});
|
|
247
257
|
|
|
248
|
-
Object.entries(showCaseDesign).forEach(([key, comp]) => {
|
|
258
|
+
Object.entries(showCaseDesign || {}).forEach(([key, comp]) => {
|
|
249
259
|
// Skip DIVIDER elements - they're UI-only and don't have runtime data
|
|
250
260
|
if (comp?.type === 'DIVIDER') {
|
|
251
261
|
return;
|
|
@@ -259,33 +269,46 @@ export function designTransformer(design: Record<string, any>, showCaseDesign: R
|
|
|
259
269
|
});
|
|
260
270
|
|
|
261
271
|
let overridenDesign = mergeDesign(parsedDesign, parsedShowcaseDesign);
|
|
262
|
-
|
|
263
|
-
|
|
272
|
+
|
|
273
|
+
try {
|
|
274
|
+
overridenDesign = updateHexColors(overridenDesign);
|
|
275
|
+
} catch (error) {
|
|
276
|
+
console.error('[Preview] Failed to update hex colors:', error);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if ( !isApiRender ) {
|
|
280
|
+
replaceGlobalFont(overridenDesign, 'Roboto');
|
|
281
|
+
}
|
|
264
282
|
return overridenDesign;
|
|
265
283
|
}
|
|
266
284
|
|
|
267
|
-
function processImage(component: any,
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
285
|
+
function processImage(component: any, blockType: BlockType, blockName: string, key: string, isApiRender: boolean = false): Record<string, any> {
|
|
286
|
+
try {
|
|
287
|
+
const assetLocation = isApiRender ?
|
|
288
|
+
`http://${distFolderPath}/${blockType}/${blockName}/assets/` :
|
|
289
|
+
`${distFolderPath}/${blockType}/${blockName}/assets/`;
|
|
290
|
+
|
|
291
|
+
const set = component.defaults?.imageData?.set || component.imageData?.set;
|
|
292
|
+
const newSet = {
|
|
293
|
+
'cropped-webp-100x200': { url: assetLocation + set?.MOBILE_WEBP_LOW_RES.url },
|
|
294
|
+
'cropped-webp-1000x2000': { url: assetLocation + set?.MOBILE_WEBP_HI_RES.url },
|
|
295
|
+
'webp-200x200': { url: assetLocation + set?.WEBP_LOW_RES.url },
|
|
296
|
+
'webp-2000x2000': { url: assetLocation + set?.WEBP_HI_2X_RES.url },
|
|
297
|
+
};
|
|
298
|
+
return {
|
|
299
|
+
[key]: {
|
|
300
|
+
set: newSet,
|
|
301
|
+
...(component.imageData?.borderInfo && { borderInfo: component.imageData.borderInfo }),
|
|
302
|
+
...(component.imageData && { bucket: {} }),
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
} catch (error) {
|
|
306
|
+
console.error(`[Preview] Failed to process image "${key}":`, error);
|
|
307
|
+
return {};
|
|
308
|
+
}
|
|
286
309
|
}
|
|
287
310
|
|
|
288
|
-
function processComponent(key: string, component: any, translations: any,
|
|
311
|
+
export function processComponent(key: string, component: any, translations: any, blockType?: BlockType, blockName?: string,
|
|
289
312
|
isApiRender: boolean = false, extraParams?: Record<string, any>): any {
|
|
290
313
|
if (!component?.type) return {};
|
|
291
314
|
switch (component.type) {
|
|
@@ -301,16 +324,16 @@ function processComponent(key: string, component: any, translations: any, sectio
|
|
|
301
324
|
if (defaultSettings) {
|
|
302
325
|
const result: Record<string, any> = {};
|
|
303
326
|
Object.entries(defaultSettings).forEach(([k, c]) => {
|
|
304
|
-
Object.assign(result, processComponent(k, c, translations,
|
|
327
|
+
Object.assign(result, processComponent(k, c, translations, blockType, blockName, isApiRender));
|
|
305
328
|
});
|
|
306
329
|
return { [key]: result };
|
|
307
330
|
} else {
|
|
308
331
|
const cards = component.cards.map((card: any, index: number) => {
|
|
309
332
|
const cardContent: Record<string, any> = {};
|
|
310
333
|
Object.entries(card.settings).forEach(([k, c]) => {
|
|
311
|
-
Object.assign(cardContent, processComponent(k, c, translations,
|
|
334
|
+
Object.assign(cardContent, processComponent(k, c, translations, blockType, blockName, isApiRender));
|
|
312
335
|
});
|
|
313
|
-
return { settings: cardContent, id: index.toString(), title: extraParams?.deckCardLabel};
|
|
336
|
+
return { settings: cardContent, id: index.toString(), title: extraParams?.deckCardLabel };
|
|
314
337
|
});
|
|
315
338
|
return { [key]: { cards } };
|
|
316
339
|
}
|
|
@@ -328,14 +351,14 @@ function processComponent(key: string, component: any, translations: any, sectio
|
|
|
328
351
|
case 'TOGGLE': {
|
|
329
352
|
return {
|
|
330
353
|
[key]: {
|
|
331
|
-
enabled: component.defaults?.enabled ?? component.enabled
|
|
332
|
-
}
|
|
354
|
+
enabled: component.defaults?.enabled ?? component.enabled,
|
|
355
|
+
},
|
|
333
356
|
};
|
|
334
357
|
}
|
|
335
358
|
case 'SELECTBOX':
|
|
336
359
|
return { [key]: component.defaults?.value ?? component.value };
|
|
337
360
|
case 'IMAGE':
|
|
338
|
-
return processImage(component,
|
|
361
|
+
return processImage(component, blockType!, blockName!, key, isApiRender);
|
|
339
362
|
case 'INFO':
|
|
340
363
|
return {
|
|
341
364
|
[key]: {
|
|
@@ -347,6 +370,38 @@ function processComponent(key: string, component: any, translations: any, sectio
|
|
|
347
370
|
},
|
|
348
371
|
},
|
|
349
372
|
};
|
|
373
|
+
case 'PRODUCT_SELECTOR':
|
|
374
|
+
return {
|
|
375
|
+
[key]: {
|
|
376
|
+
maxProducts: component?.maxProducts ?? component?.defaults?.maxProducts,
|
|
377
|
+
products: {
|
|
378
|
+
selectionType: 'MANUAL',
|
|
379
|
+
},
|
|
380
|
+
},
|
|
381
|
+
};
|
|
382
|
+
case 'CATEGORY_SELECTOR':
|
|
383
|
+
return {
|
|
384
|
+
[key]: {
|
|
385
|
+
maxCategories: component?.maxCategories ?? component?.defaults?.maxCategories,
|
|
386
|
+
categories: {
|
|
387
|
+
selectionType: 'ROOT',
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
};
|
|
391
|
+
case 'NAVIGATION_MENU':
|
|
392
|
+
return {
|
|
393
|
+
[key]: {
|
|
394
|
+
items: component.items ?? component?.defaults?.items ?? [],
|
|
395
|
+
},
|
|
396
|
+
};
|
|
397
|
+
case 'LOGO':
|
|
398
|
+
return {
|
|
399
|
+
[key]: {
|
|
400
|
+
text: translations[component?.text ?? component?.defaults?.text ?? ''],
|
|
401
|
+
type: component.logoType,
|
|
402
|
+
image: component.image,
|
|
403
|
+
},
|
|
404
|
+
};
|
|
350
405
|
default:
|
|
351
406
|
return {};
|
|
352
407
|
}
|
|
@@ -357,20 +412,20 @@ function getContentToRender(
|
|
|
357
412
|
showcase: any,
|
|
358
413
|
contentTranslations: any,
|
|
359
414
|
showcaseTranslations: any,
|
|
360
|
-
|
|
415
|
+
blockType: BlockType,
|
|
416
|
+
blockName: string,
|
|
361
417
|
isApiRender: boolean = false,
|
|
362
418
|
): any {
|
|
363
419
|
let deckCardLabel: string | undefined = undefined;
|
|
364
|
-
const parsedContent = Object.entries(content).reduce((acc, [k, c]) => {
|
|
420
|
+
const parsedContent = Object.entries(content || {}).reduce((acc, [k, c]) => {
|
|
365
421
|
if ((c as any).type === 'DECK') {
|
|
366
422
|
deckCardLabel = contentTranslations[(c as any).cards?.defaultCardContent?.label];
|
|
367
423
|
}
|
|
368
|
-
return { ...acc, ...processComponent(k, c, contentTranslations,
|
|
424
|
+
return { ...acc, ...processComponent(k, c, contentTranslations, blockType, blockName, isApiRender) };
|
|
369
425
|
}, {});
|
|
370
426
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
return { ...acc, ...processComponent(k, c, showcaseTranslations, sectionName, isApiRender, {deckCardLabel}) };
|
|
427
|
+
const parsedShowcase = Object.entries(showcase || {}).reduce((acc, [k, c]) => {
|
|
428
|
+
return { ...acc, ...processComponent(k, c, showcaseTranslations, blockType, blockName, isApiRender, { deckCardLabel }) };
|
|
374
429
|
}, {});
|
|
375
430
|
|
|
376
431
|
return overrideSettingsFromShowcase(parsedContent, parsedShowcase);
|
|
@@ -378,31 +433,34 @@ function getContentToRender(
|
|
|
378
433
|
|
|
379
434
|
export function dropdownOptions(showcaseModules: Record<string, any>): Array<{ value: string; label: string }> {
|
|
380
435
|
return Object.keys(showcaseModules).map((path) => {
|
|
381
|
-
const match = path.match(/\/sections\/([^/]+)\/js\/showcases\/(\d+)\.mjs$/);
|
|
436
|
+
const match = path.match(/\/(sections|headers|footers)\/([^/]+)\/js\/showcases\/(\d+)\.mjs$/);
|
|
382
437
|
if (!match) return null;
|
|
438
|
+
const blockType = match[1] as BlockType;
|
|
439
|
+
const blockName = match[2];
|
|
440
|
+
const showcaseId = match[3];
|
|
383
441
|
return {
|
|
384
|
-
value: `${
|
|
385
|
-
label: `${
|
|
442
|
+
value: `${blockType}:${blockName}:${showcaseId}`,
|
|
443
|
+
label: `${BLOCK_TYPE_LABELS[blockType]}: ${blockName} - Showcase ${showcaseId}`,
|
|
386
444
|
};
|
|
387
445
|
}).filter(Boolean) as Array<{ value: string; label: string }>;
|
|
388
446
|
}
|
|
389
447
|
|
|
390
|
-
export function
|
|
448
|
+
export function loadBlockCss(blockType: BlockType, blockName: string): void {
|
|
391
449
|
const link = document.createElement('link');
|
|
392
450
|
link.rel = 'stylesheet';
|
|
393
|
-
link.href = `${distFolderPath}
|
|
451
|
+
link.href = `${distFolderPath}/${blockType}/${blockName}/js/main/client/assets/client.css`;
|
|
394
452
|
document.head.appendChild(link);
|
|
395
453
|
}
|
|
396
454
|
|
|
397
455
|
/**
|
|
398
456
|
* Renders a showcase in the browser (client-side).
|
|
399
457
|
*/
|
|
400
|
-
export async function renderShowcase(
|
|
458
|
+
export async function renderShowcase(blockType: BlockType, blockName: string, showcaseId: string): Promise<void> {
|
|
401
459
|
if (!distFolderPath) throw new Error('distFolderPath not set');
|
|
402
460
|
|
|
403
|
-
const basePath = `${distFolderPath}
|
|
461
|
+
const basePath = `${distFolderPath}/${blockType}/${blockName}`;
|
|
404
462
|
const [content, contentTranslations, showcaseTranslations, showcase, design]
|
|
405
|
-
= await loadShowcaseModules(
|
|
463
|
+
= await loadShowcaseModules(blockType, blockName, showcaseId, distFolderPath);
|
|
406
464
|
|
|
407
465
|
const client = await loadModule(`${basePath}/js/main/client/client.js`) as any;
|
|
408
466
|
|
|
@@ -418,11 +476,12 @@ export async function renderShowcase(sectionName: string, showcaseId: string): P
|
|
|
418
476
|
(showcase as any).default.content || {},
|
|
419
477
|
(contentTranslations as any).default.en,
|
|
420
478
|
(showcaseTranslations as any).default.en,
|
|
421
|
-
|
|
479
|
+
blockType,
|
|
480
|
+
blockName,
|
|
422
481
|
);
|
|
423
482
|
|
|
424
483
|
// Load CSS
|
|
425
|
-
|
|
484
|
+
loadBlockCss(blockType, blockName);
|
|
426
485
|
|
|
427
486
|
// Prepare state
|
|
428
487
|
const state = {
|
|
@@ -436,48 +495,63 @@ export async function renderShowcase(sectionName: string, showcaseId: string): P
|
|
|
436
495
|
},
|
|
437
496
|
};
|
|
438
497
|
|
|
498
|
+
// Check if we're switching to a different block
|
|
499
|
+
const blockKey = `${blockType}:${blockName}`;
|
|
500
|
+
const isSameBlock = currentBlockKey === blockKey;
|
|
501
|
+
|
|
439
502
|
// Mount or update app
|
|
440
|
-
if (currentApp) {
|
|
503
|
+
if (currentApp && isSameBlock) {
|
|
441
504
|
currentApp.update(state);
|
|
442
505
|
} else {
|
|
506
|
+
if (currentApp) {
|
|
507
|
+
currentApp.unmount();
|
|
508
|
+
const appContainer = document.getElementById('app');
|
|
509
|
+
if (appContainer) {
|
|
510
|
+
appContainer.innerHTML = '';
|
|
511
|
+
}
|
|
512
|
+
}
|
|
443
513
|
const { mount, update, unmount } = (client as any).default.init();
|
|
444
514
|
currentApp = { update, unmount };
|
|
515
|
+
currentBlockKey = blockKey;
|
|
445
516
|
mount('#app', state);
|
|
446
517
|
}
|
|
447
518
|
}
|
|
448
519
|
|
|
449
|
-
async function getLayout(showcase: any,
|
|
520
|
+
async function getLayout(showcase: any, blockType: BlockType, blockName: string, distFolder: string) {
|
|
450
521
|
// First, try to get layoutId from showcase
|
|
451
522
|
if (showcase.default?.layoutId) {
|
|
452
523
|
return showcase.default.layoutId;
|
|
453
524
|
}
|
|
454
525
|
|
|
455
|
-
// If not
|
|
456
|
-
|
|
457
|
-
|
|
526
|
+
// If not in showcase, try reading from section's layout.mjs (optional)
|
|
527
|
+
try {
|
|
528
|
+
const basePath = `${distFolder}/${blockType}/${blockName}`;
|
|
529
|
+
const layoutModule = await loadModule(`${basePath}/js/settings/layout.mjs`) as any;
|
|
458
530
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
531
|
+
// layout.mjs exports an array of layout configurations
|
|
532
|
+
// Return the first element's layoutId
|
|
533
|
+
if (Array.isArray(layoutModule.default) && layoutModule.default.length > 0) {
|
|
534
|
+
return layoutModule.default[0].layoutId;
|
|
535
|
+
}
|
|
536
|
+
} catch {
|
|
537
|
+
// Layout file is optional
|
|
463
538
|
}
|
|
464
|
-
throw new Error('Layout module is not an array or is empty');
|
|
465
539
|
}
|
|
466
540
|
|
|
467
541
|
/**
|
|
468
542
|
* Prepares showcase data (content and design) without rendering.
|
|
469
543
|
* Used by API routes to return tile data.
|
|
470
544
|
*/
|
|
471
|
-
export async function getShowcaseData(
|
|
545
|
+
export async function getShowcaseData(blockType: BlockType, blockName: string, showcaseId: string, distFolder: string) {
|
|
472
546
|
const [content, contentTranslations, showcaseTranslations, showcase, design]
|
|
473
|
-
= await loadShowcaseModules(
|
|
547
|
+
= await loadShowcaseModules(blockType, blockName, showcaseId, distFolder);
|
|
474
548
|
|
|
475
549
|
// Prepare design
|
|
476
550
|
const showcaseBackground = (showcase as any).default?.design?.background;
|
|
477
551
|
const backgroundDesign = createBackgroundDesign(showcaseBackground, true); // API render
|
|
478
|
-
const overriddenDesign = designTransformer((design as any).default, (showcase as any).default.design || {});
|
|
552
|
+
const overriddenDesign = designTransformer((design as any).default, (showcase as any).default.design || {}, true);
|
|
479
553
|
overriddenDesign.background = backgroundDesign;
|
|
480
|
-
overriddenDesign.layout = await getLayout(showcase,
|
|
554
|
+
overriddenDesign.layout = await getLayout(showcase, blockType, blockName, distFolder);
|
|
481
555
|
|
|
482
556
|
// Prepare content
|
|
483
557
|
const overriddenContent = getContentToRender(
|
|
@@ -485,8 +559,9 @@ export async function getShowcaseData(sectionName: string, showcaseId: string, d
|
|
|
485
559
|
(showcase as any).default.content || {},
|
|
486
560
|
(contentTranslations as any).default.en,
|
|
487
561
|
(showcaseTranslations as any).default.en,
|
|
488
|
-
|
|
489
|
-
|
|
562
|
+
blockType,
|
|
563
|
+
blockName,
|
|
564
|
+
true,
|
|
490
565
|
);
|
|
491
566
|
|
|
492
567
|
return {
|
|
@@ -499,20 +574,26 @@ export async function getShowcaseData(sectionName: string, showcaseId: string, d
|
|
|
499
574
|
* Gets the showcase state by rendering server-side.
|
|
500
575
|
* Uses renderServerModule to get HTML directly from the SSR server.
|
|
501
576
|
*/
|
|
502
|
-
export async function getShowcaseState(
|
|
577
|
+
export async function getShowcaseState(
|
|
578
|
+
blockType: BlockType,
|
|
579
|
+
blockName: string,
|
|
580
|
+
showcaseId: string,
|
|
581
|
+
distFolder: string,
|
|
582
|
+
context: Record<string, any>,
|
|
583
|
+
externalContent: Record<string, any>) {
|
|
503
584
|
const { content: overriddenContent, design: overriddenDesign }
|
|
504
|
-
= await getShowcaseData(
|
|
585
|
+
= await getShowcaseData(blockType, blockName, showcaseId, distFolder);
|
|
505
586
|
|
|
506
587
|
const data = {
|
|
507
588
|
content: overriddenContent,
|
|
508
589
|
design: overriddenDesign,
|
|
509
590
|
defaults: {},
|
|
510
591
|
background: {},
|
|
511
|
-
externalContent:
|
|
592
|
+
externalContent: externalContent,
|
|
512
593
|
};
|
|
513
594
|
|
|
514
595
|
// Render server-side using the SSR server
|
|
515
|
-
const basePath = `${distFolder}
|
|
596
|
+
const basePath = `${distFolder}/${blockType}/${blockName}`;
|
|
516
597
|
const serverModulePath = `${basePath}/js/main/server/server.js`;
|
|
517
598
|
const html = await renderServerModule(serverModulePath, context, data);
|
|
518
599
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import * as fs from 'fs';
|
|
3
3
|
import * as path from 'path';
|
|
4
4
|
|
|
5
|
-
import { getShowcaseState } from './preview';
|
|
5
|
+
import { getShowcaseState, BlockType, BLOCK_TYPES } from './preview';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Renders a server.js module by directly calling the SSR server.
|
|
@@ -107,21 +107,19 @@ export function updateTilesSection(
|
|
|
107
107
|
): any {
|
|
108
108
|
const targetPattern = `_${sectionName}_${showcaseId}`;
|
|
109
109
|
|
|
110
|
-
const
|
|
110
|
+
const updatedTiles = tilesResponse.tiles.map((item: any) => {
|
|
111
111
|
const itemId = item.sourceId;
|
|
112
|
-
if (itemId && typeof itemId === 'string') {
|
|
113
|
-
|
|
112
|
+
if (itemId && typeof itemId === 'string' && itemId.includes(targetPattern)) {
|
|
113
|
+
item.content = content;
|
|
114
|
+
// If new design has no layout, use CUSTOM_LAYOUT
|
|
115
|
+
const layout = design.layout || 'CUSTOM_LAYOUT';
|
|
116
|
+
item.design = design;
|
|
117
|
+
item.design.layout = layout;
|
|
114
118
|
}
|
|
115
|
-
return
|
|
119
|
+
return item;
|
|
116
120
|
});
|
|
117
121
|
|
|
118
|
-
|
|
119
|
-
// Replace only content and design, preserve all other properties including tils
|
|
120
|
-
targetItem.content = content;
|
|
121
|
-
targetItem.design = design;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return tilesResponse;
|
|
122
|
+
return { ...tilesResponse, tiles: updatedTiles };
|
|
125
123
|
}
|
|
126
124
|
|
|
127
125
|
/**
|
|
@@ -154,17 +152,18 @@ export async function getSectionIds(sectionName: string, showcaseId: string, aut
|
|
|
154
152
|
}
|
|
155
153
|
|
|
156
154
|
/**
|
|
157
|
-
* Gets all showcase IDs for a given
|
|
158
|
-
* @param
|
|
155
|
+
* Gets all showcase IDs for a given block
|
|
156
|
+
* @param blockType - The block type ('sections', 'headers', 'footers')
|
|
157
|
+
* @param blockName - The block name (e.g., 'example-section')
|
|
159
158
|
* @param distPath - The dist path
|
|
160
159
|
* @returns Array of showcase IDs
|
|
161
160
|
*/
|
|
162
|
-
export function getShowcaseIds(
|
|
161
|
+
export function getShowcaseIds(blockType: BlockType, blockName: string, distPath: string): string[] {
|
|
163
162
|
try {
|
|
164
|
-
const showcasesPath = path.join(distPath,
|
|
163
|
+
const showcasesPath = path.join(distPath, blockType, blockName, 'js', 'showcases');
|
|
165
164
|
|
|
166
165
|
if (!fs.existsSync(showcasesPath)) {
|
|
167
|
-
console.error(`Showcases directory not found for
|
|
166
|
+
console.error(`Showcases directory not found for ${blockType}: ${blockName}`);
|
|
168
167
|
return [];
|
|
169
168
|
}
|
|
170
169
|
|
|
@@ -174,23 +173,38 @@ export function getShowcaseIds(sectionName: string, distPath: string): string[]
|
|
|
174
173
|
|
|
175
174
|
return showcaseIds;
|
|
176
175
|
} catch (error) {
|
|
177
|
-
console.error(`Error getting showcase IDs for
|
|
176
|
+
console.error(`Error getting showcase IDs for ${blockType} ${blockName}:`, error);
|
|
178
177
|
return [];
|
|
179
178
|
}
|
|
180
179
|
}
|
|
181
180
|
|
|
182
181
|
/**
|
|
183
|
-
*
|
|
184
|
-
|
|
182
|
+
* Determine the block type by checking which folder contains the block
|
|
183
|
+
*/
|
|
184
|
+
export function getBlockType(blockName: string, distPath: string): BlockType | null {
|
|
185
|
+
for (const blockType of BLOCK_TYPES) {
|
|
186
|
+
const blockPath = path.join(distPath, blockType, blockName);
|
|
187
|
+
if (fs.existsSync(blockPath)) {
|
|
188
|
+
return blockType;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Updates all matching block showcases in custom content
|
|
196
|
+
* @param blockType - The block type ('sections', 'headers', 'footers')
|
|
197
|
+
* @param blockName - The block name
|
|
185
198
|
* @param showcaseId - The showcase ID
|
|
186
199
|
* @param distPath - The dist path
|
|
187
200
|
* @param customContentSections - The sections array from custom content
|
|
188
201
|
* @param authToken - Bearer token
|
|
189
202
|
* @param tilesUrl - The tiles URL
|
|
190
|
-
* @returns True if at least one
|
|
203
|
+
* @returns True if at least one block was updated successfully, false otherwise
|
|
191
204
|
*/
|
|
192
|
-
async function
|
|
193
|
-
|
|
205
|
+
async function updateBlockShowcase(
|
|
206
|
+
blockType: BlockType,
|
|
207
|
+
blockName: string,
|
|
194
208
|
showcaseId: string,
|
|
195
209
|
distPath: string,
|
|
196
210
|
customContentSections: Array<Record<string, unknown>>,
|
|
@@ -198,61 +212,62 @@ async function updateSectionShowcase(
|
|
|
198
212
|
tilesUrl?: string,
|
|
199
213
|
): Promise<boolean> {
|
|
200
214
|
try {
|
|
201
|
-
// Get all section IDs from tiles
|
|
202
|
-
const sectionIds = await getSectionIds(
|
|
215
|
+
// Get all section IDs from tiles matching this block/showcase
|
|
216
|
+
const sectionIds = await getSectionIds(blockName, showcaseId, authToken, tilesUrl);
|
|
203
217
|
|
|
204
218
|
if (!sectionIds || sectionIds.length === 0) {
|
|
205
219
|
return false;
|
|
206
|
-
}
|
|
207
|
-
// Process each section ID
|
|
220
|
+
}// Process each section ID
|
|
208
221
|
for (const sectionId of sectionIds) {
|
|
209
|
-
// Find the
|
|
210
|
-
const
|
|
222
|
+
// Find the block by block ID in custom content
|
|
223
|
+
const targetBlock = customContentSections.find((section) => {
|
|
211
224
|
const state = section.state as Record<string, unknown>;
|
|
212
225
|
const data = state.data as Record<string, unknown>;
|
|
213
226
|
return data.tileId === sectionId;
|
|
214
227
|
});
|
|
215
228
|
|
|
216
|
-
if (!
|
|
217
|
-
console.error(`
|
|
229
|
+
if (!targetBlock) {
|
|
230
|
+
console.error(`Block not found in custom content with ID: ${sectionId}`);
|
|
218
231
|
continue;
|
|
219
232
|
}
|
|
220
233
|
|
|
221
234
|
// Fetch content, design, and html using getShowcaseState
|
|
222
|
-
const state =
|
|
235
|
+
const state = targetBlock.state as Record<string, unknown>;
|
|
223
236
|
// eslint-disable-next-line no-await-in-loop
|
|
224
237
|
const { content, design, html } = await getShowcaseState(
|
|
225
|
-
|
|
238
|
+
blockType,
|
|
239
|
+
blockName,
|
|
226
240
|
showcaseId,
|
|
227
241
|
distPath,
|
|
228
242
|
state.context as Record<string, unknown>,
|
|
243
|
+
(state.data as Record<string, unknown>).externalContent as Record<string, unknown>,
|
|
229
244
|
);
|
|
230
245
|
|
|
231
246
|
// Update content, design, and html
|
|
232
247
|
const data = state.data as Record<string, unknown>;
|
|
233
248
|
data.content = JSON.stringify(content);
|
|
234
249
|
data.design = JSON.stringify(design);
|
|
235
|
-
|
|
250
|
+
targetBlock.html = html;
|
|
236
251
|
}
|
|
237
252
|
|
|
238
253
|
return true;
|
|
239
254
|
} catch (error) {
|
|
240
|
-
console.error(`Error updating ${
|
|
255
|
+
console.error(`Error updating ${blockType} ${blockName} showcase ${showcaseId}:`, error);
|
|
241
256
|
return false;
|
|
242
257
|
}
|
|
243
258
|
}
|
|
244
259
|
|
|
245
260
|
/**
|
|
246
261
|
* Fetches custom content and updates all specified sections with their showcases
|
|
247
|
-
* @param
|
|
262
|
+
* @param blockNames - Array of block names to update (e.g., ['about-us', 'example-header', 'example-footer'])
|
|
248
263
|
* @param distPath - The dist path for getContentAndDesign
|
|
249
264
|
* @param authToken - Bearer token from request header (e.g., "Bearer xxx")
|
|
250
265
|
* @param customContentUrl - The custom content URL
|
|
251
|
-
* @param tilesUrl - The tiles URL to fetch
|
|
266
|
+
* @param tilesUrl - The tiles URL to fetch block IDs
|
|
252
267
|
* @returns The updated custom content response or null if failed
|
|
253
268
|
*/
|
|
254
269
|
export async function updateCustomContent(
|
|
255
|
-
|
|
270
|
+
blockNames: string[],
|
|
256
271
|
distPath: string,
|
|
257
272
|
authToken?: string,
|
|
258
273
|
customContentUrl?: string,
|
|
@@ -290,13 +305,18 @@ export async function updateCustomContent(
|
|
|
290
305
|
}
|
|
291
306
|
|
|
292
307
|
const customContentSections = customContent.sections as Array<Record<string, unknown>>;
|
|
308
|
+
// Process all blocks and showcases in parallel
|
|
309
|
+
const updatePromises = blockNames.flatMap((sectionName) => {
|
|
310
|
+
const blockType = getBlockType(sectionName, distPath);
|
|
311
|
+
if (!blockType) {
|
|
312
|
+
console.warn(`Block not found: ${sectionName}`);
|
|
313
|
+
return [];
|
|
314
|
+
}
|
|
293
315
|
|
|
294
|
-
|
|
295
|
-
const updatePromises = sectionNames.flatMap((sectionName) => {
|
|
296
|
-
const showcaseIds = getShowcaseIds(sectionName, distPath);
|
|
297
|
-
|
|
316
|
+
const showcaseIds = getShowcaseIds(blockType, sectionName, distPath);
|
|
298
317
|
return showcaseIds.map(showcaseId =>
|
|
299
|
-
|
|
318
|
+
updateBlockShowcase(
|
|
319
|
+
blockType,
|
|
300
320
|
sectionName,
|
|
301
321
|
showcaseId,
|
|
302
322
|
distPath,
|
|
@@ -391,7 +391,7 @@ const server = http.createServer(async (req: http.IncomingMessage, res: http.Ser
|
|
|
391
391
|
const { render } = serverModule.init();
|
|
392
392
|
|
|
393
393
|
// Call render with context and data
|
|
394
|
-
const result = await render(context, data || { defaults: {}, background: {}
|
|
394
|
+
const result = await render(context, data || { defaults: {}, background: {} });
|
|
395
395
|
|
|
396
396
|
res.statusCode = 200;
|
|
397
397
|
res.setHeader('Content-Type', 'application/json');
|
|
@@ -43,31 +43,29 @@ const backgroundStyle = computed<CSSProperties>(() => {
|
|
|
43
43
|
});
|
|
44
44
|
</script>
|
|
45
45
|
|
|
46
|
-
<style
|
|
47
|
-
.about-us-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
gap: 75px;
|
|
46
|
+
<style scoped>
|
|
47
|
+
.about-us-section__wrapper {
|
|
48
|
+
display: grid;
|
|
49
|
+
grid-template-columns: 1fr;
|
|
50
|
+
gap: 75px;
|
|
52
51
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
52
|
+
@media screen and (min-width: 900px) {
|
|
53
|
+
grid-template-columns: 1fr 1fr;
|
|
54
|
+
align-items: center;
|
|
55
|
+
gap: 100px;
|
|
58
56
|
}
|
|
57
|
+
}
|
|
59
58
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
.about-us-section__image {
|
|
60
|
+
max-width: 479px;
|
|
61
|
+
max-height: 610px;
|
|
62
|
+
margin: 0 auto;
|
|
63
|
+
}
|
|
65
64
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
65
|
+
.about-us-section__content {
|
|
66
|
+
flex: 1;
|
|
67
|
+
display: flex;
|
|
68
|
+
flex-direction: column;
|
|
69
|
+
align-self: center;
|
|
72
70
|
}
|
|
73
71
|
</style>
|