@designcrowd/library.brand-page 5.18.8-preview-2 → 5.18.8

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.
Files changed (39) hide show
  1. package/lib/src/{CreatedOnBrandCrowd-de-de-DKqD4Omg.js → CreatedOnBrandCrowd-de-de-BRbznSpr.js} +1 -1
  2. package/lib/src/{CreatedOnBrandCrowd-en-us-DHPYEyIQ.js → CreatedOnBrandCrowd-en-us-DTgCvurL.js} +1 -1
  3. package/lib/src/{CreatedOnBrandCrowd-es-es-BzmCs7T9.js → CreatedOnBrandCrowd-es-es-ZEi7ypLq.js} +1 -1
  4. package/lib/src/{CreatedOnBrandCrowd-fr-fr-DdQ22O_B.js → CreatedOnBrandCrowd-fr-ca-DNDjgJA5.js} +1 -1
  5. package/lib/src/{CreatedOnBrandCrowd-fr-ca-DdQ22O_B.js → CreatedOnBrandCrowd-fr-fr-DNDjgJA5.js} +1 -1
  6. package/lib/src/{CreatedOnBrandCrowd-pt-pt-DAB3YAsR.js → CreatedOnBrandCrowd-pt-br-BrKUBLlc.js} +1 -1
  7. package/lib/src/{CreatedOnBrandCrowd-pt-br-DAB3YAsR.js → CreatedOnBrandCrowd-pt-pt-BrKUBLlc.js} +1 -1
  8. package/lib/src/{CreatedOnDcom-de-de-tvfInKE_.js → CreatedOnDcom-de-de-ZxFuF7Zf.js} +1 -1
  9. package/lib/src/{CreatedOnDcom-en-us-oSq90XZ1.js → CreatedOnDcom-en-us-Bbe6q32b.js} +1 -1
  10. package/lib/src/{CreatedOnDcom-es-es-bdKNOCmq.js → CreatedOnDcom-es-es-B1xxtqwW.js} +1 -1
  11. package/lib/src/{CreatedOnDcom-fr-ca--xcJstAJ.js → CreatedOnDcom-fr-ca-dYIw-XKa.js} +1 -1
  12. package/lib/src/{CreatedOnDcom-fr-fr-BlgTeLnC.js → CreatedOnDcom-fr-fr-CdvwGLYf.js} +1 -1
  13. package/lib/src/{CreatedOnDcom-pt-br-DE-ECAWS.js → CreatedOnDcom-pt-br-oBMlRSZf.js} +1 -1
  14. package/lib/src/{CreatedOnDcom-pt-pt-DeW_eZzh.js → CreatedOnDcom-pt-pt-BA0tAdwI.js} +1 -1
  15. package/lib/src/brand-page-maker/components/admin-background-settings/ShapePositionSlider.mixin.d.ts +1 -1
  16. package/lib/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.js +0 -2
  17. package/lib/src/clients/ai-assistant-client.d.ts +0 -7
  18. package/lib/src/index.d.ts +0 -4
  19. package/lib/src/index.mjs +103 -106
  20. package/lib/src/{lib-DEVTDUPa.js → lib-DZmDarKM.js} +6221 -6252
  21. package/lib/src/lib.js +0 -10
  22. package/lib/src/metadata-builder/builders/update-content-text-builder.js +4 -12
  23. package/lib/src/utils/build-asset-manifest.d.ts +0 -4
  24. package/lib/src/utils/build-asset-manifest.js +0 -8
  25. package/lib/src/utils/minify-brand-page-metadata.js +9 -1
  26. package/package.json +1 -1
  27. package/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.ts +0 -2
  28. package/src/clients/ai-assistant-client.ts +0 -7
  29. package/src/lib.ts +0 -12
  30. package/src/metadata-builder/builders/update-content-text-builder.ts +4 -13
  31. package/src/utils/build-asset-manifest.ts +0 -12
  32. package/src/utils/minify-brand-page-metadata.ts +13 -5
  33. package/lib/src/metadata-builder/builders/reorder-pages-builder.d.ts +0 -13
  34. package/lib/src/metadata-builder/builders/reorder-pages-builder.js +0 -49
  35. package/lib/src/utils/externalize-logo-assets.d.ts +0 -16
  36. package/lib/src/utils/externalize-logo-assets.js +0 -34
  37. package/src/metadata-builder/builders/reorder-pages-builder.ts +0 -80
  38. package/src/utils/externalize-logo-assets.test.ts +0 -109
  39. package/src/utils/externalize-logo-assets.ts +0 -50
package/lib/src/lib.js CHANGED
@@ -31,16 +31,6 @@ export * as brandPageUtils from './brand-page/utils';
31
31
  export { getOptimalLogoSize, calculateOptimizedLogoDimensions, applyLogoImageBase64UrlToBrandPageTemplateModel, recalculateLogoContentDimensions, recalculateHeaderLogoSizes, } from './brand-page/utils/logo.util';
32
32
  export { sanitizeRTEHtml } from './brand-page-maker/utils';
33
33
  export { getPageSlugFromPageTitle } from './brand-page-maker/utils/slug.util';
34
- // AI-pipeline preview (longclaw per-turn snapshots): web-renderer's
35
- // /preview/ai-pipeline render page + manifest server route apply the agent's
36
- // tool calls to a brand-page metadata snapshot server-side and resolve the
37
- // target page by slug. These three symbols are otherwise internal to the
38
- // metadata-builder / route utils.
39
- export { executeToolCallsAsync } from './metadata-builder/ai-metadata-executor';
40
- export { findNavItemByPageSlug } from './brand-page/utils/route.util';
41
- // Tiptap-JSON RTE -> HTML (same converter the metadata builders use). Lets the
42
- // preview render the agent's raw Tiptap fields, which the renderer expects as HTML.
43
- export { tiptapJsonToHtml } from './metadata-builder/helpers/rte.helpers';
44
34
  // stores
45
35
  export { useBrandPageMakerStore, useBrandPageMakerUserStore, useUiStore, useBrandPageContainerStore, useEventManagerStore, useLocalisedRouteStore, useVideoStore, } from './brand-page-maker/stores';
46
36
  export { useShopConfigStore } from './shared/ecommerce-settings/store/shop-settings-store';
@@ -1,8 +1,6 @@
1
1
  import {} from '../../brand-page/models';
2
2
  import { BaseMetadataBuilder, BUILDER_TYPES, } from '../models';
3
3
  import { hasRTEContent, replaceTextInRTE } from '../../brand-page-maker/utils';
4
- import { tiptapJsonToHtml } from '../helpers/rte.helpers';
5
- import { isTiptapDocument } from '../parsers/section-tool-arguments';
6
4
  export const buildUpdateTextRequest = (id, payload) => {
7
5
  return {
8
6
  id,
@@ -11,12 +9,6 @@ export const buildUpdateTextRequest = (id, payload) => {
11
9
  };
12
10
  };
13
11
  const updateTextField = (target, fieldName, newText) => {
14
- // AI tool-call text may arrive as Tiptap JSON ({type:'doc',...}); normalize it to
15
- // the HTML string the model stores so a Tiptap doc is never persisted (mirrors the
16
- // section builders). No-op for plain/HTML strings — isTiptapDocument only matches docs.
17
- const text = isTiptapDocument(newText)
18
- ? tiptapJsonToHtml(newText)
19
- : newText;
20
12
  // Check if this is an array item path like "textItems.0.text"
21
13
  const match = fieldName.match(/^(\w+)\.(\d+)\.(\w+)$/);
22
14
  if (match) {
@@ -27,8 +19,8 @@ const updateTextField = (target, fieldName, newText) => {
27
19
  const updatedValue = existingText &&
28
20
  typeof existingText === 'string' &&
29
21
  hasRTEContent(existingText)
30
- ? replaceTextInRTE(existingText, text)
31
- : text;
22
+ ? replaceTextInRTE(existingText, newText)
23
+ : newText;
32
24
  // Create a new array with the updated item to trigger reactivity
33
25
  const newArray = [...array];
34
26
  newArray[+index] = {
@@ -44,10 +36,10 @@ const updateTextField = (target, fieldName, newText) => {
44
36
  if (existingText &&
45
37
  typeof existingText === 'string' &&
46
38
  hasRTEContent(existingText)) {
47
- target[fieldName] = replaceTextInRTE(existingText, text);
39
+ target[fieldName] = replaceTextInRTE(existingText, newText);
48
40
  }
49
41
  else {
50
- target[fieldName] = text;
42
+ target[fieldName] = newText;
51
43
  }
52
44
  }
53
45
  };
@@ -14,10 +14,6 @@ export interface AssetEntry {
14
14
  * 2. Fall back to logoImageBase64Url if logoImage is unavailable.
15
15
  */
16
16
  export declare function resolveLogoData(logoImage: LogoImageModel | undefined, logoImageBase64Url: string | undefined): string | undefined;
17
- /**
18
- * Look up an asset key from the manifest by matching the data content.
19
- */
20
- export declare function findAssetKey(assets: AssetEntry[] | undefined, data: string | undefined): string | undefined;
21
17
  /**
22
18
  * Build an asset manifest from the brand page model.
23
19
  *
@@ -23,14 +23,6 @@ export function resolveLogoData(logoImage, logoImageBase64Url) {
23
23
  // Fallback to base64 if available
24
24
  return logoImageBase64Url || undefined;
25
25
  }
26
- /**
27
- * Look up an asset key from the manifest by matching the data content.
28
- */
29
- export function findAssetKey(assets, data) {
30
- if (!data || !assets)
31
- return undefined;
32
- return assets.find((a) => a.data === data)?.key;
33
- }
34
26
  /**
35
27
  * Hash a string using SHA-256 and return the first 16 hex characters.
36
28
  * Uses the Web Crypto API (available in browsers and modern runtimes).
@@ -1,7 +1,7 @@
1
1
  import { BRAND_PAGE_TYPES, } from '../brand-page/models';
2
2
  import { hasRTEContent } from '../brand-page-maker/utils';
3
3
  import { useExternalRichTextEditor } from '../brand-page-maker/composables/rich-text-editor/useRichTextEditor';
4
- import { findAssetKey, resolveLogoData, } from './build-asset-manifest';
4
+ import { resolveLogoData } from './build-asset-manifest';
5
5
  const BUTTON_TYPES = [
6
6
  'general-link',
7
7
  'website',
@@ -78,6 +78,14 @@ const includeBackgroundStyle = (value) => {
78
78
  return null;
79
79
  return { backgroundStyle: { backgroundColor: bg } };
80
80
  };
81
+ /**
82
+ * Look up an asset key from the manifest by matching the data content.
83
+ */
84
+ const findAssetKey = (assets, data) => {
85
+ if (!data || !assets)
86
+ return undefined;
87
+ return assets.find((a) => a.data === data)?.key;
88
+ };
81
89
  const minifyContent = (content, editor, assets) => {
82
90
  const base = { id: content.id, type: content.type };
83
91
  const c = content;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@designcrowd/library.brand-page",
3
3
  "description": "A collection of Maker/Designer applications",
4
- "version": "5.18.8-preview-2",
4
+ "version": "5.18.8",
5
5
  "author": "Design.com Eng",
6
6
  "license": "UNLICENSED",
7
7
  "type": "module",
@@ -19,7 +19,6 @@ import {
19
19
  } from '../../../clients/websocket-gateway-client';
20
20
  import { minifyBrandPageMetadata } from '../../../utils/minify-brand-page-metadata';
21
21
  import { buildAssetManifest } from '../../../utils/build-asset-manifest';
22
- import { externalizeLogoAssets } from '../../../utils/externalize-logo-assets';
23
22
  import { usePageNavigationStore } from '../../../shared/page-navigation/store/page-navigation-store';
24
23
  import { navigateToPageByPageId } from '../../routing';
25
24
  import {
@@ -269,7 +268,6 @@ export function useAiWebsiteBuilderChat(): UseAiChatResult {
269
268
 
270
269
  const websiteContext = {
271
270
  metadata: minifyBrandPageMetadata(brandPageData, assets),
272
- metadataFull: externalizeLogoAssets(brandPageData, assets),
273
271
  ...(assets.length > 0 && { assets }),
274
272
  websiteTemplateToken: containerStore.designTemplateToken,
275
273
  ...(containerStore.designDraftToken && {
@@ -2,7 +2,6 @@ import axios, { type AxiosError } from 'axios';
2
2
  import { useBrandPageContainerStore } from '../brand-page-maker/stores/brandpage-container/brand-page-container-store';
3
3
  import type { MinifiedBrandPageMetadata } from '../utils/minify-brand-page-metadata';
4
4
  import type { AssetEntry } from '../utils/build-asset-manifest';
5
- import type { BrandPageModel } from '../brand-page/models';
6
5
 
7
6
  export interface ActiveContext {
8
7
  currentPageId: string;
@@ -15,12 +14,6 @@ export interface LogoContext {
15
14
 
16
15
  export interface WebsiteContext {
17
16
  metadata: MinifiedBrandPageMetadata;
18
- /**
19
- * Full structural metadata (logos externalized to `logoAssetKey`, no base64,
20
- * no field-stripping) — persisted for the preview render only. The LLM uses
21
- * `metadata` (minified); the preview renderer uses this for full fidelity.
22
- */
23
- metadataFull?: BrandPageModel;
24
17
  assets?: AssetEntry[];
25
18
  activeContext?: ActiveContext;
26
19
  businessName?: string;
package/src/lib.ts CHANGED
@@ -87,18 +87,6 @@ export {
87
87
  export { sanitizeRTEHtml } from './brand-page-maker/utils';
88
88
  export { getPageSlugFromPageTitle } from './brand-page-maker/utils/slug.util';
89
89
 
90
- // AI-pipeline preview (longclaw per-turn snapshots): web-renderer's
91
- // /preview/ai-pipeline render page + manifest server route apply the agent's
92
- // tool calls to a brand-page metadata snapshot server-side and resolve the
93
- // target page by slug. These three symbols are otherwise internal to the
94
- // metadata-builder / route utils.
95
- export { executeToolCallsAsync } from './metadata-builder/ai-metadata-executor';
96
- export type { ParsedToolCall } from './metadata-builder/parsers/ai-toolcall-parser';
97
- export { findNavItemByPageSlug } from './brand-page/utils/route.util';
98
- // Tiptap-JSON RTE -> HTML (same converter the metadata builders use). Lets the
99
- // preview render the agent's raw Tiptap fields, which the renderer expects as HTML.
100
- export { tiptapJsonToHtml } from './metadata-builder/helpers/rte.helpers';
101
-
102
90
  // stores
103
91
  export {
104
92
  useBrandPageMakerStore,
@@ -7,8 +7,6 @@ import {
7
7
  type MetadataBuilderRequest,
8
8
  } from '../models';
9
9
  import { hasRTEContent, replaceTextInRTE } from '../../brand-page-maker/utils';
10
- import { tiptapJsonToHtml } from '../helpers/rte.helpers';
11
- import { isTiptapDocument } from '../parsers/section-tool-arguments';
12
10
 
13
11
  export interface UpdateTextPayload {
14
12
  contentId: string;
@@ -29,13 +27,6 @@ export const buildUpdateTextRequest = (
29
27
  };
30
28
 
31
29
  const updateTextField = (target: any, fieldName: string, newText: string) => {
32
- // AI tool-call text may arrive as Tiptap JSON ({type:'doc',...}); normalize it to
33
- // the HTML string the model stores so a Tiptap doc is never persisted (mirrors the
34
- // section builders). No-op for plain/HTML strings — isTiptapDocument only matches docs.
35
- const text = isTiptapDocument(newText as unknown)
36
- ? tiptapJsonToHtml(newText as unknown)
37
- : newText;
38
-
39
30
  // Check if this is an array item path like "textItems.0.text"
40
31
  const match = fieldName.match(/^(\w+)\.(\d+)\.(\w+)$/);
41
32
 
@@ -49,8 +40,8 @@ const updateTextField = (target: any, fieldName: string, newText: string) => {
49
40
  existingText &&
50
41
  typeof existingText === 'string' &&
51
42
  hasRTEContent(existingText)
52
- ? replaceTextInRTE(existingText, text)
53
- : text;
43
+ ? replaceTextInRTE(existingText, newText)
44
+ : newText;
54
45
 
55
46
  // Create a new array with the updated item to trigger reactivity
56
47
  const newArray = [...array];
@@ -70,9 +61,9 @@ const updateTextField = (target: any, fieldName: string, newText: string) => {
70
61
  typeof existingText === 'string' &&
71
62
  hasRTEContent(existingText)
72
63
  ) {
73
- target[fieldName] = replaceTextInRTE(existingText, text);
64
+ target[fieldName] = replaceTextInRTE(existingText, newText);
74
65
  } else {
75
- target[fieldName] = text;
66
+ target[fieldName] = newText;
76
67
  }
77
68
  }
78
69
  };
@@ -41,18 +41,6 @@ export function resolveLogoData(
41
41
  return logoImageBase64Url || undefined;
42
42
  }
43
43
 
44
- /**
45
- * Look up an asset key from the manifest by matching the data content.
46
- */
47
- export function findAssetKey(
48
- assets: AssetEntry[] | undefined,
49
- data: string | undefined,
50
- ): string | undefined {
51
- if (!data || !assets) return undefined;
52
-
53
- return assets.find((a) => a.data === data)?.key;
54
- }
55
-
56
44
  /**
57
45
  * Hash a string using SHA-256 and return the first 16 hex characters.
58
46
  * Uses the Web Crypto API (available in browsers and modern runtimes).
@@ -13,11 +13,7 @@ import type { TextStyleDeclaration } from '../brand-page/models/styles.model';
13
13
  import { hasRTEContent } from '../brand-page-maker/utils';
14
14
  import { useExternalRichTextEditor } from '../brand-page-maker/composables/rich-text-editor/useRichTextEditor';
15
15
  import type { Editor, JSONContent } from '@tiptap/vue-3';
16
- import {
17
- type AssetEntry,
18
- findAssetKey,
19
- resolveLogoData,
20
- } from './build-asset-manifest';
16
+ import { type AssetEntry, resolveLogoData } from './build-asset-manifest';
21
17
 
22
18
  type MinifiedFooterSocialLink = { imageUrl: string; url: string };
23
19
 
@@ -192,6 +188,18 @@ const includeBackgroundStyle = (
192
188
  return { backgroundStyle: { backgroundColor: bg } };
193
189
  };
194
190
 
191
+ /**
192
+ * Look up an asset key from the manifest by matching the data content.
193
+ */
194
+ const findAssetKey = (
195
+ assets: AssetEntry[] | undefined,
196
+ data: string | undefined,
197
+ ): string | undefined => {
198
+ if (!data || !assets) return undefined;
199
+
200
+ return assets.find((a) => a.data === data)?.key;
201
+ };
202
+
195
203
  const minifyContent = (
196
204
  content: Content,
197
205
  editor: Editor,
@@ -1,13 +0,0 @@
1
- import type { BrandPageModel } from '../../brand-page/models';
2
- import { BaseMetadataBuilder, type BuilderResult, type MetadataBuilderRequest } from '../models';
3
- import type { Logger } from '../logger';
4
- export interface ReorderPagesPayload {
5
- pageOrder: string[];
6
- }
7
- export declare class ReorderPagesBuilder extends BaseMetadataBuilder<ReorderPagesPayload> {
8
- constructor(request: MetadataBuilderRequest<ReorderPagesPayload>, logger?: Logger);
9
- validateAsync(): Promise<{
10
- errors: string[];
11
- }>;
12
- buildAsync(model: BrandPageModel): Promise<BuilderResult>;
13
- }
@@ -1,49 +0,0 @@
1
- import { BaseMetadataBuilder, } from '../models';
2
- export class ReorderPagesBuilder extends BaseMetadataBuilder {
3
- constructor(request, logger) {
4
- super(request, logger);
5
- }
6
- async validateAsync() {
7
- const errors = [];
8
- const payload = this.request.payload;
9
- if (!payload?.pageOrder?.length) {
10
- errors.push('page_order is required for reorder_pages');
11
- }
12
- return { errors };
13
- }
14
- async buildAsync(model) {
15
- this.log('Build called');
16
- const validation = await this.validateAsync();
17
- if (validation.errors.length) {
18
- return { request: this.request, model, errors: validation.errors };
19
- }
20
- const { pageOrder } = this.request.payload;
21
- // Build pageId → routeId lookup from nav.routes
22
- const pageIdToRouteId = new Map();
23
- for (const route of model.nav.routes) {
24
- pageIdToRouteId.set(route.pageId, route.id);
25
- }
26
- // Build new nav.orders from the flat page_order (array of pageIds)
27
- const newOrders = [];
28
- const seenRouteIds = new Set();
29
- for (const pageId of pageOrder) {
30
- const routeId = pageIdToRouteId.get(pageId);
31
- if (!routeId)
32
- continue;
33
- newOrders.push({ routeId, subRoutes: [] });
34
- seenRouteIds.add(routeId);
35
- }
36
- // Safety: append any routes not mentioned (don't lose pages)
37
- for (const route of model.nav.routes) {
38
- const routeId = pageIdToRouteId.get(route.pageId);
39
- if (routeId && !seenRouteIds.has(routeId)) {
40
- newOrders.push({ routeId, subRoutes: [] });
41
- }
42
- }
43
- model.nav.orders = newOrders;
44
- this.log({
45
- message: `Reordered pages: ${pageOrder.join(', ')}`,
46
- });
47
- return { request: this.request, model };
48
- }
49
- }
@@ -1,16 +0,0 @@
1
- import type { BrandPageModel } from '../brand-page/models';
2
- import { type AssetEntry } from './build-asset-manifest';
3
- /**
4
- * Full-model counterpart to the logo handling in `minifyBrandPageMetadata`.
5
- *
6
- * Returns a deep clone of the model with each header/footer logo's inline
7
- * base64 (`logoImageBase64Url`) replaced by its `logoAssetKey` — resolved
8
- * against the same asset manifest minify uses. Every other field is left
9
- * intact (no field-stripping, RTE stays as HTML).
10
- *
11
- * This is the payload persisted for the preview render: the renderer gets full
12
- * fidelity while the heavy logo data stays in the separate `assets` manifest and
13
- * is re-injected downstream (ai-pipelines `_inject_logo_urls`), exactly as for
14
- * the minified payload.
15
- */
16
- export declare const externalizeLogoAssets: (model: BrandPageModel, assets: AssetEntry[]) => BrandPageModel;
@@ -1,34 +0,0 @@
1
- import cloneDeep from 'lodash.clonedeep';
2
- import { findAssetKey, resolveLogoData, } from './build-asset-manifest';
3
- /**
4
- * Full-model counterpart to the logo handling in `minifyBrandPageMetadata`.
5
- *
6
- * Returns a deep clone of the model with each header/footer logo's inline
7
- * base64 (`logoImageBase64Url`) replaced by its `logoAssetKey` — resolved
8
- * against the same asset manifest minify uses. Every other field is left
9
- * intact (no field-stripping, RTE stays as HTML).
10
- *
11
- * This is the payload persisted for the preview render: the renderer gets full
12
- * fidelity while the heavy logo data stays in the separate `assets` manifest and
13
- * is re-injected downstream (ai-pipelines `_inject_logo_urls`), exactly as for
14
- * the minified payload.
15
- */
16
- export const externalizeLogoAssets = (model, assets) => {
17
- const clone = cloneDeep(model);
18
- const logoSections = [
19
- clone.pageHeaderDesktop,
20
- clone.pageHeaderMobile,
21
- clone.pageFooterDesktop,
22
- clone.pageFooterMobile,
23
- ];
24
- for (const section of logoSections) {
25
- if (!section)
26
- continue;
27
- const key = findAssetKey(assets, resolveLogoData(section.logoImage, section.logoImageBase64Url));
28
- if (key) {
29
- section.logoAssetKey = key;
30
- delete section.logoImageBase64Url;
31
- }
32
- }
33
- return clone;
34
- };
@@ -1,80 +0,0 @@
1
- import type { BrandPageModel, NavOrder } from '../../brand-page/models';
2
- import {
3
- BaseMetadataBuilder,
4
- type BuilderResult,
5
- type MetadataBuilderRequest,
6
- } from '../models';
7
- import type { Logger } from '../logger';
8
-
9
- export interface ReorderPagesPayload {
10
- pageOrder: string[];
11
- }
12
-
13
- export class ReorderPagesBuilder extends BaseMetadataBuilder<ReorderPagesPayload> {
14
- constructor(
15
- request: MetadataBuilderRequest<ReorderPagesPayload>,
16
- logger?: Logger,
17
- ) {
18
- super(request, logger);
19
- }
20
-
21
- async validateAsync(): Promise<{ errors: string[] }> {
22
- const errors: string[] = [];
23
- const payload = this.request.payload;
24
-
25
- if (!payload?.pageOrder?.length) {
26
- errors.push('page_order is required for reorder_pages');
27
- }
28
-
29
- return { errors };
30
- }
31
-
32
- async buildAsync(model: BrandPageModel): Promise<BuilderResult> {
33
- this.log('Build called');
34
-
35
- const validation = await this.validateAsync();
36
-
37
- if (validation.errors.length) {
38
- return { request: this.request, model, errors: validation.errors };
39
- }
40
-
41
- const { pageOrder } = this.request.payload!;
42
-
43
- // Build pageId → routeId lookup from nav.routes
44
- const pageIdToRouteId = new Map<string, string>();
45
-
46
- for (const route of model.nav.routes) {
47
- pageIdToRouteId.set(route.pageId, route.id);
48
- }
49
-
50
- // Build new nav.orders from the flat page_order (array of pageIds)
51
- const newOrders: NavOrder[] = [];
52
- const seenRouteIds = new Set<string>();
53
-
54
- for (const pageId of pageOrder) {
55
- const routeId = pageIdToRouteId.get(pageId);
56
-
57
- if (!routeId) continue;
58
-
59
- newOrders.push({ routeId, subRoutes: [] });
60
- seenRouteIds.add(routeId);
61
- }
62
-
63
- // Safety: append any routes not mentioned (don't lose pages)
64
- for (const route of model.nav.routes) {
65
- const routeId = pageIdToRouteId.get(route.pageId);
66
-
67
- if (routeId && !seenRouteIds.has(routeId)) {
68
- newOrders.push({ routeId, subRoutes: [] });
69
- }
70
- }
71
-
72
- model.nav.orders = newOrders;
73
-
74
- this.log({
75
- message: `Reordered pages: ${pageOrder.join(', ')}`,
76
- });
77
-
78
- return { request: this.request, model };
79
- }
80
- }
@@ -1,109 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { externalizeLogoAssets } from './externalize-logo-assets';
3
- import type { AssetEntry } from './build-asset-manifest';
4
- import type { BrandPageModel } from '../brand-page/models';
5
-
6
- const LOGO_DATA = 'data:image/svg+xml;base64,AAAA';
7
-
8
- const makeModel = (): any => ({
9
- v: 1,
10
- brandPageType: 'brandSite',
11
- styles: {},
12
- nav: { routes: [], orders: [] },
13
- pageHeaderDesktop: {
14
- isLogoVisible: true,
15
- logoImageBase64Url: LOGO_DATA,
16
- logoImageHeight: '40px',
17
- },
18
- pageHeaderMobile: undefined,
19
- pageFooterDesktop: {
20
- isLogoVisible: true,
21
- logoImageBase64Url: LOGO_DATA,
22
- copyrightText: '© x',
23
- },
24
- pageFooterMobile: undefined,
25
- pages: [
26
- {
27
- id: 'p1',
28
- title: 'Home',
29
- contents: [
30
- {
31
- id: 'c1',
32
- type: 'contact-form',
33
- title: '<p>Contact</p>',
34
- fields: [
35
- {
36
- id: 'firstName',
37
- name: 'First',
38
- enabled: true,
39
- required: true,
40
- type: 'text',
41
- },
42
- ],
43
- },
44
- {
45
- id: 'c2',
46
- type: 'banner-section',
47
- title: '<p>Hi</p>',
48
- backgroundStyle: {
49
- backgroundColor: '#fff',
50
- backgroundImage: 'url(x)',
51
- },
52
- },
53
- ],
54
- },
55
- ],
56
- });
57
-
58
- const assets: AssetEntry[] = [
59
- { key: 'logo-key-1', type: 'logo', data: LOGO_DATA },
60
- ];
61
-
62
- describe('externalizeLogoAssets', () => {
63
- it('replaces header/footer logo base64 with logoAssetKey', () => {
64
- const out: any = externalizeLogoAssets(
65
- makeModel() as BrandPageModel,
66
- assets,
67
- );
68
-
69
- expect(out.pageHeaderDesktop.logoAssetKey).toBe('logo-key-1');
70
- expect(out.pageHeaderDesktop.logoImageBase64Url).toBeUndefined();
71
- expect(out.pageFooterDesktop.logoAssetKey).toBe('logo-key-1');
72
- expect(out.pageFooterDesktop.logoImageBase64Url).toBeUndefined();
73
- });
74
-
75
- it('keeps structural fields intact (no field-stripping, RTE stays HTML)', () => {
76
- const out: any = externalizeLogoAssets(
77
- makeModel() as BrandPageModel,
78
- assets,
79
- );
80
-
81
- const contactForm = out.pages[0].contents[0];
82
-
83
- expect(contactForm.fields).toHaveLength(1); // minify drops this; we keep it
84
- expect(contactForm.title).toBe('<p>Contact</p>'); // RTE stays HTML, not Tiptap JSON
85
-
86
- const banner = out.pages[0].contents[1];
87
-
88
- expect(banner.backgroundStyle).toEqual({
89
- backgroundColor: '#fff',
90
- backgroundImage: 'url(x)', // minify reduces to backgroundColor only; we keep all
91
- });
92
- });
93
-
94
- it('does not mutate the input model', () => {
95
- const model = makeModel();
96
-
97
- externalizeLogoAssets(model as BrandPageModel, assets);
98
-
99
- expect(model.pageHeaderDesktop.logoImageBase64Url).toBe(LOGO_DATA);
100
- expect(model.pageHeaderDesktop.logoAssetKey).toBeUndefined();
101
- });
102
-
103
- it('leaves the logo untouched when no matching asset exists', () => {
104
- const out: any = externalizeLogoAssets(makeModel() as BrandPageModel, []);
105
-
106
- expect(out.pageHeaderDesktop.logoImageBase64Url).toBe(LOGO_DATA);
107
- expect(out.pageHeaderDesktop.logoAssetKey).toBeUndefined();
108
- });
109
- });
@@ -1,50 +0,0 @@
1
- import cloneDeep from 'lodash.clonedeep';
2
- import type { BrandPageModel } from '../brand-page/models';
3
- import {
4
- type AssetEntry,
5
- findAssetKey,
6
- resolveLogoData,
7
- } from './build-asset-manifest';
8
-
9
- /**
10
- * Full-model counterpart to the logo handling in `minifyBrandPageMetadata`.
11
- *
12
- * Returns a deep clone of the model with each header/footer logo's inline
13
- * base64 (`logoImageBase64Url`) replaced by its `logoAssetKey` — resolved
14
- * against the same asset manifest minify uses. Every other field is left
15
- * intact (no field-stripping, RTE stays as HTML).
16
- *
17
- * This is the payload persisted for the preview render: the renderer gets full
18
- * fidelity while the heavy logo data stays in the separate `assets` manifest and
19
- * is re-injected downstream (ai-pipelines `_inject_logo_urls`), exactly as for
20
- * the minified payload.
21
- */
22
- export const externalizeLogoAssets = (
23
- model: BrandPageModel,
24
- assets: AssetEntry[],
25
- ): BrandPageModel => {
26
- const clone = cloneDeep(model);
27
-
28
- const logoSections = [
29
- clone.pageHeaderDesktop,
30
- clone.pageHeaderMobile,
31
- clone.pageFooterDesktop,
32
- clone.pageFooterMobile,
33
- ];
34
-
35
- for (const section of logoSections) {
36
- if (!section) continue;
37
-
38
- const key = findAssetKey(
39
- assets,
40
- resolveLogoData(section.logoImage, section.logoImageBase64Url),
41
- );
42
-
43
- if (key) {
44
- (section as unknown as Record<string, unknown>).logoAssetKey = key;
45
- delete section.logoImageBase64Url;
46
- }
47
- }
48
-
49
- return clone;
50
- };