@lightspeed/crane 2.0.5 → 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 +34 -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 +108 -72
- 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 +40 -40
- 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 +42 -43
- package/template/reference/sections/intro-slider/component/Title.vue +7 -7
- package/template/reference/sections/intro-slider/settings/content.ts +33 -36
- 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 +1 -1
- package/template/reference/sections/tag-lines/component/SectionImage.vue +18 -18
- package/template/reference/sections/tag-lines/component/Title.vue +1 -1
- package/template/reference/sections/tag-lines/settings/content.ts +47 -47
- 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 +1 -1
- 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,9 +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;
|
|
7
|
-
let
|
|
16
|
+
let currentBlockKey: string | null = null;
|
|
8
17
|
|
|
9
18
|
export function setDistFolderPath(path: string): void {
|
|
10
19
|
distFolderPath = path;
|
|
@@ -17,8 +26,8 @@ export function setDistFolderPath(path: string): void {
|
|
|
17
26
|
/**
|
|
18
27
|
* Loads all required modules for a showcase.
|
|
19
28
|
*/
|
|
20
|
-
async function loadShowcaseModules(
|
|
21
|
-
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}`;
|
|
22
31
|
return Promise.all([
|
|
23
32
|
loadModule(`${basePath}/js/settings/content.mjs`),
|
|
24
33
|
loadModule(`${basePath}/js/settings/translations.mjs`),
|
|
@@ -230,7 +239,7 @@ export function mergeDesign(
|
|
|
230
239
|
return result;
|
|
231
240
|
}
|
|
232
241
|
|
|
233
|
-
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> {
|
|
234
243
|
const parsedDesign: Record<string, any> = {};
|
|
235
244
|
const parsedShowcaseDesign: Record<string, any> = {};
|
|
236
245
|
Object.entries(design || {}).forEach(([key, comp]) => {
|
|
@@ -267,15 +276,17 @@ export function designTransformer(design: Record<string, any>, showCaseDesign: R
|
|
|
267
276
|
console.error('[Preview] Failed to update hex colors:', error);
|
|
268
277
|
}
|
|
269
278
|
|
|
270
|
-
|
|
279
|
+
if ( !isApiRender ) {
|
|
280
|
+
replaceGlobalFont(overridenDesign, 'Roboto');
|
|
281
|
+
}
|
|
271
282
|
return overridenDesign;
|
|
272
283
|
}
|
|
273
284
|
|
|
274
|
-
function processImage(component: any,
|
|
285
|
+
function processImage(component: any, blockType: BlockType, blockName: string, key: string, isApiRender: boolean = false): Record<string, any> {
|
|
275
286
|
try {
|
|
276
287
|
const assetLocation = isApiRender ?
|
|
277
|
-
`http://${distFolderPath}
|
|
278
|
-
`${distFolderPath}
|
|
288
|
+
`http://${distFolderPath}/${blockType}/${blockName}/assets/` :
|
|
289
|
+
`${distFolderPath}/${blockType}/${blockName}/assets/`;
|
|
279
290
|
|
|
280
291
|
const set = component.defaults?.imageData?.set || component.imageData?.set;
|
|
281
292
|
const newSet = {
|
|
@@ -297,7 +308,7 @@ function processImage(component: any, sectionName: string, key: string, isApiRen
|
|
|
297
308
|
}
|
|
298
309
|
}
|
|
299
310
|
|
|
300
|
-
export function processComponent(key: string, component: any, translations: any,
|
|
311
|
+
export function processComponent(key: string, component: any, translations: any, blockType?: BlockType, blockName?: string,
|
|
301
312
|
isApiRender: boolean = false, extraParams?: Record<string, any>): any {
|
|
302
313
|
if (!component?.type) return {};
|
|
303
314
|
switch (component.type) {
|
|
@@ -313,16 +324,16 @@ export function processComponent(key: string, component: any, translations: any,
|
|
|
313
324
|
if (defaultSettings) {
|
|
314
325
|
const result: Record<string, any> = {};
|
|
315
326
|
Object.entries(defaultSettings).forEach(([k, c]) => {
|
|
316
|
-
Object.assign(result, processComponent(k, c, translations,
|
|
327
|
+
Object.assign(result, processComponent(k, c, translations, blockType, blockName, isApiRender));
|
|
317
328
|
});
|
|
318
329
|
return { [key]: result };
|
|
319
330
|
} else {
|
|
320
331
|
const cards = component.cards.map((card: any, index: number) => {
|
|
321
332
|
const cardContent: Record<string, any> = {};
|
|
322
333
|
Object.entries(card.settings).forEach(([k, c]) => {
|
|
323
|
-
Object.assign(cardContent, processComponent(k, c, translations,
|
|
334
|
+
Object.assign(cardContent, processComponent(k, c, translations, blockType, blockName, isApiRender));
|
|
324
335
|
});
|
|
325
|
-
return { settings: cardContent, id: index.toString(), title: extraParams?.deckCardLabel};
|
|
336
|
+
return { settings: cardContent, id: index.toString(), title: extraParams?.deckCardLabel };
|
|
326
337
|
});
|
|
327
338
|
return { [key]: { cards } };
|
|
328
339
|
}
|
|
@@ -340,14 +351,14 @@ export function processComponent(key: string, component: any, translations: any,
|
|
|
340
351
|
case 'TOGGLE': {
|
|
341
352
|
return {
|
|
342
353
|
[key]: {
|
|
343
|
-
enabled: component.defaults?.enabled ?? component.enabled
|
|
344
|
-
}
|
|
354
|
+
enabled: component.defaults?.enabled ?? component.enabled,
|
|
355
|
+
},
|
|
345
356
|
};
|
|
346
357
|
}
|
|
347
358
|
case 'SELECTBOX':
|
|
348
359
|
return { [key]: component.defaults?.value ?? component.value };
|
|
349
360
|
case 'IMAGE':
|
|
350
|
-
return processImage(component,
|
|
361
|
+
return processImage(component, blockType!, blockName!, key, isApiRender);
|
|
351
362
|
case 'INFO':
|
|
352
363
|
return {
|
|
353
364
|
[key]: {
|
|
@@ -359,11 +370,38 @@ export function processComponent(key: string, component: any, translations: any,
|
|
|
359
370
|
},
|
|
360
371
|
},
|
|
361
372
|
};
|
|
362
|
-
// Leaving PRODUCT_SELECTOR and CATEGORY_SELECTOR references for future implementation
|
|
363
373
|
case 'PRODUCT_SELECTOR':
|
|
364
|
-
return {
|
|
374
|
+
return {
|
|
375
|
+
[key]: {
|
|
376
|
+
maxProducts: component?.maxProducts ?? component?.defaults?.maxProducts,
|
|
377
|
+
products: {
|
|
378
|
+
selectionType: 'MANUAL',
|
|
379
|
+
},
|
|
380
|
+
},
|
|
381
|
+
};
|
|
365
382
|
case 'CATEGORY_SELECTOR':
|
|
366
|
-
return {
|
|
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
|
+
};
|
|
367
405
|
default:
|
|
368
406
|
return {};
|
|
369
407
|
}
|
|
@@ -374,7 +412,8 @@ function getContentToRender(
|
|
|
374
412
|
showcase: any,
|
|
375
413
|
contentTranslations: any,
|
|
376
414
|
showcaseTranslations: any,
|
|
377
|
-
|
|
415
|
+
blockType: BlockType,
|
|
416
|
+
blockName: string,
|
|
378
417
|
isApiRender: boolean = false,
|
|
379
418
|
): any {
|
|
380
419
|
let deckCardLabel: string | undefined = undefined;
|
|
@@ -382,12 +421,11 @@ function getContentToRender(
|
|
|
382
421
|
if ((c as any).type === 'DECK') {
|
|
383
422
|
deckCardLabel = contentTranslations[(c as any).cards?.defaultCardContent?.label];
|
|
384
423
|
}
|
|
385
|
-
return { ...acc, ...processComponent(k, c, contentTranslations,
|
|
424
|
+
return { ...acc, ...processComponent(k, c, contentTranslations, blockType, blockName, isApiRender) };
|
|
386
425
|
}, {});
|
|
387
426
|
|
|
388
|
-
|
|
389
427
|
const parsedShowcase = Object.entries(showcase || {}).reduce((acc, [k, c]) => {
|
|
390
|
-
return { ...acc, ...processComponent(k, c, showcaseTranslations,
|
|
428
|
+
return { ...acc, ...processComponent(k, c, showcaseTranslations, blockType, blockName, isApiRender, { deckCardLabel }) };
|
|
391
429
|
}, {});
|
|
392
430
|
|
|
393
431
|
return overrideSettingsFromShowcase(parsedContent, parsedShowcase);
|
|
@@ -395,44 +433,34 @@ function getContentToRender(
|
|
|
395
433
|
|
|
396
434
|
export function dropdownOptions(showcaseModules: Record<string, any>): Array<{ value: string; label: string }> {
|
|
397
435
|
return Object.keys(showcaseModules).map((path) => {
|
|
398
|
-
const match = path.match(/\/sections\/([^/]+)\/js\/showcases\/(\d+)\.mjs$/);
|
|
436
|
+
const match = path.match(/\/(sections|headers|footers)\/([^/]+)\/js\/showcases\/(\d+)\.mjs$/);
|
|
399
437
|
if (!match) return null;
|
|
438
|
+
const blockType = match[1] as BlockType;
|
|
439
|
+
const blockName = match[2];
|
|
440
|
+
const showcaseId = match[3];
|
|
400
441
|
return {
|
|
401
|
-
value: `${
|
|
402
|
-
label: `${
|
|
442
|
+
value: `${blockType}:${blockName}:${showcaseId}`,
|
|
443
|
+
label: `${BLOCK_TYPE_LABELS[blockType]}: ${blockName} - Showcase ${showcaseId}`,
|
|
403
444
|
};
|
|
404
445
|
}).filter(Boolean) as Array<{ value: string; label: string }>;
|
|
405
446
|
}
|
|
406
447
|
|
|
407
|
-
export function
|
|
408
|
-
const cssHref = `${distFolderPath}/sections/${sectionName}/js/main/client/assets/client.css`;
|
|
409
|
-
|
|
410
|
-
// Check if CSS for this section is already loaded
|
|
411
|
-
const existingLink = document.querySelector(`link[href="${cssHref}"]`);
|
|
412
|
-
if (existingLink) {
|
|
413
|
-
return; // CSS already loaded
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// Remove CSS from other sections (cleanup)
|
|
417
|
-
const oldLinks = document.querySelectorAll('link[data-section-css]');
|
|
418
|
-
oldLinks.forEach(link => link.remove());
|
|
419
|
-
|
|
448
|
+
export function loadBlockCss(blockType: BlockType, blockName: string): void {
|
|
420
449
|
const link = document.createElement('link');
|
|
421
450
|
link.rel = 'stylesheet';
|
|
422
|
-
link.href = `${distFolderPath}
|
|
423
|
-
link.setAttribute('data-section-css', sectionName);
|
|
451
|
+
link.href = `${distFolderPath}/${blockType}/${blockName}/js/main/client/assets/client.css`;
|
|
424
452
|
document.head.appendChild(link);
|
|
425
453
|
}
|
|
426
454
|
|
|
427
455
|
/**
|
|
428
456
|
* Renders a showcase in the browser (client-side).
|
|
429
457
|
*/
|
|
430
|
-
export async function renderShowcase(
|
|
458
|
+
export async function renderShowcase(blockType: BlockType, blockName: string, showcaseId: string): Promise<void> {
|
|
431
459
|
if (!distFolderPath) throw new Error('distFolderPath not set');
|
|
432
460
|
|
|
433
|
-
const basePath = `${distFolderPath}
|
|
461
|
+
const basePath = `${distFolderPath}/${blockType}/${blockName}`;
|
|
434
462
|
const [content, contentTranslations, showcaseTranslations, showcase, design]
|
|
435
|
-
= await loadShowcaseModules(
|
|
463
|
+
= await loadShowcaseModules(blockType, blockName, showcaseId, distFolderPath);
|
|
436
464
|
|
|
437
465
|
const client = await loadModule(`${basePath}/js/main/client/client.js`) as any;
|
|
438
466
|
|
|
@@ -448,11 +476,12 @@ export async function renderShowcase(sectionName: string, showcaseId: string): P
|
|
|
448
476
|
(showcase as any).default.content || {},
|
|
449
477
|
(contentTranslations as any).default.en,
|
|
450
478
|
(showcaseTranslations as any).default.en,
|
|
451
|
-
|
|
479
|
+
blockType,
|
|
480
|
+
blockName,
|
|
452
481
|
);
|
|
453
482
|
|
|
454
483
|
// Load CSS
|
|
455
|
-
|
|
484
|
+
loadBlockCss(blockType, blockName);
|
|
456
485
|
|
|
457
486
|
// Prepare state
|
|
458
487
|
const state = {
|
|
@@ -466,63 +495,63 @@ export async function renderShowcase(sectionName: string, showcaseId: string): P
|
|
|
466
495
|
},
|
|
467
496
|
};
|
|
468
497
|
|
|
469
|
-
// Check if we're switching to a different
|
|
470
|
-
const
|
|
498
|
+
// Check if we're switching to a different block
|
|
499
|
+
const blockKey = `${blockType}:${blockName}`;
|
|
500
|
+
const isSameBlock = currentBlockKey === blockKey;
|
|
471
501
|
|
|
472
502
|
// Mount or update app
|
|
473
|
-
if (currentApp &&
|
|
474
|
-
// Same section, just update with new showcase data
|
|
503
|
+
if (currentApp && isSameBlock) {
|
|
475
504
|
currentApp.update(state);
|
|
476
505
|
} else {
|
|
477
|
-
// Different section or first load - unmount old app and mount new one
|
|
478
506
|
if (currentApp) {
|
|
479
507
|
currentApp.unmount();
|
|
480
|
-
// Clear the app container
|
|
481
508
|
const appContainer = document.getElementById('app');
|
|
482
509
|
if (appContainer) {
|
|
483
510
|
appContainer.innerHTML = '';
|
|
484
511
|
}
|
|
485
512
|
}
|
|
486
|
-
|
|
487
513
|
const { mount, update, unmount } = (client as any).default.init();
|
|
488
514
|
currentApp = { update, unmount };
|
|
489
|
-
|
|
515
|
+
currentBlockKey = blockKey;
|
|
490
516
|
mount('#app', state);
|
|
491
517
|
}
|
|
492
518
|
}
|
|
493
519
|
|
|
494
|
-
async function getLayout(showcase: any,
|
|
520
|
+
async function getLayout(showcase: any, blockType: BlockType, blockName: string, distFolder: string) {
|
|
495
521
|
// First, try to get layoutId from showcase
|
|
496
522
|
if (showcase.default?.layoutId) {
|
|
497
523
|
return showcase.default.layoutId;
|
|
498
524
|
}
|
|
499
525
|
|
|
500
|
-
// If not
|
|
501
|
-
|
|
502
|
-
|
|
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;
|
|
503
530
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
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
|
|
508
538
|
}
|
|
509
|
-
throw new Error('Layout module is not an array or is empty');
|
|
510
539
|
}
|
|
511
540
|
|
|
512
541
|
/**
|
|
513
542
|
* Prepares showcase data (content and design) without rendering.
|
|
514
543
|
* Used by API routes to return tile data.
|
|
515
544
|
*/
|
|
516
|
-
export async function getShowcaseData(
|
|
545
|
+
export async function getShowcaseData(blockType: BlockType, blockName: string, showcaseId: string, distFolder: string) {
|
|
517
546
|
const [content, contentTranslations, showcaseTranslations, showcase, design]
|
|
518
|
-
= await loadShowcaseModules(
|
|
547
|
+
= await loadShowcaseModules(blockType, blockName, showcaseId, distFolder);
|
|
519
548
|
|
|
520
549
|
// Prepare design
|
|
521
550
|
const showcaseBackground = (showcase as any).default?.design?.background;
|
|
522
551
|
const backgroundDesign = createBackgroundDesign(showcaseBackground, true); // API render
|
|
523
|
-
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);
|
|
524
553
|
overriddenDesign.background = backgroundDesign;
|
|
525
|
-
overriddenDesign.layout = await getLayout(showcase,
|
|
554
|
+
overriddenDesign.layout = await getLayout(showcase, blockType, blockName, distFolder);
|
|
526
555
|
|
|
527
556
|
// Prepare content
|
|
528
557
|
const overriddenContent = getContentToRender(
|
|
@@ -530,8 +559,9 @@ export async function getShowcaseData(sectionName: string, showcaseId: string, d
|
|
|
530
559
|
(showcase as any).default.content || {},
|
|
531
560
|
(contentTranslations as any).default.en,
|
|
532
561
|
(showcaseTranslations as any).default.en,
|
|
533
|
-
|
|
534
|
-
|
|
562
|
+
blockType,
|
|
563
|
+
blockName,
|
|
564
|
+
true,
|
|
535
565
|
);
|
|
536
566
|
|
|
537
567
|
return {
|
|
@@ -544,20 +574,26 @@ export async function getShowcaseData(sectionName: string, showcaseId: string, d
|
|
|
544
574
|
* Gets the showcase state by rendering server-side.
|
|
545
575
|
* Uses renderServerModule to get HTML directly from the SSR server.
|
|
546
576
|
*/
|
|
547
|
-
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>) {
|
|
548
584
|
const { content: overriddenContent, design: overriddenDesign }
|
|
549
|
-
= await getShowcaseData(
|
|
585
|
+
= await getShowcaseData(blockType, blockName, showcaseId, distFolder);
|
|
550
586
|
|
|
551
587
|
const data = {
|
|
552
588
|
content: overriddenContent,
|
|
553
589
|
design: overriddenDesign,
|
|
554
590
|
defaults: {},
|
|
555
591
|
background: {},
|
|
556
|
-
externalContent:
|
|
592
|
+
externalContent: externalContent,
|
|
557
593
|
};
|
|
558
594
|
|
|
559
595
|
// Render server-side using the SSR server
|
|
560
|
-
const basePath = `${distFolder}
|
|
596
|
+
const basePath = `${distFolder}/${blockType}/${blockName}`;
|
|
561
597
|
const serverModulePath = `${basePath}/js/main/server/server.js`;
|
|
562
598
|
const html = await renderServerModule(serverModulePath, context, data);
|
|
563
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>
|