@designcrowd/library.brand-page 5.19.1-id-based-items-1 → 5.19.1
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-0PcGqy0N.js → CreatedOnBrandCrowd-de-de-BmeMc9oz.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-en-us-DB8wcFMI.js → CreatedOnBrandCrowd-en-us-CQNdm_6P.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-es-es-DVWQ243C.js → CreatedOnBrandCrowd-es-es-BrmHWeAM.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-fr-D-M_L0Du.js → CreatedOnBrandCrowd-fr-ca-CaIyjTXl.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-ca-D-M_L0Du.js → CreatedOnBrandCrowd-fr-fr-CaIyjTXl.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-pt-CNpzknxU.js → CreatedOnBrandCrowd-pt-br-BClE0Nd9.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-br-CNpzknxU.js → CreatedOnBrandCrowd-pt-pt-BClE0Nd9.js} +1 -1
- package/lib/src/{CreatedOnDcom-de-de-DMJoD5wE.js → CreatedOnDcom-de-de-CkrdlPqK.js} +1 -1
- package/lib/src/{CreatedOnDcom-en-us-B-F3Pmjv.js → CreatedOnDcom-en-us-D33J9qky.js} +1 -1
- package/lib/src/{CreatedOnDcom-es-es-DmLk-cA2.js → CreatedOnDcom-es-es-BsmInngw.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-ca-ntnsCzvA.js → CreatedOnDcom-fr-ca-DOXlsRea.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-fr-DFcO0gHL.js → CreatedOnDcom-fr-fr-SfFYLtyn.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-br-CtuJoVXW.js → CreatedOnDcom-pt-br-DcWeuZUy.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-pt-B7shZVVn.js → CreatedOnDcom-pt-pt-DZsZZ4iX.js} +1 -1
- package/lib/src/brand-page/components/image-gallery/masonry/MasonryGallery.mixin.d.ts +1 -1
- package/lib/src/brand-page/models/index.d.ts +0 -12
- package/lib/src/brand-page-maker/stores/ai-checkpoint/ai-checkpoint-store.d.ts +0 -18
- package/lib/src/index.mjs +1 -1
- package/lib/src/{lib-BQmJBVbS.js → lib-C5vGkjfs.js} +17561 -17370
- package/lib/src/metadata-builder/builders/footer-builder.js +44 -34
- package/lib/src/metadata-builder/builders/section-builders/image-gallery-builder.js +68 -57
- package/lib/src/metadata-builder/builders/section-builders/images-section-builder.js +93 -58
- package/lib/src/metadata-builder/builders/section-builders/shapes-section-builder.js +99 -66
- package/lib/src/metadata-builder/builders/section-builders/social-links-builder.js +17 -57
- package/lib/src/metadata-builder/builders/section-builders/testimonial-section-builder.js +85 -51
- package/lib/src/metadata-builder/builders/section-builders/text-section-builder.js +74 -49
- package/lib/src/metadata-builder/parsers/ai-toolcall-parser.d.ts +4 -13
- package/lib/src/metadata-builder/parsers/section-tool-arguments.d.ts +48 -66
- package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +0 -2
- package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +0 -2
- package/package.json +1 -1
- package/src/brand-page/components/image-gallery/masonry/MasonryGallery.mixin.ts +1 -2
- package/src/brand-page/models/index.ts +0 -12
- package/src/metadata-builder/builders/footer-builder.test.ts +0 -298
- package/src/metadata-builder/builders/footer-builder.ts +65 -56
- package/src/metadata-builder/builders/section-builders/image-gallery-builder.ts +88 -62
- package/src/metadata-builder/builders/section-builders/images-section-builder.ts +129 -66
- package/src/metadata-builder/builders/section-builders/shapes-section-builder.ts +160 -105
- package/src/metadata-builder/builders/section-builders/social-links-builder.ts +20 -65
- package/src/metadata-builder/builders/section-builders/testimonial-section-builder.ts +146 -83
- package/src/metadata-builder/builders/section-builders/text-section-builder.ts +122 -70
- package/src/metadata-builder/parsers/ai-toolcall-parser.ts +4 -13
- package/src/metadata-builder/parsers/section-tool-arguments.ts +48 -66
- package/lib/src/metadata-builder/builders/item-id-utils.d.ts +0 -41
- package/lib/src/metadata-builder/builders/item-id-utils.js +0 -68
- package/src/metadata-builder/builders/item-id-utils.test.ts +0 -118
- package/src/metadata-builder/builders/item-id-utils.ts +0 -85
|
@@ -25,12 +25,6 @@ import {
|
|
|
25
25
|
validateTestimonialSection,
|
|
26
26
|
TESTIMONIAL_ITEM_CAP,
|
|
27
27
|
} from '../section-rules/testimonial-section.rules';
|
|
28
|
-
import {
|
|
29
|
-
ensureItemIds,
|
|
30
|
-
generateItemId,
|
|
31
|
-
removeItemsByIds,
|
|
32
|
-
repositionItemByAnchor,
|
|
33
|
-
} from '../item-id-utils';
|
|
34
28
|
|
|
35
29
|
const resolveItemColorsAsync = async (
|
|
36
30
|
item: TestimonialItem,
|
|
@@ -56,7 +50,6 @@ const applyTestimonialItemsAsync = async (
|
|
|
56
50
|
): Promise<void> => {
|
|
57
51
|
content.items = await Promise.all(
|
|
58
52
|
items.map(async (item) => ({
|
|
59
|
-
id: item.item_id || generateItemId(),
|
|
60
53
|
imageUrl: item.avatar_shape_url || item.avatar_image_url || '',
|
|
61
54
|
name: item.name || '',
|
|
62
55
|
position: item.position || '',
|
|
@@ -67,77 +60,101 @@ const applyTestimonialItemsAsync = async (
|
|
|
67
60
|
);
|
|
68
61
|
};
|
|
69
62
|
|
|
70
|
-
/**
|
|
71
|
-
* Apply a batch of item operations by id (BP-5603), mirroring the section-level
|
|
72
|
-
* id-based contract one level deeper:
|
|
73
|
-
* - item with `item_id` matching an existing item → update in place (and
|
|
74
|
-
* reposition if `move_before_item_id` is set);
|
|
75
|
-
* - item without `item_id` → add a new item,
|
|
76
|
-
* positioned by `move_before_item_id`;
|
|
77
|
-
* - `_remove_item_ids` → delete by id.
|
|
78
|
-
*
|
|
79
|
-
* Existing items are id-backfilled first so legacy sections become referenceable
|
|
80
|
-
* the moment the metadata builder touches them. Order-independent: there are no
|
|
81
|
-
* positional indices to shift between operations.
|
|
82
|
-
*/
|
|
83
63
|
const mergeTestimonialItemsAsync = async (
|
|
84
64
|
content: TestimonialSectionContent,
|
|
85
65
|
items: TestimonialItem[],
|
|
86
|
-
|
|
66
|
+
replaceAll: boolean,
|
|
87
67
|
): Promise<void> => {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
68
|
+
const hasIndexedItems = items.some((item) => item.item_index !== undefined);
|
|
69
|
+
|
|
70
|
+
if (hasIndexedItems) {
|
|
71
|
+
await Promise.all(
|
|
72
|
+
items.map(async (item) => {
|
|
73
|
+
const targetIndex = item.item_index;
|
|
74
|
+
|
|
75
|
+
if (
|
|
76
|
+
targetIndex === undefined ||
|
|
77
|
+
targetIndex < 0 ||
|
|
78
|
+
!content.items?.[targetIndex]
|
|
79
|
+
)
|
|
80
|
+
return;
|
|
81
|
+
|
|
82
|
+
const existing = content.items[targetIndex];
|
|
83
|
+
const prevImageUrl = existing.imageUrl;
|
|
84
|
+
|
|
85
|
+
if (item.avatar_shape_url !== undefined) {
|
|
86
|
+
existing.imageUrl = item.avatar_shape_url;
|
|
87
|
+
existing.mediaType = 'shape';
|
|
88
|
+
} else if (item.avatar_image_url !== undefined) {
|
|
89
|
+
existing.imageUrl = item.avatar_image_url;
|
|
90
|
+
existing.mediaType = 'image';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (item.name !== undefined) existing.name = item.name;
|
|
94
|
+
|
|
95
|
+
if (item.position !== undefined) existing.position = item.position;
|
|
96
|
+
|
|
97
|
+
if (item.testimonial !== undefined)
|
|
98
|
+
existing.testimonials = item.testimonial;
|
|
99
|
+
|
|
100
|
+
if (
|
|
101
|
+
item.avatar_shape_url !== undefined ||
|
|
102
|
+
item.color_theme !== undefined
|
|
103
|
+
) {
|
|
104
|
+
existing.colorReplacements = await resolveItemColorsAsync(
|
|
105
|
+
item,
|
|
106
|
+
existing.colorReplacements,
|
|
107
|
+
prevImageUrl,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
}),
|
|
111
|
+
);
|
|
99
112
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
existing.mediaType = 'shape';
|
|
103
|
-
} else if (item.avatar_image_url !== undefined) {
|
|
104
|
-
existing.imageUrl = item.avatar_image_url;
|
|
105
|
-
existing.mediaType = 'image';
|
|
106
|
-
}
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
107
115
|
|
|
108
|
-
|
|
116
|
+
const itemsToMerge = items.filter((item) => item.insert_at === undefined);
|
|
109
117
|
|
|
110
|
-
|
|
118
|
+
const updatedItems = await Promise.all(
|
|
119
|
+
itemsToMerge.map(async (item, index) => {
|
|
120
|
+
const existing = content.items?.[index];
|
|
111
121
|
|
|
112
|
-
|
|
113
|
-
|
|
122
|
+
const imageUrl =
|
|
123
|
+
item.avatar_shape_url ??
|
|
124
|
+
item.avatar_image_url ??
|
|
125
|
+
existing?.imageUrl ??
|
|
126
|
+
'';
|
|
127
|
+
const mediaType = item.avatar_shape_url
|
|
128
|
+
? 'shape'
|
|
129
|
+
: item.avatar_image_url
|
|
130
|
+
? 'image'
|
|
131
|
+
: existing?.mediaType ?? 'image';
|
|
114
132
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
133
|
+
return {
|
|
134
|
+
...(existing || {}),
|
|
135
|
+
imageUrl,
|
|
136
|
+
name: item.name ?? existing?.name ?? '',
|
|
137
|
+
position: item.position ?? existing?.position ?? '',
|
|
138
|
+
testimonials: item.testimonial ?? existing?.testimonials ?? '',
|
|
139
|
+
colorReplacements: await resolveItemColorsAsync(
|
|
120
140
|
item,
|
|
121
|
-
existing
|
|
122
|
-
|
|
123
|
-
)
|
|
124
|
-
|
|
141
|
+
existing?.colorReplacements,
|
|
142
|
+
existing?.imageUrl,
|
|
143
|
+
),
|
|
144
|
+
mediaType,
|
|
145
|
+
};
|
|
146
|
+
}),
|
|
147
|
+
);
|
|
125
148
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
position: item.position ?? '',
|
|
133
|
-
testimonials: item.testimonial ?? '',
|
|
134
|
-
colorReplacements: await resolveItemColorsAsync(item, [], undefined),
|
|
135
|
-
mediaType: item.avatar_shape_url ? 'shape' : 'image',
|
|
136
|
-
} as TestimonialSectionContent['items'][number];
|
|
137
|
-
|
|
138
|
-
repositionItemByAnchor(content.items, newItem, item.move_before_item_id);
|
|
139
|
-
}
|
|
149
|
+
if (
|
|
150
|
+
!replaceAll &&
|
|
151
|
+
content.items &&
|
|
152
|
+
content.items.length > itemsToMerge.length
|
|
153
|
+
) {
|
|
154
|
+
updatedItems.push(...content.items.slice(itemsToMerge.length));
|
|
140
155
|
}
|
|
156
|
+
|
|
157
|
+
content.items = updatedItems;
|
|
141
158
|
};
|
|
142
159
|
|
|
143
160
|
export class TestimonialSectionBuilder extends BaseSectionBuilder<TestimonialSectionContent> {
|
|
@@ -211,29 +228,75 @@ export class TestimonialSectionBuilder extends BaseSectionBuilder<TestimonialSec
|
|
|
211
228
|
};
|
|
212
229
|
}
|
|
213
230
|
|
|
214
|
-
if (args.
|
|
215
|
-
|
|
216
|
-
await applyTestimonialItemsAsync(content, args.items);
|
|
217
|
-
} else if (this.mode === 'create') {
|
|
218
|
-
if (args.items && Array.isArray(args.items)) {
|
|
231
|
+
if (args.items && Array.isArray(args.items)) {
|
|
232
|
+
if (this.mode === 'create') {
|
|
219
233
|
await applyTestimonialItemsAsync(content, args.items);
|
|
234
|
+
} else {
|
|
235
|
+
await mergeTestimonialItemsAsync(
|
|
236
|
+
content,
|
|
237
|
+
args.items,
|
|
238
|
+
!!args._replace_items,
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Phase 2: Item operations (remove, insert, reorder)
|
|
244
|
+
if (args._remove_item_indices?.length && content.items) {
|
|
245
|
+
const sorted = [...args._remove_item_indices].sort((a, b) => b - a);
|
|
246
|
+
|
|
247
|
+
for (const idx of sorted) {
|
|
248
|
+
if (idx >= 0 && idx < content.items.length) {
|
|
249
|
+
content.items.splice(idx, 1);
|
|
250
|
+
}
|
|
220
251
|
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
args._remove_item_ids,
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (args.items && Array.isArray(args.items)) {
|
|
255
|
+
const insertItems = args.items.filter(
|
|
256
|
+
(item) => item.insert_at !== undefined && item.item_index === undefined,
|
|
257
|
+
);
|
|
258
|
+
const sortedInserts = [...insertItems].sort(
|
|
259
|
+
(a, b) => (b.insert_at ?? 0) - (a.insert_at ?? 0),
|
|
230
260
|
);
|
|
261
|
+
|
|
262
|
+
for (const item of sortedInserts) {
|
|
263
|
+
const pos = Math.max(
|
|
264
|
+
0,
|
|
265
|
+
Math.min(item.insert_at ?? 0, content.items?.length ?? 0),
|
|
266
|
+
);
|
|
267
|
+
const newItem = {
|
|
268
|
+
imageUrl: item.avatar_shape_url ?? item.avatar_image_url ?? '',
|
|
269
|
+
name: item.name ?? '',
|
|
270
|
+
position: item.position ?? '',
|
|
271
|
+
testimonials: item.testimonial ?? '',
|
|
272
|
+
colorReplacements: await resolveItemColorsAsync(item, [], undefined),
|
|
273
|
+
mediaType: item.avatar_shape_url ? 'shape' : 'image',
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
content.items = content.items ?? [];
|
|
277
|
+
content.items.splice(pos, 0, newItem as any);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (args._reorder_items?.length && content.items) {
|
|
282
|
+
const newOrder = args._reorder_items;
|
|
283
|
+
const unique = new Set(newOrder);
|
|
284
|
+
|
|
285
|
+
if (unique.size === newOrder.length) {
|
|
286
|
+
const reordered = newOrder
|
|
287
|
+
.filter((idx) => idx >= 0 && idx < content.items!.length)
|
|
288
|
+
.map((idx) => content.items![idx]);
|
|
289
|
+
|
|
290
|
+
if (reordered.length === content.items.length) {
|
|
291
|
+
content.items = reordered;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
231
294
|
}
|
|
232
295
|
|
|
233
296
|
// Post-merge cap check. itemCapCheck only fires for create / update with
|
|
234
|
-
// _replace_items, so
|
|
235
|
-
//
|
|
236
|
-
//
|
|
297
|
+
// _replace_items, so insert_at on an already-full section silently bloats
|
|
298
|
+
// the array. Surface as an error so the AI retry loop gets a signal and
|
|
299
|
+
// can reissue with _remove_item_indices or fewer inserts.
|
|
237
300
|
// brandSite key mirrors base-section-builder's hardcoded brandPageType.
|
|
238
301
|
const cap = TESTIMONIAL_ITEM_CAP.brandSite;
|
|
239
302
|
|
|
@@ -19,12 +19,6 @@ import {
|
|
|
19
19
|
} from '../section-field-utils';
|
|
20
20
|
import { BaseSectionBuilder } from './base-section-builder';
|
|
21
21
|
import { validateTextSection } from '../section-rules/text-section.rules';
|
|
22
|
-
import {
|
|
23
|
-
ensureItemIds,
|
|
24
|
-
generateItemId,
|
|
25
|
-
removeItemsByIds,
|
|
26
|
-
repositionItemByAnchor,
|
|
27
|
-
} from '../item-id-utils';
|
|
28
22
|
|
|
29
23
|
/** Resolve a RichTextField (string or TiptapDocument) to a plain HTML string. */
|
|
30
24
|
const resolveText = (value: unknown): { html: string; wasTiptap: boolean } => {
|
|
@@ -58,7 +52,6 @@ const createTextItem = (
|
|
|
58
52
|
const { html, wasTiptap } = resolveText(item.text);
|
|
59
53
|
|
|
60
54
|
return {
|
|
61
|
-
id: item.item_id || generateItemId(),
|
|
62
55
|
text: html,
|
|
63
56
|
textStyle: clearTextStyleTypeIfTiptap(
|
|
64
57
|
textStyle,
|
|
@@ -68,10 +61,6 @@ const createTextItem = (
|
|
|
68
61
|
};
|
|
69
62
|
};
|
|
70
63
|
|
|
71
|
-
/**
|
|
72
|
-
* Full replacement: discard existing items and rebuild from the payload,
|
|
73
|
-
* assigning a stable id to each new item.
|
|
74
|
-
*/
|
|
75
64
|
const applyTextItems = (
|
|
76
65
|
content: TextContent,
|
|
77
66
|
items: TextSectionItem[],
|
|
@@ -82,33 +71,26 @@ const applyTextItems = (
|
|
|
82
71
|
);
|
|
83
72
|
};
|
|
84
73
|
|
|
85
|
-
/**
|
|
86
|
-
* Id-based merge (BP-5603): mirrors the section-level id contract one level
|
|
87
|
-
* deeper.
|
|
88
|
-
* - item with `item_id` matching an existing item → update in place and
|
|
89
|
-
* reposition if `move_before_item_id` is set; unknown id is skipped.
|
|
90
|
-
* - item without `item_id` → add a new item,
|
|
91
|
-
* positioned by `move_before_item_id`.
|
|
92
|
-
* - `removeIds` → delete by id.
|
|
93
|
-
*
|
|
94
|
-
* Existing items are id-backfilled first so legacy sections without ids
|
|
95
|
-
* become referenceable the moment this builder processes them.
|
|
96
|
-
*/
|
|
97
74
|
const mergeTextItems = (
|
|
98
75
|
content: TextContent,
|
|
99
76
|
items: TextSectionItem[],
|
|
100
|
-
|
|
77
|
+
replaceAll: boolean,
|
|
101
78
|
textStyleOptions: TextStyleOptions | undefined,
|
|
102
79
|
): void => {
|
|
103
|
-
|
|
104
|
-
|
|
80
|
+
const hasIndexedItems = items.some((item) => item.item_index !== undefined);
|
|
81
|
+
|
|
82
|
+
if (hasIndexedItems) {
|
|
83
|
+
for (const item of items) {
|
|
84
|
+
const targetIndex = item.item_index;
|
|
105
85
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
86
|
+
if (
|
|
87
|
+
targetIndex === undefined ||
|
|
88
|
+
targetIndex < 0 ||
|
|
89
|
+
!content.textItems?.[targetIndex]
|
|
90
|
+
)
|
|
91
|
+
continue;
|
|
109
92
|
|
|
110
|
-
|
|
111
|
-
if (!existing) continue;
|
|
93
|
+
const existing = content.textItems[targetIndex];
|
|
112
94
|
|
|
113
95
|
if (item.text !== undefined) {
|
|
114
96
|
const { html, wasTiptap } = resolveText(item.text);
|
|
@@ -122,32 +104,51 @@ const mergeTextItems = (
|
|
|
122
104
|
}
|
|
123
105
|
|
|
124
106
|
if (item.text_style && typeof item.text_style === 'object') {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
107
|
+
const mergedStyle = (
|
|
108
|
+
item.text_style
|
|
109
|
+
? { ...(existing.textStyle || {}), ...item.text_style }
|
|
110
|
+
: existing.textStyle || {}
|
|
111
|
+
) as TextStyleDeclaration;
|
|
112
|
+
|
|
113
|
+
existing.textStyle = mergedStyle;
|
|
129
114
|
}
|
|
115
|
+
}
|
|
130
116
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const itemsToMerge = items.filter((item) => item.insert_at === undefined);
|
|
121
|
+
|
|
122
|
+
const updatedItems = itemsToMerge.map((item, index) => {
|
|
123
|
+
const existing = content.textItems?.[index];
|
|
124
|
+
const baseStyle = existing?.textStyle || content.textStyle || {};
|
|
125
|
+
|
|
126
|
+
const mergedStyle = (
|
|
127
|
+
item.text_style ? { ...baseStyle, ...item.text_style } : baseStyle
|
|
128
|
+
) as TextStyleDeclaration;
|
|
129
|
+
|
|
130
|
+
const resolved = item.text !== undefined ? resolveText(item.text) : null;
|
|
131
|
+
const text = resolved ? resolved.html : existing?.text ?? '';
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
text,
|
|
135
|
+
textStyle: clearTextStyleTypeIfTiptap(
|
|
136
|
+
mergedStyle,
|
|
137
|
+
resolved?.wasTiptap ?? false,
|
|
140
138
|
textStyleOptions,
|
|
141
|
-
)
|
|
139
|
+
),
|
|
140
|
+
};
|
|
141
|
+
});
|
|
142
142
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}
|
|
143
|
+
if (
|
|
144
|
+
!replaceAll &&
|
|
145
|
+
content.textItems &&
|
|
146
|
+
content.textItems.length > itemsToMerge.length
|
|
147
|
+
) {
|
|
148
|
+
updatedItems.push(...content.textItems.slice(itemsToMerge.length));
|
|
150
149
|
}
|
|
150
|
+
|
|
151
|
+
content.textItems = updatedItems;
|
|
151
152
|
};
|
|
152
153
|
|
|
153
154
|
/**
|
|
@@ -235,31 +236,82 @@ export class TextSectionBuilder extends BaseSectionBuilder<TextContent> {
|
|
|
235
236
|
|
|
236
237
|
const args = this.args as TextSectionArguments;
|
|
237
238
|
|
|
238
|
-
if (args.
|
|
239
|
-
|
|
240
|
-
applyTextItems(content, args.items, textStyleOptions);
|
|
241
|
-
} else if (this.mode === 'create') {
|
|
242
|
-
if (args.items && Array.isArray(args.items)) {
|
|
239
|
+
if (args.items && Array.isArray(args.items)) {
|
|
240
|
+
if (this.mode === 'create') {
|
|
243
241
|
applyTextItems(content, args.items, textStyleOptions);
|
|
244
242
|
} else {
|
|
245
|
-
|
|
243
|
+
mergeTextItems(
|
|
244
|
+
content,
|
|
245
|
+
args.items,
|
|
246
|
+
!!args._replace_items,
|
|
247
|
+
textStyleOptions,
|
|
248
|
+
);
|
|
246
249
|
}
|
|
247
|
-
} else if (
|
|
248
|
-
(args.items && Array.isArray(args.items)) ||
|
|
249
|
-
args._remove_item_ids?.length
|
|
250
|
-
) {
|
|
251
|
-
// Update: id-based add / edit / move / remove in a single pass.
|
|
252
|
-
mergeTextItems(
|
|
253
|
-
content,
|
|
254
|
-
args.items ?? [],
|
|
255
|
-
args._remove_item_ids,
|
|
256
|
-
textStyleOptions,
|
|
257
|
-
);
|
|
258
250
|
} else {
|
|
259
|
-
// No items array: apply the single text/text_style shorthand fields.
|
|
260
251
|
applySingleTextField(content, args, textStyleOptions);
|
|
261
252
|
}
|
|
262
253
|
|
|
254
|
+
// Phase 2: Item operations (remove, insert, reorder)
|
|
255
|
+
if (args._remove_item_indices?.length && content.textItems) {
|
|
256
|
+
const sorted = [...args._remove_item_indices].sort((a, b) => b - a);
|
|
257
|
+
|
|
258
|
+
for (const idx of sorted) {
|
|
259
|
+
if (idx >= 0 && idx < content.textItems.length) {
|
|
260
|
+
content.textItems.splice(idx, 1);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (args.items && Array.isArray(args.items)) {
|
|
266
|
+
const insertItems = args.items.filter(
|
|
267
|
+
(item) => item.insert_at !== undefined && item.item_index === undefined,
|
|
268
|
+
);
|
|
269
|
+
const sortedInserts = [...insertItems].sort(
|
|
270
|
+
(a, b) => (b.insert_at ?? 0) - (a.insert_at ?? 0),
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
for (const item of sortedInserts) {
|
|
274
|
+
const pos = Math.max(
|
|
275
|
+
0,
|
|
276
|
+
Math.min(item.insert_at ?? 0, content.textItems?.length ?? 0),
|
|
277
|
+
);
|
|
278
|
+
const baseStyle = content.textStyle || {};
|
|
279
|
+
const textStyle = (
|
|
280
|
+
item.text_style
|
|
281
|
+
? { ...baseStyle, ...item.text_style }
|
|
282
|
+
: { ...baseStyle }
|
|
283
|
+
) as TextStyleDeclaration;
|
|
284
|
+
|
|
285
|
+
const { html, wasTiptap } = resolveText(item.text);
|
|
286
|
+
const newItem: TextItem = {
|
|
287
|
+
text: html,
|
|
288
|
+
textStyle: clearTextStyleTypeIfTiptap(
|
|
289
|
+
textStyle,
|
|
290
|
+
wasTiptap,
|
|
291
|
+
textStyleOptions,
|
|
292
|
+
),
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
content.textItems = content.textItems ?? [];
|
|
296
|
+
content.textItems.splice(pos, 0, newItem);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (args._reorder_items?.length && content.textItems) {
|
|
301
|
+
const newOrder = args._reorder_items;
|
|
302
|
+
const unique = new Set(newOrder);
|
|
303
|
+
|
|
304
|
+
if (unique.size === newOrder.length) {
|
|
305
|
+
const reordered = newOrder
|
|
306
|
+
.filter((idx) => idx >= 0 && idx < content.textItems!.length)
|
|
307
|
+
.map((idx) => content.textItems![idx]);
|
|
308
|
+
|
|
309
|
+
if (reordered.length === content.textItems.length) {
|
|
310
|
+
content.textItems = reordered;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
263
315
|
if (args.background_color !== undefined) {
|
|
264
316
|
content.backgroundColor = {
|
|
265
317
|
...content.backgroundColor,
|
|
@@ -202,17 +202,8 @@ export interface ReorderPagesArguments {
|
|
|
202
202
|
export interface UndoArguments {}
|
|
203
203
|
|
|
204
204
|
export interface FooterSocialLinkItem {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
* Mirrors the section-level item_id convention (BP-5603).
|
|
208
|
-
*/
|
|
209
|
-
item_id?: string;
|
|
210
|
-
/**
|
|
211
|
-
* Id of an existing link to position this link before (insert for new links,
|
|
212
|
-
* move for existing ones). Use "__end__" for the end of the list. Omit to
|
|
213
|
-
* leave an existing link in place / append a new link.
|
|
214
|
-
*/
|
|
215
|
-
move_before_item_id?: string;
|
|
205
|
+
item_index?: number;
|
|
206
|
+
insert_at?: number;
|
|
216
207
|
social_link_icon?: string;
|
|
217
208
|
url?: string;
|
|
218
209
|
}
|
|
@@ -236,8 +227,8 @@ export interface UpdateFooterArguments {
|
|
|
236
227
|
copyright_text_font_family?: string;
|
|
237
228
|
social_links?: FooterSocialLinkItem[];
|
|
238
229
|
_replace_social_links?: boolean;
|
|
239
|
-
|
|
240
|
-
|
|
230
|
+
_remove_social_link_indices?: number[];
|
|
231
|
+
_reorder_social_links?: number[];
|
|
241
232
|
/** Desktop-only styling (display_style, background_color, icon_color, is_logo_visible, logo sizes). */
|
|
242
233
|
desktop?: ViewportFooterOverrides;
|
|
243
234
|
/** Mobile-only styling (display_style, background_color, icon_color, is_logo_visible, logo sizes). */
|