@designcrowd/library.brand-page 6.0.12 → 6.0.13
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/lib/src/{CreatedOnBrandCrowd-de-de-BfQcnhof.js → CreatedOnBrandCrowd-de-de-CBjFFeBP.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-en-us-B86JfNzV.js → CreatedOnBrandCrowd-en-us-NuNXF81K.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-es-es-Dy70BV7X.js → CreatedOnBrandCrowd-es-es-BJG8nfug.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-fr-CQ8UhGRG.js → CreatedOnBrandCrowd-fr-ca-uX-nL53e.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-ca-CQ8UhGRG.js → CreatedOnBrandCrowd-fr-fr-uX-nL53e.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-pt-c257u4q-.js → CreatedOnBrandCrowd-pt-br-DTrgFGQV.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-br-c257u4q-.js → CreatedOnBrandCrowd-pt-pt-DTrgFGQV.js} +1 -1
- package/lib/src/{CreatedOnDcom-de-de-cRZ_Lx3_.js → CreatedOnDcom-de-de-DSfpxioT.js} +1 -1
- package/lib/src/{CreatedOnDcom-en-us-CrfHKR-1.js → CreatedOnDcom-en-us-C7G4gKem.js} +1 -1
- package/lib/src/{CreatedOnDcom-es-es-BFjhMPTR.js → CreatedOnDcom-es-es-DVckCaji.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-ca-CZimjPcn.js → CreatedOnDcom-fr-ca-C5GDrm_C.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-fr-V1z3-WzE.js → CreatedOnDcom-fr-fr-v5eMm-j2.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-br-NrmWKrvi.js → CreatedOnDcom-pt-br-Dq3-fPUc.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-pt-CKLASXM9.js → CreatedOnDcom-pt-pt-DFsncYax.js} +1 -1
- package/lib/src/brand-page/components/image-gallery/ImageGallery.stories.js +3 -0
- package/lib/src/brand-page/components/image-gallery/masonry/MasonryGallery.mixin.d.ts +1 -1
- package/lib/src/brand-page/components/images-section/ImagesSection.stories.js +2 -1
- package/lib/src/brand-page/components/social-links/SocialLinks.stories.js +3 -0
- package/lib/src/brand-page/components/testimonial-section/__fixtures__/data.js +3 -0
- package/lib/src/brand-page/components/text/Text.stories.js +2 -0
- package/lib/src/brand-page/models/index.d.ts +13 -0
- package/lib/src/brand-page/models/index.js +4 -0
- package/lib/src/brand-page/utils/migration/migration-01.js +2 -0
- package/lib/src/brand-page/utils/migration/migration-18.d.ts +14 -0
- package/lib/src/brand-page/utils/migration/migration-18.js +17 -0
- package/lib/src/brand-page/utils/migration.util.js +6 -0
- package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.stories.js +1 -0
- package/lib/src/brand-page-maker/components/brand-page-maker/__fixtures__/brand-page-examples.js +1 -0
- package/lib/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.js +5 -0
- package/lib/src/brand-page-maker/constants/config-settings-constant.js +4 -0
- package/lib/src/brand-page-maker/models/content-settings.js +4 -0
- package/lib/src/brand-page-maker/stores/ai-checkpoint/ai-checkpoint-store.d.ts +18 -0
- package/lib/src/index.mjs +1 -1
- package/lib/src/{lib-CHfUKgt-.js → lib-BG-pyXi9.js} +19091 -18974
- package/lib/src/metadata-builder/ai-metadata-executor.js +24 -1
- package/lib/src/metadata-builder/builders/footer-builder.js +32 -41
- package/lib/src/metadata-builder/builders/item-id-utils.d.ts +54 -0
- package/lib/src/metadata-builder/builders/item-id-utils.js +96 -0
- package/lib/src/metadata-builder/builders/move-page-builder.d.ts +32 -0
- package/lib/src/metadata-builder/builders/move-page-builder.js +137 -0
- package/lib/src/metadata-builder/builders/nav-utils.d.ts +26 -0
- package/lib/src/metadata-builder/builders/nav-utils.js +95 -0
- package/lib/src/metadata-builder/builders/section-builders/image-gallery-builder.js +57 -68
- package/lib/src/metadata-builder/builders/section-builders/images-section-builder.js +58 -93
- package/lib/src/metadata-builder/builders/section-builders/move-section-builder.d.ts +13 -1
- package/lib/src/metadata-builder/builders/section-builders/move-section-builder.js +40 -30
- package/lib/src/metadata-builder/builders/section-builders/shapes-section-builder.js +66 -99
- package/lib/src/metadata-builder/builders/section-builders/social-links-builder.js +57 -17
- package/lib/src/metadata-builder/builders/section-builders/testimonial-section-builder.js +51 -85
- package/lib/src/metadata-builder/builders/section-builders/text-section-builder.js +50 -74
- package/lib/src/metadata-builder/builders/section-field-maps.js +17 -0
- package/lib/src/metadata-builder/builders/set-page-visibility-builder.d.ts +23 -0
- package/lib/src/metadata-builder/builders/set-page-visibility-builder.js +83 -0
- package/lib/src/metadata-builder/metadata-executor.js +4 -0
- package/lib/src/metadata-builder/models.d.ts +2 -0
- package/lib/src/metadata-builder/models.js +2 -0
- package/lib/src/metadata-builder/parsers/ai-toolcall-parser.d.ts +31 -7
- package/lib/src/metadata-builder/parsers/ai-toolcall-parser.js +2 -0
- package/lib/src/metadata-builder/parsers/section-tool-arguments.d.ts +84 -48
- package/lib/src/shared/content-settings/components/links-field/LinksField.mixin.js +2 -0
- package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +2 -0
- package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +2 -0
- package/lib/src/utils/minify-brand-page-metadata.d.ts +2 -0
- package/lib/src/utils/minify-brand-page-metadata.js +8 -1
- package/package.json +1 -1
- package/src/brand-page/components/image-gallery/ImageGallery.stories.ts +3 -0
- package/src/brand-page/components/image-gallery/ImageGallery.test.ts +1 -0
- package/src/brand-page/components/image-gallery/masonry/MasonryGallery.mixin.ts +2 -1
- package/src/brand-page/components/images-section/ImagesSection.stories.ts +2 -1
- package/src/brand-page/components/social-links/SocialLinks.stories.ts +3 -0
- package/src/brand-page/components/social-links/SocialLinks.vue.test.ts +5 -0
- package/src/brand-page/components/testimonial-section/__fixtures__/data.ts +3 -0
- package/src/brand-page/components/text/Text.stories.ts +2 -0
- package/src/brand-page/models/index.ts +17 -0
- package/src/brand-page/utils/__snapshots__/migration.util.test.ts.snap +23 -3
- package/src/brand-page/utils/__snapshots__/migration2.util.test.ts.snap +6 -0
- package/src/brand-page/utils/__snapshots__/model.util.test.ts.snap +12 -0
- package/src/brand-page/utils/migration/migration-01.ts +2 -0
- package/src/brand-page/utils/migration/migration-18.test.ts +131 -0
- package/src/brand-page/utils/migration/migration-18.ts +22 -0
- package/src/brand-page/utils/migration.util.ts +7 -0
- package/src/brand-page/utils/personalisation.util.test.ts +4 -0
- package/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.stories.ts +1 -0
- package/src/brand-page-maker/components/brand-page-maker/__fixtures__/brand-page-examples.ts +1 -0
- package/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.ts +6 -0
- package/src/brand-page-maker/constants/config-settings-constant.ts +4 -0
- package/src/brand-page-maker/models/content-settings.ts +4 -0
- package/src/metadata-builder/ai-metadata-executor.ts +31 -1
- package/src/metadata-builder/builders/footer-builder.test.ts +335 -28
- package/src/metadata-builder/builders/footer-builder.ts +40 -57
- package/src/metadata-builder/builders/item-id-utils.test.ts +184 -0
- package/src/metadata-builder/builders/item-id-utils.ts +117 -0
- package/src/metadata-builder/builders/move-page-builder.ts +227 -0
- package/src/metadata-builder/builders/nav-utils.ts +141 -0
- package/src/metadata-builder/builders/section-builders/image-gallery-builder.ts +62 -88
- package/src/metadata-builder/builders/section-builders/images-section-builder.ts +66 -129
- package/src/metadata-builder/builders/section-builders/move-section-builder.ts +58 -35
- package/src/metadata-builder/builders/section-builders/shapes-section-builder.ts +105 -160
- package/src/metadata-builder/builders/section-builders/social-links-builder.ts +65 -20
- package/src/metadata-builder/builders/section-builders/testimonial-section-builder.ts +83 -146
- package/src/metadata-builder/builders/section-builders/text-section-builder.ts +71 -122
- package/src/metadata-builder/builders/section-field-maps.ts +18 -0
- package/src/metadata-builder/builders/set-page-visibility-builder.ts +122 -0
- package/src/metadata-builder/metadata-executor.ts +4 -0
- package/src/metadata-builder/models.ts +2 -0
- package/src/metadata-builder/parsers/ai-toolcall-parser.ts +32 -7
- package/src/metadata-builder/parsers/section-tool-arguments.ts +84 -48
- package/src/shared/content-settings/components/links-field/LinksField.mixin.ts +2 -0
- package/src/utils/minify-brand-page-metadata.ts +14 -2
|
@@ -3,6 +3,7 @@ import { isTiptapDocument, } from '../../parsers/section-tool-arguments';
|
|
|
3
3
|
import { applyScalarFields, applyStyleFields, resolveGlobalPresetIntoStyle, } from '../section-field-utils';
|
|
4
4
|
import { BaseSectionBuilder } from './base-section-builder';
|
|
5
5
|
import { validateTextSection } from '../section-rules/text-section.rules';
|
|
6
|
+
import { ensureItemIds, generateItemId, removeItemsByIds, repositionItemByAnchor, } from '../item-id-utils';
|
|
6
7
|
/** Resolve a RichTextField (string or TiptapDocument) to a plain HTML string. */
|
|
7
8
|
const resolveText = (value) => {
|
|
8
9
|
if (isTiptapDocument(value))
|
|
@@ -16,55 +17,58 @@ const createTextItem = (item, baseStyle, textStyleOptions) => {
|
|
|
16
17
|
const textStyle = (item.text_style ? { ...baseStyle, ...item.text_style } : { ...baseStyle });
|
|
17
18
|
const { html, wasTiptap } = resolveText(item.text);
|
|
18
19
|
return {
|
|
20
|
+
id: item.item_id || generateItemId(),
|
|
19
21
|
text: html,
|
|
20
22
|
textStyle: clearTextStyleTypeIfTiptap(textStyle, wasTiptap, textStyleOptions),
|
|
21
23
|
};
|
|
22
24
|
};
|
|
25
|
+
/**
|
|
26
|
+
* Full replacement: discard existing items and rebuild from the payload,
|
|
27
|
+
* assigning a stable id to each new item.
|
|
28
|
+
*/
|
|
23
29
|
const applyTextItems = (content, items, textStyleOptions) => {
|
|
24
30
|
content.textItems = items.map((item) => createTextItem(item, content.textStyle || {}, textStyleOptions));
|
|
25
31
|
};
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Id-based merge (BP-5603): mirrors the section-level id contract one level
|
|
34
|
+
* deeper.
|
|
35
|
+
* - item with `item_id` matching an existing item → update in place and
|
|
36
|
+
* reposition if `move_before_item_id` is set; unknown id is skipped.
|
|
37
|
+
* - item without `item_id` → add a new item,
|
|
38
|
+
* positioned by `move_before_item_id`.
|
|
39
|
+
* - `removeIds` → delete by id.
|
|
40
|
+
*
|
|
41
|
+
* Existing items are id-backfilled first so legacy sections without ids
|
|
42
|
+
* become referenceable the moment this builder processes them.
|
|
43
|
+
*/
|
|
44
|
+
const mergeTextItems = (content, items, removeIds, textStyleOptions) => {
|
|
45
|
+
ensureItemIds(content.textItems);
|
|
46
|
+
content.textItems = removeItemsByIds(content.textItems, removeIds);
|
|
47
|
+
for (const item of items) {
|
|
48
|
+
if (item.item_id) {
|
|
49
|
+
const existing = content.textItems?.find((i) => i.id === item.item_id);
|
|
50
|
+
// Unknown id — skip rather than guess at a position.
|
|
51
|
+
if (!existing)
|
|
34
52
|
continue;
|
|
35
|
-
const existing = content.textItems[targetIndex];
|
|
36
53
|
if (item.text !== undefined) {
|
|
37
54
|
const { html, wasTiptap } = resolveText(item.text);
|
|
38
55
|
existing.text = html;
|
|
39
56
|
existing.textStyle = clearTextStyleTypeIfTiptap(existing.textStyle || {}, wasTiptap, textStyleOptions);
|
|
40
57
|
}
|
|
41
58
|
if (item.text_style && typeof item.text_style === 'object') {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
59
|
+
existing.textStyle = {
|
|
60
|
+
...(existing.textStyle || {}),
|
|
61
|
+
...item.text_style,
|
|
62
|
+
};
|
|
46
63
|
}
|
|
64
|
+
repositionItemByAnchor(content.textItems, existing, item.move_before_item_id);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
const newItem = createTextItem(item, content.textStyle || {}, textStyleOptions);
|
|
68
|
+
content.textItems = content.textItems ?? [];
|
|
69
|
+
repositionItemByAnchor(content.textItems, newItem, item.move_before_item_id);
|
|
47
70
|
}
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
const itemsToMerge = items.filter((item) => item.insert_at === undefined);
|
|
51
|
-
const updatedItems = itemsToMerge.map((item, index) => {
|
|
52
|
-
const existing = content.textItems?.[index];
|
|
53
|
-
const baseStyle = existing?.textStyle || content.textStyle || {};
|
|
54
|
-
const mergedStyle = (item.text_style ? { ...baseStyle, ...item.text_style } : baseStyle);
|
|
55
|
-
const resolved = item.text !== undefined ? resolveText(item.text) : null;
|
|
56
|
-
const text = resolved ? resolved.html : (existing?.text ?? '');
|
|
57
|
-
return {
|
|
58
|
-
text,
|
|
59
|
-
textStyle: clearTextStyleTypeIfTiptap(mergedStyle, resolved?.wasTiptap ?? false, textStyleOptions),
|
|
60
|
-
};
|
|
61
|
-
});
|
|
62
|
-
if (!replaceAll &&
|
|
63
|
-
content.textItems &&
|
|
64
|
-
content.textItems.length > itemsToMerge.length) {
|
|
65
|
-
updatedItems.push(...content.textItems.slice(itemsToMerge.length));
|
|
66
71
|
}
|
|
67
|
-
content.textItems = updatedItems;
|
|
68
72
|
};
|
|
69
73
|
/**
|
|
70
74
|
* Handles the single `text` + `text_style` fields (BrandPage/BrandContact
|
|
@@ -83,6 +87,7 @@ const applySingleTextField = (content, args, textStyleOptions) => {
|
|
|
83
87
|
else {
|
|
84
88
|
content.textItems = [
|
|
85
89
|
{
|
|
90
|
+
id: generateItemId(),
|
|
86
91
|
text: html,
|
|
87
92
|
textStyle: clearTextStyleTypeIfTiptap({ ...(content.textStyle || {}) }, wasTiptap, textStyleOptions),
|
|
88
93
|
},
|
|
@@ -121,56 +126,27 @@ export class TextSectionBuilder extends BaseSectionBuilder {
|
|
|
121
126
|
applyScalarFields(content, this.args, this.sectionType);
|
|
122
127
|
applyStyleFields(content, this.args, this.sectionType, this.getGlobalTextStyles(model));
|
|
123
128
|
const args = this.args;
|
|
124
|
-
if (args.items && Array.isArray(args.items)) {
|
|
125
|
-
|
|
129
|
+
if (args._replace_items && args.items && Array.isArray(args.items)) {
|
|
130
|
+
// Full replacement: discard existing items and rebuild from the payload.
|
|
131
|
+
applyTextItems(content, args.items, textStyleOptions);
|
|
132
|
+
}
|
|
133
|
+
else if (this.mode === 'create') {
|
|
134
|
+
if (args.items && Array.isArray(args.items)) {
|
|
126
135
|
applyTextItems(content, args.items, textStyleOptions);
|
|
127
136
|
}
|
|
128
137
|
else {
|
|
129
|
-
|
|
138
|
+
applySingleTextField(content, args, textStyleOptions);
|
|
130
139
|
}
|
|
131
140
|
}
|
|
141
|
+
else if ((args.items && Array.isArray(args.items)) ||
|
|
142
|
+
args._remove_item_ids?.length) {
|
|
143
|
+
// Update: id-based add / edit / move / remove in a single pass.
|
|
144
|
+
mergeTextItems(content, args.items ?? [], args._remove_item_ids, textStyleOptions);
|
|
145
|
+
}
|
|
132
146
|
else {
|
|
147
|
+
// No items array: apply the single text/text_style shorthand fields.
|
|
133
148
|
applySingleTextField(content, args, textStyleOptions);
|
|
134
149
|
}
|
|
135
|
-
// Phase 2: Item operations (remove, insert, reorder)
|
|
136
|
-
if (args._remove_item_indices?.length && content.textItems) {
|
|
137
|
-
const sorted = [...args._remove_item_indices].sort((a, b) => b - a);
|
|
138
|
-
for (const idx of sorted) {
|
|
139
|
-
if (idx >= 0 && idx < content.textItems.length) {
|
|
140
|
-
content.textItems.splice(idx, 1);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
if (args.items && Array.isArray(args.items)) {
|
|
145
|
-
const insertItems = args.items.filter((item) => item.insert_at !== undefined && item.item_index === undefined);
|
|
146
|
-
const sortedInserts = [...insertItems].sort((a, b) => (b.insert_at ?? 0) - (a.insert_at ?? 0));
|
|
147
|
-
for (const item of sortedInserts) {
|
|
148
|
-
const pos = Math.max(0, Math.min(item.insert_at ?? 0, content.textItems?.length ?? 0));
|
|
149
|
-
const baseStyle = content.textStyle || {};
|
|
150
|
-
const textStyle = (item.text_style
|
|
151
|
-
? { ...baseStyle, ...item.text_style }
|
|
152
|
-
: { ...baseStyle });
|
|
153
|
-
const { html, wasTiptap } = resolveText(item.text);
|
|
154
|
-
const newItem = {
|
|
155
|
-
text: html,
|
|
156
|
-
textStyle: clearTextStyleTypeIfTiptap(textStyle, wasTiptap, textStyleOptions),
|
|
157
|
-
};
|
|
158
|
-
content.textItems = content.textItems ?? [];
|
|
159
|
-
content.textItems.splice(pos, 0, newItem);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
if (args._reorder_items?.length && content.textItems) {
|
|
163
|
-
const newOrder = args._reorder_items;
|
|
164
|
-
const unique = new Set(newOrder);
|
|
165
|
-
if (unique.size === newOrder.length) {
|
|
166
|
-
const reordered = newOrder
|
|
167
|
-
.filter((idx) => idx >= 0 && idx < content.textItems.length)
|
|
168
|
-
.map((idx) => content.textItems[idx]);
|
|
169
|
-
if (reordered.length === content.textItems.length) {
|
|
170
|
-
content.textItems = reordered;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
150
|
if (args.background_color !== undefined) {
|
|
175
151
|
content.backgroundColor = {
|
|
176
152
|
...content.backgroundColor,
|
|
@@ -12,6 +12,12 @@
|
|
|
12
12
|
function defineFieldMap(map) {
|
|
13
13
|
return map;
|
|
14
14
|
}
|
|
15
|
+
// BP-5687: vertical padding fields shared by all sections that render
|
|
16
|
+
// contentPaddingStyle (see src/brand-page/utils/content-padding.util.ts).
|
|
17
|
+
const PADDING_FIELDS = {
|
|
18
|
+
padding_top: 'topPadding',
|
|
19
|
+
padding_bottom: 'bottomPadding',
|
|
20
|
+
};
|
|
15
21
|
const bannerFields = defineFieldMap({
|
|
16
22
|
title: 'title',
|
|
17
23
|
description: 'description',
|
|
@@ -20,12 +26,14 @@ const bannerFields = defineFieldMap({
|
|
|
20
26
|
background_image_url: 'imageUrl',
|
|
21
27
|
background_shape_url: 'imageUrl',
|
|
22
28
|
display_style: 'displayStyle',
|
|
29
|
+
...PADDING_FIELDS,
|
|
23
30
|
});
|
|
24
31
|
const bookingFields = defineFieldMap({
|
|
25
32
|
title: 'title',
|
|
26
33
|
description: 'description',
|
|
27
34
|
button_text: 'buttonText',
|
|
28
35
|
button_url: 'url',
|
|
36
|
+
...PADDING_FIELDS,
|
|
29
37
|
});
|
|
30
38
|
const aboutFields = defineFieldMap({
|
|
31
39
|
title: 'title',
|
|
@@ -33,11 +41,13 @@ const aboutFields = defineFieldMap({
|
|
|
33
41
|
about_image_url: 'imageUrl',
|
|
34
42
|
about_shape_url: 'imageUrl',
|
|
35
43
|
display_style: 'displayStyle',
|
|
44
|
+
...PADDING_FIELDS,
|
|
36
45
|
});
|
|
37
46
|
const testimonialFields = defineFieldMap({
|
|
38
47
|
title: 'title',
|
|
39
48
|
display_style: 'displayStyle',
|
|
40
49
|
// items handled separately
|
|
50
|
+
...PADDING_FIELDS,
|
|
41
51
|
});
|
|
42
52
|
const imagesSectionFields = defineFieldMap({
|
|
43
53
|
title: 'title',
|
|
@@ -46,6 +56,7 @@ const imagesSectionFields = defineFieldMap({
|
|
|
46
56
|
aspect_ratio: 'aspectRatio',
|
|
47
57
|
are_images_full_width: 'areImagesFullWidth',
|
|
48
58
|
// items handled separately
|
|
59
|
+
...PADDING_FIELDS,
|
|
49
60
|
});
|
|
50
61
|
const shapesSectionFields = defineFieldMap({
|
|
51
62
|
title: 'title',
|
|
@@ -53,10 +64,12 @@ const shapesSectionFields = defineFieldMap({
|
|
|
53
64
|
display_style: 'displayStyle',
|
|
54
65
|
size: 'size',
|
|
55
66
|
// items handled separately
|
|
67
|
+
...PADDING_FIELDS,
|
|
56
68
|
});
|
|
57
69
|
const textFields = defineFieldMap({
|
|
58
70
|
text: 'text',
|
|
59
71
|
column_style: 'columnStyle',
|
|
72
|
+
...PADDING_FIELDS,
|
|
60
73
|
});
|
|
61
74
|
const profileFields = defineFieldMap({
|
|
62
75
|
name: 'name',
|
|
@@ -73,15 +86,18 @@ const contactFormFields = defineFieldMap({
|
|
|
73
86
|
header_image_url: 'imageUrl',
|
|
74
87
|
display_style: 'displayStyle',
|
|
75
88
|
layout_style: 'layoutStyle',
|
|
89
|
+
...PADDING_FIELDS,
|
|
76
90
|
});
|
|
77
91
|
const mailingListFields = defineFieldMap({
|
|
78
92
|
button_text: 'buttonText',
|
|
79
93
|
accepted_fields: 'acceptedFields',
|
|
94
|
+
...PADDING_FIELDS,
|
|
80
95
|
});
|
|
81
96
|
const socialLinksFields = defineFieldMap({
|
|
82
97
|
title: 'title',
|
|
83
98
|
icon_style: 'iconDisplayStyle',
|
|
84
99
|
// links handled separately
|
|
100
|
+
...PADDING_FIELDS,
|
|
85
101
|
});
|
|
86
102
|
const dividerFields = defineFieldMap({
|
|
87
103
|
divider_style: 'dividerStyle',
|
|
@@ -94,6 +110,7 @@ const imageFields = defineFieldMap({
|
|
|
94
110
|
const imageGalleryFields = defineFieldMap({
|
|
95
111
|
is_full_width: 'isFullWidth',
|
|
96
112
|
// images handled separately
|
|
113
|
+
...PADDING_FIELDS,
|
|
97
114
|
});
|
|
98
115
|
const buttonWithIconFields = defineFieldMap({
|
|
99
116
|
title: 'title',
|
|
@@ -0,0 +1,23 @@
|
|
|
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 SetPageVisibilityPayload {
|
|
5
|
+
/** The page whose visibility is toggled (resolved from page_id). */
|
|
6
|
+
pageId: string;
|
|
7
|
+
/** true = hide the page from the navigation; false = show it. */
|
|
8
|
+
isHidden: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Toggle a page's navigation visibility (the AI-chat equivalent of the page-settings
|
|
12
|
+
* "Hide page / Show page" menu). Sets `isHidden` on BOTH the matching `NavRoute`
|
|
13
|
+
* (in `model.nav.routes`) and the `PageModel` (in `model.pages`) — the two places the
|
|
14
|
+
* editor keeps in sync. No slug/order recompute (the nav order is unchanged); the
|
|
15
|
+
* published-site nav already filters hidden pages via `isHidden`.
|
|
16
|
+
*/
|
|
17
|
+
export declare class SetPageVisibilityBuilder extends BaseMetadataBuilder<SetPageVisibilityPayload> {
|
|
18
|
+
constructor(request: MetadataBuilderRequest<SetPageVisibilityPayload>, logger?: Logger);
|
|
19
|
+
validateAsync(): Promise<{
|
|
20
|
+
errors: string[];
|
|
21
|
+
}>;
|
|
22
|
+
buildAsync(model: BrandPageModel): Promise<BuilderResult>;
|
|
23
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { BaseMetadataBuilder, } from '../models';
|
|
2
|
+
import { syncPagesWithNavOrders } from './nav-utils';
|
|
3
|
+
/**
|
|
4
|
+
* Toggle a page's navigation visibility (the AI-chat equivalent of the page-settings
|
|
5
|
+
* "Hide page / Show page" menu). Sets `isHidden` on BOTH the matching `NavRoute`
|
|
6
|
+
* (in `model.nav.routes`) and the `PageModel` (in `model.pages`) — the two places the
|
|
7
|
+
* editor keeps in sync. No slug/order recompute (the nav order is unchanged); the
|
|
8
|
+
* published-site nav already filters hidden pages via `isHidden`.
|
|
9
|
+
*/
|
|
10
|
+
export class SetPageVisibilityBuilder extends BaseMetadataBuilder {
|
|
11
|
+
constructor(request, logger) {
|
|
12
|
+
super(request, logger);
|
|
13
|
+
}
|
|
14
|
+
async validateAsync() {
|
|
15
|
+
const errors = [];
|
|
16
|
+
if (!this.request.payload?.pageId) {
|
|
17
|
+
errors.push('pageId is required for set_page_visibility');
|
|
18
|
+
}
|
|
19
|
+
return { errors };
|
|
20
|
+
}
|
|
21
|
+
async buildAsync(model) {
|
|
22
|
+
this.log('Build called');
|
|
23
|
+
const validation = await this.validateAsync();
|
|
24
|
+
if (validation.errors.length) {
|
|
25
|
+
return { request: this.request, model, errors: validation.errors };
|
|
26
|
+
}
|
|
27
|
+
const { pageId, isHidden } = this.request.payload;
|
|
28
|
+
const targetRoute = model.nav.routes.find((r) => r.pageId === pageId);
|
|
29
|
+
if (!targetRoute) {
|
|
30
|
+
return {
|
|
31
|
+
request: this.request,
|
|
32
|
+
model,
|
|
33
|
+
errors: [`Page with ID "${pageId}" not found`],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
// Guards apply only when HIDING; showing a page is always safe.
|
|
37
|
+
if (isHidden) {
|
|
38
|
+
// --- GUARD: the home page (orders[0]) cannot be hidden ---
|
|
39
|
+
const homeRouteId = model.nav.orders[0]?.routeId;
|
|
40
|
+
if (homeRouteId === targetRoute.id) {
|
|
41
|
+
return {
|
|
42
|
+
request: this.request,
|
|
43
|
+
model,
|
|
44
|
+
errors: [
|
|
45
|
+
`Home page "${pageId}" cannot be hidden from the navigation`,
|
|
46
|
+
],
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
// --- GUARD: cannot hide the last remaining visible page ---
|
|
50
|
+
const visibleCount = model.nav.routes.filter((r) => !r.isHidden).length;
|
|
51
|
+
if (!targetRoute.isHidden && visibleCount <= 1) {
|
|
52
|
+
return {
|
|
53
|
+
request: this.request,
|
|
54
|
+
model,
|
|
55
|
+
errors: [
|
|
56
|
+
`Cannot hide "${pageId}" — at least one page must stay visible`,
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// Set on the NavRoute (drives the nav) and the PageModel (drives header page lists).
|
|
62
|
+
targetRoute.isHidden = isHidden;
|
|
63
|
+
const targetPage = model.pages.find((p) => p.id === pageId);
|
|
64
|
+
if (targetPage) {
|
|
65
|
+
targetPage.isHidden = isHidden;
|
|
66
|
+
}
|
|
67
|
+
// Match the editor's page-navigation store (reorderVisibleAndHiddenRoutes): after any
|
|
68
|
+
// visibility change, float visible pages to the top and sink hidden pages to the bottom
|
|
69
|
+
// of the top-level nav order, stable within each group. Top-level only — mirrors the
|
|
70
|
+
// editor, which does not re-partition nested subRoutes. `model.pages` is then re-synced
|
|
71
|
+
// to the new DFS order (same as MovePageBuilder).
|
|
72
|
+
const routeById = new Map(model.nav.routes.map((r) => [r.id, r]));
|
|
73
|
+
const isOrderHidden = (o) => Boolean(routeById.get(o.routeId)?.isHidden);
|
|
74
|
+
const visibleOrders = model.nav.orders.filter((o) => !isOrderHidden(o));
|
|
75
|
+
const hiddenOrders = model.nav.orders.filter((o) => isOrderHidden(o));
|
|
76
|
+
model.nav.orders = [...visibleOrders, ...hiddenOrders];
|
|
77
|
+
syncPagesWithNavOrders(model);
|
|
78
|
+
this.log({
|
|
79
|
+
message: `Set page "${pageId}" visibility — isHidden: ${isHidden}`,
|
|
80
|
+
});
|
|
81
|
+
return { request: this.request, model };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -7,6 +7,8 @@ import { BannerSectionBuilder, BookingSectionBuilder, AboutSectionBuilder, Conta
|
|
|
7
7
|
import { AddPageBuilder } from './builders/add-page-builder';
|
|
8
8
|
import { RemovePageBuilder } from './builders/remove-page-builder';
|
|
9
9
|
import { RenamePageBuilder } from './builders/rename-page-builder';
|
|
10
|
+
import { MovePageBuilder } from './builders/move-page-builder';
|
|
11
|
+
import { SetPageVisibilityBuilder } from './builders/set-page-visibility-builder';
|
|
10
12
|
import { FooterBuilder } from './builders/footer-builder';
|
|
11
13
|
import { GlobalStylesBuilder } from './builders/global-styles-builder';
|
|
12
14
|
import { HeaderBuilder } from './builders/header-builder';
|
|
@@ -74,6 +76,8 @@ const getBuilderRegistry = () => ({
|
|
|
74
76
|
[BUILDER_TYPES.ADD_PAGE]: AddPageBuilder,
|
|
75
77
|
[BUILDER_TYPES.REMOVE_PAGE]: RemovePageBuilder,
|
|
76
78
|
[BUILDER_TYPES.RENAME_PAGE]: RenamePageBuilder,
|
|
79
|
+
[BUILDER_TYPES.MOVE_PAGE]: MovePageBuilder,
|
|
80
|
+
[BUILDER_TYPES.SET_PAGE_VISIBILITY]: SetPageVisibilityBuilder,
|
|
77
81
|
// Footer operations
|
|
78
82
|
[BUILDER_TYPES.UPDATE_FOOTER]: FooterBuilder,
|
|
79
83
|
// Global styles operations
|
|
@@ -59,6 +59,8 @@ export declare const BUILDER_TYPES: {
|
|
|
59
59
|
readonly ADD_PAGE: "add-page";
|
|
60
60
|
readonly REMOVE_PAGE: "remove-page";
|
|
61
61
|
readonly RENAME_PAGE: "rename-page";
|
|
62
|
+
readonly MOVE_PAGE: "move-page";
|
|
63
|
+
readonly SET_PAGE_VISIBILITY: "set-page-visibility";
|
|
62
64
|
readonly UPDATE_FOOTER: "update-footer";
|
|
63
65
|
readonly UPDATE_GLOBAL_STYLES: "update-global-styles";
|
|
64
66
|
readonly UPDATE_HEADER: "update-header";
|
|
@@ -61,6 +61,8 @@ export const BUILDER_TYPES = {
|
|
|
61
61
|
ADD_PAGE: 'add-page',
|
|
62
62
|
REMOVE_PAGE: 'remove-page',
|
|
63
63
|
RENAME_PAGE: 'rename-page',
|
|
64
|
+
MOVE_PAGE: 'move-page',
|
|
65
|
+
SET_PAGE_VISIBILITY: 'set-page-visibility',
|
|
64
66
|
// Footer operations
|
|
65
67
|
UPDATE_FOOTER: 'update-footer',
|
|
66
68
|
// Global styles operations
|
|
@@ -35,6 +35,8 @@ export declare const AI_ASSISTANT_TOOL_NAMES: {
|
|
|
35
35
|
readonly ADD_PAGE: "add_page";
|
|
36
36
|
readonly REMOVE_PAGE: "remove_page";
|
|
37
37
|
readonly RENAME_PAGE: "rename_page";
|
|
38
|
+
readonly MOVE_PAGE: "move_page";
|
|
39
|
+
readonly SET_PAGE_VISIBILITY: "set_page_visibility";
|
|
38
40
|
readonly UPDATE_FOOTER: "update_footer";
|
|
39
41
|
readonly UPDATE_GLOBAL_STYLES: "tool_update_global_styles";
|
|
40
42
|
readonly UPDATE_HEADER: "update_header";
|
|
@@ -72,7 +74,11 @@ export interface DeleteSectionArguments {
|
|
|
72
74
|
}
|
|
73
75
|
export interface MoveSectionArguments {
|
|
74
76
|
content_id: string;
|
|
75
|
-
|
|
77
|
+
/**
|
|
78
|
+
* Relative anchor on the destination page: the content_id to insert this section
|
|
79
|
+
* BEFORE (a page's first content_id moves it to the top). Omit to append to the end.
|
|
80
|
+
*/
|
|
81
|
+
move_before_content_id?: string;
|
|
76
82
|
target_page_id?: string;
|
|
77
83
|
}
|
|
78
84
|
export interface AddPageArguments {
|
|
@@ -86,8 +92,17 @@ export interface RenamePageArguments {
|
|
|
86
92
|
page_id: string;
|
|
87
93
|
page_name: string;
|
|
88
94
|
}
|
|
89
|
-
export interface
|
|
90
|
-
|
|
95
|
+
export interface MovePageArguments {
|
|
96
|
+
page_id: string;
|
|
97
|
+
/** Destination parent page_id, or null/omitted for top-level. */
|
|
98
|
+
parent_page_id?: string | null;
|
|
99
|
+
/** Sibling anchor page_id to insert before, or "__end__"/omitted to append. */
|
|
100
|
+
move_before_page_id?: string | null;
|
|
101
|
+
}
|
|
102
|
+
export interface SetPageVisibilityArguments {
|
|
103
|
+
page_id: string;
|
|
104
|
+
/** true = hide the page from the navigation; false = show it. */
|
|
105
|
+
is_hidden: boolean;
|
|
91
106
|
}
|
|
92
107
|
/**
|
|
93
108
|
* Arguments for the `undo` tool call.
|
|
@@ -99,8 +114,17 @@ export interface ReorderPagesArguments {
|
|
|
99
114
|
export interface UndoArguments {
|
|
100
115
|
}
|
|
101
116
|
export interface FooterSocialLinkItem {
|
|
102
|
-
|
|
103
|
-
|
|
117
|
+
/**
|
|
118
|
+
* Stable id of the existing link to update or move. Omit to ADD a new link.
|
|
119
|
+
* Mirrors the section-level item_id convention (BP-5603).
|
|
120
|
+
*/
|
|
121
|
+
item_id?: string;
|
|
122
|
+
/**
|
|
123
|
+
* Id of an existing link to position this link before (insert for new links,
|
|
124
|
+
* move for existing ones). Use "__end__" for the end of the list. Omit to
|
|
125
|
+
* leave an existing link in place / append a new link.
|
|
126
|
+
*/
|
|
127
|
+
move_before_item_id?: string;
|
|
104
128
|
social_link_icon?: string;
|
|
105
129
|
url?: string;
|
|
106
130
|
}
|
|
@@ -122,8 +146,8 @@ export interface UpdateFooterArguments {
|
|
|
122
146
|
copyright_text_style?: TextStyleArguments;
|
|
123
147
|
social_links?: FooterSocialLinkItem[];
|
|
124
148
|
_replace_social_links?: boolean;
|
|
125
|
-
_remove_social_link_indices
|
|
126
|
-
|
|
149
|
+
/** Stable ids of social links to remove. Order-independent (replaces index-based _remove_social_link_indices). */
|
|
150
|
+
_remove_social_link_ids?: string[];
|
|
127
151
|
/** Desktop-only styling (display_style, background_color, icon_color, is_logo_visible, logo sizes). */
|
|
128
152
|
desktop?: ViewportFooterOverrides;
|
|
129
153
|
/** Mobile-only styling (display_style, background_color, icon_color, is_logo_visible, logo sizes). */
|
|
@@ -41,6 +41,8 @@ export const AI_ASSISTANT_TOOL_NAMES = {
|
|
|
41
41
|
ADD_PAGE: 'add_page',
|
|
42
42
|
REMOVE_PAGE: 'remove_page',
|
|
43
43
|
RENAME_PAGE: 'rename_page',
|
|
44
|
+
MOVE_PAGE: 'move_page',
|
|
45
|
+
SET_PAGE_VISIBILITY: 'set_page_visibility',
|
|
44
46
|
// Footer operation tools
|
|
45
47
|
UPDATE_FOOTER: 'update_footer',
|
|
46
48
|
// Global styles operation tools
|