@designcrowd/library.brand-page 6.0.26-json-prereq-2 → 6.0.27-websitev2-dev-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-CKBqjDH3.js → CreatedOnBrandCrowd-de-de-BpFKOKIo.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-en-us-CRXiimvT.js → CreatedOnBrandCrowd-en-us-USGAnC_n.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-es-es-DCYdY6sj.js → CreatedOnBrandCrowd-es-es-CvVx3h4H.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-fr-Dk7LOhxE.js → CreatedOnBrandCrowd-fr-ca-BCwamW5a.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-ca-Dk7LOhxE.js → CreatedOnBrandCrowd-fr-fr-BCwamW5a.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-pt-Cp4ufHJP.js → CreatedOnBrandCrowd-pt-br-Bt72hMrQ.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-br-Cp4ufHJP.js → CreatedOnBrandCrowd-pt-pt-Bt72hMrQ.js} +1 -1
- package/lib/src/{CreatedOnDcom-de-de-ZSLkWMwM.js → CreatedOnDcom-de-de-BxUDrgG9.js} +1 -1
- package/lib/src/{CreatedOnDcom-en-us-BgdIGfT3.js → CreatedOnDcom-en-us-wjVp5eOs.js} +1 -1
- package/lib/src/{CreatedOnDcom-es-es-D0KemkCZ.js → CreatedOnDcom-es-es-CwFL2q5h.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-ca-BuwNjNbC.js → CreatedOnDcom-fr-ca-BPZ2_PNq.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-fr-DCQcsFPf.js → CreatedOnDcom-fr-fr-D0vLOikZ.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-br-DD-oLAS1.js → CreatedOnDcom-pt-br-CTwbxm9H.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-pt-D8qLq2wW.js → CreatedOnDcom-pt-pt-Cfqf8LaM.js} +1 -1
- package/lib/src/brand-page/components/contact-form/ContactForm.mixin.js +2 -5
- package/lib/src/brand-page/models/index.d.ts +26 -34
- package/lib/src/brand-page/utils/migration.util.js +0 -11
- package/lib/src/brand-page/utils/text.util.d.ts +1 -1
- package/lib/src/brand-page/utils/text.util.js +0 -32
- package/lib/src/brand-page/utils/vcard.util.js +4 -4
- package/lib/src/brand-page/utils/view-model.util.d.ts +0 -1
- package/lib/src/brand-page/utils/view-model.util.js +20 -68
- package/lib/src/brand-page/view-models.d.ts +3 -3
- package/lib/src/brand-page-maker/composables/ai-chat/useAiMetadataBuilder.d.ts +1 -0
- package/lib/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.js +7 -3
- package/lib/src/brand-page-maker/composables/rich-text-editor/bubbleMenu.d.ts +9 -2
- package/lib/src/brand-page-maker/composables/rich-text-editor/bubbleMenu.js +10 -6
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontSize.d.ts +20 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontSize.js +44 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/LineHeight.d.ts +27 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/LineHeight.js +44 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.d.ts +1 -1
- package/lib/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.js +103 -24
- package/lib/src/brand-page-maker/utils/index.d.ts +0 -2
- package/lib/src/brand-page-maker/utils/index.js +0 -2
- package/lib/src/brand-page-maker/utils/rich-text-editor.util.d.ts +5 -28
- package/lib/src/brand-page-maker/utils/rich-text-editor.util.js +5 -48
- package/lib/src/brand-page-maker/validators/field.validator.js +2 -10
- package/lib/src/clients/ai-assistant-client.d.ts +1 -0
- package/lib/src/clients/ai-assistant-client.js +1 -0
- package/lib/src/css/library.brand-page-brandCrowd.min.css +39 -39
- package/lib/src/css/library.brand-page-brandPage.min.css +39 -39
- package/lib/src/css/library.brand-page-designCom.min.css +39 -39
- package/lib/src/css/library.brand-page-preview.min.css +39 -39
- package/lib/src/index.d.ts +0 -1
- package/lib/src/index.mjs +138 -143
- package/lib/src/{lib-Db6j9fQZ.js → lib-Cg4BExY4.js} +72135 -75458
- package/lib/src/lib.js +0 -2
- package/lib/src/metadata-builder/ai-metadata-executor.d.ts +1 -0
- package/lib/src/metadata-builder/ai-metadata-executor.js +19 -0
- package/lib/src/metadata-builder/builders/global-font-propagation.d.ts +28 -0
- package/lib/src/metadata-builder/builders/global-font-propagation.js +125 -0
- package/lib/src/metadata-builder/builders/global-styles-builder.js +16 -0
- package/lib/src/metadata-builder/builders/section-builders/about-section-builder.js +3 -2
- package/lib/src/metadata-builder/builders/section-builders/banner-section-builder.js +3 -2
- package/lib/src/metadata-builder/builders/section-builders/booking-section-builder.js +3 -2
- package/lib/src/metadata-builder/builders/section-builders/contact-form-builder.js +4 -3
- package/lib/src/metadata-builder/builders/section-builders/images-section-builder.js +3 -2
- package/lib/src/metadata-builder/builders/section-builders/index.d.ts +1 -0
- package/lib/src/metadata-builder/builders/section-builders/index.js +1 -0
- package/lib/src/metadata-builder/builders/section-builders/mailing-list-builder.js +2 -1
- package/lib/src/metadata-builder/builders/section-builders/mailing-list-section-builder.js +2 -1
- package/lib/src/metadata-builder/builders/section-builders/profile-section-builder.js +4 -3
- package/lib/src/metadata-builder/builders/section-builders/shapes-section-builder.js +3 -2
- package/lib/src/metadata-builder/builders/section-builders/support-me-builder.d.ts +8 -0
- package/lib/src/metadata-builder/builders/section-builders/support-me-builder.js +25 -0
- package/lib/src/metadata-builder/builders/section-builders/testimonial-section-builder.js +2 -1
- package/lib/src/metadata-builder/builders/section-builders/text-section-builder.js +16 -17
- package/lib/src/metadata-builder/builders/section-field-maps.js +15 -0
- package/lib/src/metadata-builder/builders/section-field-utils.d.ts +6 -1
- package/lib/src/metadata-builder/builders/section-field-utils.js +48 -12
- package/lib/src/metadata-builder/builders/section-rules/support-me.rules.d.ts +2 -0
- package/lib/src/metadata-builder/builders/section-rules/support-me.rules.js +17 -0
- package/lib/src/metadata-builder/builders/update-content-text-builder.d.ts +17 -0
- package/lib/src/metadata-builder/builders/update-content-text-builder.js +103 -0
- package/lib/src/metadata-builder/builders/update-content-text-style-builder.d.ts +16 -0
- package/lib/src/metadata-builder/builders/update-content-text-style-builder.js +216 -0
- package/lib/src/metadata-builder/helpers/rte.helpers.d.ts +13 -11
- package/lib/src/metadata-builder/helpers/rte.helpers.js +73 -14
- package/lib/src/metadata-builder/metadata-executor.js +7 -1
- package/lib/src/metadata-builder/models.d.ts +4 -0
- package/lib/src/metadata-builder/models.js +4 -0
- package/lib/src/metadata-builder/parsers/ai-toolcall-parser.d.ts +20 -0
- package/lib/src/metadata-builder/parsers/ai-toolcall-parser.js +8 -0
- package/lib/src/metadata-builder/parsers/section-tool-arguments.d.ts +14 -2
- package/lib/src/metadata-builder/sanitizers/rte-schema-check.d.ts +8 -7
- package/lib/src/metadata-builder/sanitizers/rte-schema-check.js +52 -21
- package/lib/src/shared/content-settings/components/GenericTextInput.mixin.js +1 -14
- package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.js +3 -12
- package/lib/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.mixin.d.ts +2 -3
- package/lib/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.mixin.js +3 -10
- package/lib/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.d.ts +1 -2
- package/lib/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.js +1 -4
- package/lib/src/utils/minify-brand-page-metadata.js +8 -4
- package/package.json +15 -17
- package/src/brand-page/components/contact-form/ContactForm.mixin.ts +2 -5
- package/src/brand-page/components/text-wrapper/TextWrapper.vue +6 -17
- package/src/brand-page/models/index.ts +26 -35
- package/src/brand-page/utils/__snapshots__/migration.util.test.ts.snap +3 -3
- package/src/brand-page/utils/migration.util.ts +0 -12
- package/src/brand-page/utils/style.util.test.ts +0 -51
- package/src/brand-page/utils/text.util.ts +4 -43
- package/src/brand-page/utils/vcard.util.ts +4 -6
- package/src/brand-page/utils/view-model.util.ts +26 -94
- package/src/brand-page/view-models.ts +2 -3
- package/src/brand-page-maker/components/ai-website-builder/AiWebsiteBuilder.vue +1 -1
- package/src/brand-page-maker/components/rich-text-editor/RichTextEditor.vue +3 -4
- package/src/brand-page-maker/composables/ai-chat/useAiMetadataBuilder.ts +1 -0
- package/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.ts +7 -3
- package/src/brand-page-maker/composables/rich-text-editor/bubbleMenu.ts +10 -6
- package/src/brand-page-maker/composables/rich-text-editor/extensions/FontSize.ts +73 -0
- package/src/brand-page-maker/composables/rich-text-editor/extensions/LineHeight.ts +85 -0
- package/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.ts +111 -27
- package/src/brand-page-maker/utils/index.ts +0 -2
- package/src/brand-page-maker/utils/rich-text-editor.util.test.ts +3 -37
- package/src/brand-page-maker/utils/rich-text-editor.util.ts +11 -66
- package/src/brand-page-maker/validators/field.validator.ts +2 -10
- package/src/clients/ai-assistant-client.ts +1 -0
- package/src/lib.ts +0 -9
- package/src/metadata-builder/ai-metadata-executor.ts +26 -0
- package/src/metadata-builder/builders/global-font-propagation.test.ts +269 -0
- package/src/metadata-builder/builders/global-font-propagation.ts +205 -0
- package/src/metadata-builder/builders/global-styles-builder.ts +21 -0
- package/src/metadata-builder/builders/section-builders/about-section-builder.ts +3 -2
- package/src/metadata-builder/builders/section-builders/banner-section-builder.ts +3 -2
- package/src/metadata-builder/builders/section-builders/booking-section-builder.ts +5 -2
- package/src/metadata-builder/builders/section-builders/contact-form-builder.ts +4 -3
- package/src/metadata-builder/builders/section-builders/images-section-builder.ts +3 -2
- package/src/metadata-builder/builders/section-builders/index.ts +1 -0
- package/src/metadata-builder/builders/section-builders/mailing-list-builder.ts +2 -1
- package/src/metadata-builder/builders/section-builders/mailing-list-section-builder.ts +2 -1
- package/src/metadata-builder/builders/section-builders/profile-section-builder.ts +6 -3
- package/src/metadata-builder/builders/section-builders/shapes-section-builder.ts +3 -2
- package/src/metadata-builder/builders/section-builders/support-me-builder.test.ts +177 -0
- package/src/metadata-builder/builders/section-builders/support-me-builder.ts +43 -0
- package/src/metadata-builder/builders/section-builders/testimonial-section-builder.ts +2 -1
- package/src/metadata-builder/builders/section-builders/text-section-builder.ts +18 -21
- package/src/metadata-builder/builders/section-field-maps.ts +18 -0
- package/src/metadata-builder/builders/section-field-utils.test.ts +83 -0
- package/src/metadata-builder/builders/section-field-utils.ts +69 -18
- package/src/metadata-builder/builders/section-rules/support-me.rules.ts +39 -0
- package/src/metadata-builder/builders/update-content-text-builder.ts +148 -0
- package/src/metadata-builder/builders/update-content-text-style-builder.ts +352 -0
- package/src/metadata-builder/helpers/rte.helpers.ts +101 -15
- package/src/metadata-builder/metadata-executor.ts +8 -0
- package/src/metadata-builder/models.ts +5 -0
- package/src/metadata-builder/parsers/ai-toolcall-parser.ts +31 -0
- package/src/metadata-builder/parsers/section-tool-arguments.ts +14 -1
- package/src/metadata-builder/sanitizers/rte-schema-check.ts +53 -24
- package/src/shared/content-settings/components/GenericTextInput.mixin.ts +1 -17
- package/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.ts +3 -16
- package/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.mixin.ts +4 -13
- package/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.vue +4 -6
- package/src/shared/content-settings/components/text-image-field/TextImageField.vue +3 -6
- package/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.ts +1 -6
- package/src/shared/content-settings/components/text-items-field/TextItemsField.vue +1 -1
- package/src/utils/minify-brand-page-metadata.test.ts +61 -0
- package/src/utils/minify-brand-page-metadata.ts +9 -4
- package/lib/src/brand-page/utils/migration/migration-20.d.ts +0 -2
- package/lib/src/brand-page/utils/migration/migration-20.js +0 -66
- package/lib/src/brand-page-maker/__fixtures__/complex-rte.fixture.d.ts +0 -22
- package/lib/src/brand-page-maker/__fixtures__/complex-rte.fixture.js +0 -22
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.d.ts +0 -14
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.js +0 -39
- package/lib/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.d.ts +0 -28
- package/lib/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.js +0 -90
- package/lib/src/brand-page-maker/utils/rte-render.util.d.ts +0 -63
- package/lib/src/brand-page-maker/utils/rte-render.util.js +0 -95
- package/lib/src/brand-page-maker/utils/rte-sanitize.util.d.ts +0 -6
- package/lib/src/brand-page-maker/utils/rte-sanitize.util.js +0 -158
- package/src/brand-page/utils/migration/migration-20.server.test.ts +0 -125
- package/src/brand-page/utils/migration/migration-20.test.ts +0 -72
- package/src/brand-page/utils/migration/migration-20.ts +0 -87
- package/src/brand-page/utils/text.util.test.ts +0 -47
- package/src/brand-page/utils/view-model.pageid-rte.test.ts +0 -104
- package/src/brand-page-maker/__fixtures__/complex-rte.fixture.ts +0 -23
- package/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.test.ts +0 -52
- package/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.ts +0 -42
- package/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.ts +0 -137
- package/src/brand-page-maker/utils/__snapshots__/rte-render-parity.test.ts.snap +0 -202
- package/src/brand-page-maker/utils/rte-features.test.ts +0 -351
- package/src/brand-page-maker/utils/rte-render-parity.test.ts +0 -157
- package/src/brand-page-maker/utils/rte-render.util.ts +0 -165
- package/src/brand-page-maker/utils/rte-sanitize-json.test.ts +0 -158
- package/src/brand-page-maker/utils/rte-sanitize.util.ts +0 -195
- package/src/brand-page-maker/validators/field.validator.test.ts +0 -55
|
@@ -3,6 +3,9 @@ import type { EventTracker } from '../brand-page-maker/composables/useEventTrack
|
|
|
3
3
|
import type { Logger } from './logger';
|
|
4
4
|
|
|
5
5
|
export const BUILDER_TYPES = {
|
|
6
|
+
UPDATE_CONTENT_TEXT: 'update-content-text',
|
|
7
|
+
UPDATE_CONTENT_TEXT_STYLE: 'update-content-text-style',
|
|
8
|
+
|
|
6
9
|
// Per-section builder types (create)
|
|
7
10
|
CREATE_BANNER_SECTION: 'create-banner-section',
|
|
8
11
|
CREATE_ABOUT_SECTION: 'create-about-section',
|
|
@@ -29,6 +32,7 @@ export const BUILDER_TYPES = {
|
|
|
29
32
|
CREATE_TWITCH: 'create-twitch',
|
|
30
33
|
CREATE_TIKTOK: 'create-tiktok',
|
|
31
34
|
CREATE_BOOKING_SECTION: 'create-booking-section',
|
|
35
|
+
CREATE_SUPPORT_ME: 'create-support-me',
|
|
32
36
|
|
|
33
37
|
// Per-section builder types (update)
|
|
34
38
|
UPDATE_BANNER_SECTION: 'update-banner-section',
|
|
@@ -56,6 +60,7 @@ export const BUILDER_TYPES = {
|
|
|
56
60
|
UPDATE_TWITCH: 'update-twitch',
|
|
57
61
|
UPDATE_TIKTOK: 'update-tiktok',
|
|
58
62
|
UPDATE_BOOKING_SECTION: 'update-booking-section',
|
|
63
|
+
UPDATE_SUPPORT_ME: 'update-support-me',
|
|
59
64
|
|
|
60
65
|
// Section-level operations
|
|
61
66
|
DELETE_SECTION: 'delete-section',
|
|
@@ -7,10 +7,15 @@ import type {
|
|
|
7
7
|
import type { SectionType, TextStyleArguments } from './section-tool-arguments';
|
|
8
8
|
|
|
9
9
|
// Tool names — must match the function names generated by the AI pipeline.
|
|
10
|
+
// Text element tools use camelCase names.
|
|
10
11
|
// Section tools follow the pattern: build_{sectionType}_section
|
|
11
12
|
// where sectionType matches the ContentType value (e.g. 'banner-section', 'profile').
|
|
12
13
|
// The mode ('create' | 'update') is a field in the tool arguments, not the tool name.
|
|
13
14
|
export const AI_ASSISTANT_TOOL_NAMES = {
|
|
15
|
+
// Text element agent tools
|
|
16
|
+
UPDATE_TEXT: 'updateText',
|
|
17
|
+
UPDATE_STYLE: 'updateStyle',
|
|
18
|
+
|
|
14
19
|
// Website builder agent tools — unified build
|
|
15
20
|
BUILD_PROFILE_SECTION: 'build_profile_section',
|
|
16
21
|
BUILD_TEXT_SECTION: 'build_text_section',
|
|
@@ -38,6 +43,7 @@ export const AI_ASSISTANT_TOOL_NAMES = {
|
|
|
38
43
|
BUILD_TESTIMONIAL_SECTION: 'build_testimonial-section_section',
|
|
39
44
|
BUILD_DIVIDER_SECTION: 'build_divider-section_section',
|
|
40
45
|
BUILD_BOOKING_SECTION: 'build_booking-section_section',
|
|
46
|
+
BUILD_SUPPORT_ME_SECTION: 'build_support-me_section',
|
|
41
47
|
|
|
42
48
|
// Section-level operation tools
|
|
43
49
|
DELETE_SECTION: 'delete_section',
|
|
@@ -79,6 +85,28 @@ export interface ParsedToolCall {
|
|
|
79
85
|
usage_type?: 'free' | 'paid';
|
|
80
86
|
}
|
|
81
87
|
|
|
88
|
+
export interface BaseArguments {
|
|
89
|
+
message?: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// updateText tool arguments
|
|
93
|
+
export interface UpdateTextToolArguments extends BaseArguments {
|
|
94
|
+
contentId: string;
|
|
95
|
+
fieldName: string;
|
|
96
|
+
metadata: {
|
|
97
|
+
text: string;
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// updateStyle tool arguments
|
|
102
|
+
export interface UpdateStyleToolArguments extends BaseArguments {
|
|
103
|
+
contentId: string;
|
|
104
|
+
fieldName: string;
|
|
105
|
+
metadata: {
|
|
106
|
+
style: BrandPageCssStyle;
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
82
110
|
// Maps each section tool name to its SectionType.
|
|
83
111
|
// Mode is now determined from the tool arguments, not the tool name.
|
|
84
112
|
export const SECTION_TOOL_MAP: Record<string, { sectionType: SectionType }> = {
|
|
@@ -136,6 +164,9 @@ export const SECTION_TOOL_MAP: Record<string, { sectionType: SectionType }> = {
|
|
|
136
164
|
[AI_ASSISTANT_TOOL_NAMES.BUILD_BOOKING_SECTION]: {
|
|
137
165
|
sectionType: 'booking-section',
|
|
138
166
|
},
|
|
167
|
+
[AI_ASSISTANT_TOOL_NAMES.BUILD_SUPPORT_ME_SECTION]: {
|
|
168
|
+
sectionType: 'support-me',
|
|
169
|
+
},
|
|
139
170
|
};
|
|
140
171
|
|
|
141
172
|
export interface DeleteSectionArguments {
|
|
@@ -17,7 +17,6 @@ type UnsupportedContentType =
|
|
|
17
17
|
| 'video'
|
|
18
18
|
| 'map'
|
|
19
19
|
| 'request-payment'
|
|
20
|
-
| 'support-me'
|
|
21
20
|
| 'shop-section';
|
|
22
21
|
|
|
23
22
|
export type SectionType = Exclude<ContentType, UnsupportedContentType>;
|
|
@@ -537,6 +536,19 @@ export interface BookingSectionArguments {
|
|
|
537
536
|
padding_bottom?: number;
|
|
538
537
|
}
|
|
539
538
|
|
|
539
|
+
export interface SupportMeArguments {
|
|
540
|
+
title?: string;
|
|
541
|
+
description?: string;
|
|
542
|
+
success_message?: string;
|
|
543
|
+
/** Matches DISPLAY_STYLE_PREVIEW / DISPLAY_STYLE_COLLAPSED. */
|
|
544
|
+
display_style?: 'preview' | 'collapsed';
|
|
545
|
+
title_style?: TextStyleArguments;
|
|
546
|
+
heading_style?: TextStyleArguments;
|
|
547
|
+
description_style?: TextStyleArguments;
|
|
548
|
+
item_text_style?: TextStyleArguments;
|
|
549
|
+
button_text_style?: ButtonTextStyleArguments;
|
|
550
|
+
}
|
|
551
|
+
|
|
540
552
|
export interface DividerSectionArguments {
|
|
541
553
|
divider_style?: 'none' | 'thin' | 'thick';
|
|
542
554
|
divider_color?: string | null;
|
|
@@ -574,6 +586,7 @@ export type SectionToolArguments =
|
|
|
574
586
|
| TestimonialSectionArguments
|
|
575
587
|
| ShapesSectionArguments
|
|
576
588
|
| BookingSectionArguments
|
|
589
|
+
| SupportMeArguments
|
|
577
590
|
| DividerSectionArguments;
|
|
578
591
|
|
|
579
592
|
// ---------------------------------------------------------------------------
|
|
@@ -1,6 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import
|
|
1
|
+
import { Editor } from '@tiptap/vue-3';
|
|
2
|
+
import StarterKit from '@tiptap/starter-kit';
|
|
3
|
+
import Underline from '@tiptap/extension-underline';
|
|
4
|
+
import TextStyle from '@tiptap/extension-text-style';
|
|
5
|
+
import TextAlign from '@tiptap/extension-text-align';
|
|
6
|
+
import FontFamily from '@tiptap/extension-font-family';
|
|
7
|
+
import ColorExtension from '@tiptap/extension-color';
|
|
8
|
+
import Highlight from '@tiptap/extension-highlight';
|
|
9
|
+
import Link from '@tiptap/extension-link';
|
|
10
|
+
import { FontSize } from '../../brand-page-maker/composables/rich-text-editor/extensions/FontSize';
|
|
11
|
+
import { LetterSpacing } from '../../brand-page-maker/composables/rich-text-editor/extensions/LetterSpacing';
|
|
12
|
+
import { LineHeight } from '../../brand-page-maker/composables/rich-text-editor/extensions/LineHeight';
|
|
13
|
+
import { AutoLink } from '../../brand-page-maker/composables/rich-text-editor/extensions/AutoLink';
|
|
4
14
|
import { isTiptapDocument } from '../parsers/section-tool-arguments';
|
|
5
15
|
import {
|
|
6
16
|
rteStyleCheck,
|
|
@@ -9,30 +19,45 @@ import {
|
|
|
9
19
|
} from './section-args-validator';
|
|
10
20
|
|
|
11
21
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* We import `getRteExtensions` (a leaf module — @tiptap + prosemirror only), not
|
|
18
|
-
* the maker composable (`useExternalRichTextEditor`), which would pull in the
|
|
19
|
-
* AI executor → section-builders → section-rules graph and re-enter this file
|
|
20
|
-
* before its exports are bound.
|
|
22
|
+
* Constructs a headless Tiptap Editor with the same extension set as
|
|
23
|
+
* `useExternalRichTextEditor` in the maker. We import the Tiptap extension
|
|
24
|
+
* files directly (they are leaf modules) instead of going through the maker
|
|
25
|
+
* composable, which would pull in the AI executor → section-builders →
|
|
26
|
+
* section-rules graph and re-enter this file before its exports are bound.
|
|
21
27
|
*/
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
28
|
+
const buildHeadlessEditor = (): Editor =>
|
|
29
|
+
new Editor({
|
|
30
|
+
editable: false,
|
|
31
|
+
extensions: [
|
|
32
|
+
StarterKit,
|
|
33
|
+
Underline,
|
|
34
|
+
TextStyle.configure({ mergeNestedSpanStyles: true }),
|
|
35
|
+
TextAlign.configure({ types: ['heading', 'paragraph'] }),
|
|
36
|
+
FontFamily.configure({ types: ['textStyle'] }),
|
|
37
|
+
FontSize,
|
|
38
|
+
LetterSpacing,
|
|
39
|
+
LineHeight,
|
|
40
|
+
ColorExtension.configure({ types: ['textStyle'] }),
|
|
41
|
+
Highlight.configure({ multicolor: true }),
|
|
42
|
+
Link.configure({
|
|
43
|
+
openOnClick: false,
|
|
44
|
+
defaultProtocol: 'https',
|
|
45
|
+
autolink: true,
|
|
46
|
+
}),
|
|
47
|
+
AutoLink,
|
|
48
|
+
],
|
|
49
|
+
});
|
|
26
50
|
|
|
27
51
|
/**
|
|
28
|
-
* Validates a Tiptap doc against the real ProseMirror
|
|
29
|
-
* (`schema.nodeFromJSON`). Catches malformed AI input — stray
|
|
30
|
-
* in `content`, unknown node types, malformed attrs —
|
|
31
|
-
* and the renderer's silent `setContent` drops
|
|
52
|
+
* Validates a Tiptap doc against the headless editor's real ProseMirror
|
|
53
|
+
* schema (`schema.nodeFromJSON`). Catches malformed AI input — stray
|
|
54
|
+
* non-node children in `content`, unknown node types, malformed attrs —
|
|
55
|
+
* before the builder runs and the renderer's silent `setContent` drops
|
|
56
|
+
* the doc to `<p></p>` (BP-5669).
|
|
32
57
|
*
|
|
33
|
-
* Returns the field-prefixed error string so the AI's tool-call retry
|
|
34
|
-
* it via `BuilderResult.errors` → `tool_result.errorMessage`
|
|
35
|
-
* corrected payload.
|
|
58
|
+
* Returns the field-prefixed error string so the AI's tool-call retry
|
|
59
|
+
* loop reads it via `BuilderResult.errors` → `tool_result.errorMessage`
|
|
60
|
+
* and re-emits a corrected payload.
|
|
36
61
|
*/
|
|
37
62
|
export const rteSchemaCheck =
|
|
38
63
|
(field: string): SectionCheck =>
|
|
@@ -41,14 +66,18 @@ export const rteSchemaCheck =
|
|
|
41
66
|
|
|
42
67
|
if (!isTiptapDocument(value)) return null;
|
|
43
68
|
|
|
69
|
+
const editor = buildHeadlessEditor();
|
|
70
|
+
|
|
44
71
|
try {
|
|
45
|
-
|
|
72
|
+
editor.schema.nodeFromJSON(value);
|
|
46
73
|
|
|
47
74
|
return null;
|
|
48
75
|
} catch (err) {
|
|
49
76
|
const detail = err instanceof Error ? err.message : String(err);
|
|
50
77
|
|
|
51
78
|
return `"${field}" is not valid Tiptap JSON: ${detail}`;
|
|
79
|
+
} finally {
|
|
80
|
+
editor.destroy();
|
|
52
81
|
}
|
|
53
82
|
};
|
|
54
83
|
|
|
@@ -175,23 +175,7 @@ export default defineComponent({
|
|
|
175
175
|
},
|
|
176
176
|
},
|
|
177
177
|
watch: {
|
|
178
|
-
value(newVal: string | undefined
|
|
179
|
-
// BP-5787: RTE values are Tiptap JSON objects. The parent (TextItemsField)
|
|
180
|
-
// re-clones its items array (cloneDeep) on ANY change — including a sibling
|
|
181
|
-
// field's style sync — yielding a NEW object reference with identical text
|
|
182
|
-
// content. For objects Vue fires this watcher on reference change, so that
|
|
183
|
-
// spurious re-clone would overwrite `_value` and clobber an in-flight edit
|
|
184
|
-
// (a mark applied but not yet debounced to the store), silently dropping
|
|
185
|
-
// the user's formatting. Strings never hit this (same content = same
|
|
186
|
-
// reference = no fire), so mirror that: ignore re-clones whose content is
|
|
187
|
-
// unchanged from the previous prop value.
|
|
188
|
-
if (
|
|
189
|
-
this.showRichTextEditor &&
|
|
190
|
-
JSON.stringify(newVal) === JSON.stringify(oldVal)
|
|
191
|
-
) {
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
|
|
178
|
+
value(newVal: string | undefined) {
|
|
195
179
|
this._value = newVal;
|
|
196
180
|
|
|
197
181
|
if (this.showRichTextEditor) {
|
|
@@ -21,9 +21,6 @@ import {
|
|
|
21
21
|
replaceTextInRTE,
|
|
22
22
|
RTE_ALLOWED_SECTION_TYPES,
|
|
23
23
|
isRTEManagedStyleField,
|
|
24
|
-
isTiptapDocument,
|
|
25
|
-
renderRteToHtml,
|
|
26
|
-
toRteJson,
|
|
27
24
|
} from '@brand-page-maker/utils';
|
|
28
25
|
import { isDefaultContentText } from '@shared/content-settings/utils';
|
|
29
26
|
import { useUiLogoSettingsStore } from '../../../logo-settings/store';
|
|
@@ -330,20 +327,10 @@ export default defineComponent({
|
|
|
330
327
|
this.isToggled = isToggled;
|
|
331
328
|
},
|
|
332
329
|
onUseGeneratedText(generatedText: string) {
|
|
333
|
-
let formattedText
|
|
330
|
+
let formattedText = generatedText;
|
|
334
331
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
// (cold AI path, shared editor); string fields use it directly.
|
|
338
|
-
if (this.showRichTextEditor) {
|
|
339
|
-
if (isTiptapDocument(this.value)) {
|
|
340
|
-
formattedText =
|
|
341
|
-
toRteJson(
|
|
342
|
-
replaceTextInRTE(renderRteToHtml(this.value), generatedText),
|
|
343
|
-
) ?? generatedText;
|
|
344
|
-
} else if (typeof this.value === 'string') {
|
|
345
|
-
formattedText = replaceTextInRTE(this.value, generatedText);
|
|
346
|
-
}
|
|
332
|
+
if (this.showRichTextEditor && typeof this.value === 'string') {
|
|
333
|
+
formattedText = replaceTextInRTE(this.value, generatedText);
|
|
347
334
|
}
|
|
348
335
|
|
|
349
336
|
this.contentSettingsStore.updateItemValue({
|
|
@@ -4,7 +4,6 @@ import type {
|
|
|
4
4
|
ColorReplacement,
|
|
5
5
|
Content,
|
|
6
6
|
LogoTheme,
|
|
7
|
-
RichTextValue,
|
|
8
7
|
TestimonialItems,
|
|
9
8
|
} from '@brand-page';
|
|
10
9
|
import {
|
|
@@ -18,7 +17,7 @@ import debounce from 'lodash.debounce';
|
|
|
18
17
|
import type { UpdateItemValuePayload } from '../../models';
|
|
19
18
|
import cloneDeep from 'lodash.clonedeep';
|
|
20
19
|
import { useSnapshotStore } from '@brand-page-maker/stores';
|
|
21
|
-
import { getPlainTextFromRTE
|
|
20
|
+
import { getPlainTextFromRTE } from '@brand-page-maker/utils';
|
|
22
21
|
import { isDefaultContentText } from '../../utils';
|
|
23
22
|
import { decodeHtmlEntities } from '@brand-page/utils';
|
|
24
23
|
import { ESCAPED_NEWLINE_CHARACTER } from '../../../../constants/ui';
|
|
@@ -92,10 +91,8 @@ export default defineComponent({
|
|
|
92
91
|
created() {
|
|
93
92
|
this.options = cloneDeep(this.value);
|
|
94
93
|
|
|
95
|
-
// BP-5787: testimonials may be Tiptap JSON (migrated legacy content), so
|
|
96
|
-
// read visible text via getPlainTextFromRTE rather than `.trim()` on a doc.
|
|
97
94
|
this.isEmpty = this.options.map(
|
|
98
|
-
(option) =>
|
|
95
|
+
(option) => option.testimonials.trim().length === 0,
|
|
99
96
|
);
|
|
100
97
|
|
|
101
98
|
if (!this.changeDebounce) {
|
|
@@ -209,17 +206,11 @@ export default defineComponent({
|
|
|
209
206
|
this.options[index][payload.name] = payload.value;
|
|
210
207
|
this.emitChange();
|
|
211
208
|
},
|
|
212
|
-
getItemTextPreview(text:
|
|
213
|
-
// BP-5787: a JSON doc has no `.replace` — take its visible text first.
|
|
214
|
-
const value = typeof text === 'string' ? text : getPlainTextFromRTE(text);
|
|
215
|
-
|
|
209
|
+
getItemTextPreview(text: string): string {
|
|
216
210
|
return decodeHtmlEntities(
|
|
217
|
-
|
|
211
|
+
text.replace(new RegExp(ESCAPED_NEWLINE_CHARACTER, 'g'), ' '),
|
|
218
212
|
);
|
|
219
213
|
},
|
|
220
|
-
rteTextLength(value: RichTextValue): number {
|
|
221
|
-
return getRteTextLength(value);
|
|
222
|
-
},
|
|
223
214
|
getPlainTextValue(index: number): string {
|
|
224
215
|
const text = this.options[index]?.testimonials || '';
|
|
225
216
|
const plainText = getPlainTextFromRTE(text);
|
package/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.vue
CHANGED
|
@@ -26,8 +26,7 @@
|
|
|
26
26
|
:error="
|
|
27
27
|
isEmpty[option.index] ||
|
|
28
28
|
(options[option.index].testimonials &&
|
|
29
|
-
|
|
30
|
-
maxTestimonialLength)
|
|
29
|
+
options[option.index].testimonials.length >= maxTestimonialLength)
|
|
31
30
|
"
|
|
32
31
|
:resize="false"
|
|
33
32
|
class="tw-w-full"
|
|
@@ -43,8 +42,7 @@
|
|
|
43
42
|
<p
|
|
44
43
|
v-else-if="
|
|
45
44
|
options[option.index].testimonials &&
|
|
46
|
-
|
|
47
|
-
maxTestimonialLength
|
|
45
|
+
options[option.index].testimonials.length >= maxTestimonialLength
|
|
48
46
|
"
|
|
49
47
|
class="tw-text-error-500 tw-text-sm"
|
|
50
48
|
>
|
|
@@ -129,7 +127,7 @@
|
|
|
129
127
|
v-if="
|
|
130
128
|
options[option.index] &&
|
|
131
129
|
options[option.index].name &&
|
|
132
|
-
|
|
130
|
+
options[option.index].name.length >= maxTitleLength
|
|
133
131
|
"
|
|
134
132
|
class="tw-text-error-500 tw-text-sm"
|
|
135
133
|
>
|
|
@@ -160,7 +158,7 @@
|
|
|
160
158
|
v-if="
|
|
161
159
|
options[option.index] &&
|
|
162
160
|
options[option.index].position &&
|
|
163
|
-
|
|
161
|
+
options[option.index].position.length >= maxTitleLength
|
|
164
162
|
"
|
|
165
163
|
class="tw-text-error-500 tw-text-sm"
|
|
166
164
|
>
|
|
@@ -97,10 +97,10 @@
|
|
|
97
97
|
:attrs="{
|
|
98
98
|
'data-test-tag': 'image-title-text-input',
|
|
99
99
|
}"
|
|
100
|
-
:error="
|
|
100
|
+
:error="options[option.index].title.length >= titleLength"
|
|
101
101
|
/>
|
|
102
102
|
<p
|
|
103
|
-
v-if="
|
|
103
|
+
v-if="options[option.index].title.length >= titleLength"
|
|
104
104
|
class="tw-text-error-500 tw-text-sm tw-mt-2"
|
|
105
105
|
>
|
|
106
106
|
{{ maxLengthError }}
|
|
@@ -134,9 +134,7 @@
|
|
|
134
134
|
:attrs="{
|
|
135
135
|
'data-test-tag': 'image-description-text-input',
|
|
136
136
|
}"
|
|
137
|
-
:error="
|
|
138
|
-
rteTextLength(options[option.index].paragraph) >= descriptionLength
|
|
139
|
-
"
|
|
137
|
+
:error="options[option.index].paragraph.length >= descriptionLength"
|
|
140
138
|
@update:model-value="onTextAreaTextChange($event, option.index)"
|
|
141
139
|
/>
|
|
142
140
|
<PersonalisationOverrideField
|
|
@@ -313,7 +311,6 @@ import debounce from 'lodash.debounce';
|
|
|
313
311
|
import type { UpdateItemValuePayload } from '../../models';
|
|
314
312
|
import cloneDeep from 'lodash.clonedeep';
|
|
315
313
|
import { isURL } from '../../../../utils/validator.util';
|
|
316
|
-
import { getRteTextLength as rteTextLength } from '@brand-page-maker/utils';
|
|
317
314
|
|
|
318
315
|
const DEBOUNCE_IN_MS = 250;
|
|
319
316
|
|
|
@@ -3,7 +3,6 @@ import type { PropType } from 'vue';
|
|
|
3
3
|
import type {
|
|
4
4
|
Content,
|
|
5
5
|
LogoTheme,
|
|
6
|
-
RichTextValue,
|
|
7
6
|
TextItem,
|
|
8
7
|
TextStyleDeclaration,
|
|
9
8
|
} from '@brand-page/models';
|
|
@@ -20,7 +19,7 @@ import type { UpdateItemValuePayload } from '../../models';
|
|
|
20
19
|
import cloneDeep from 'lodash.clonedeep';
|
|
21
20
|
import { decodeHtmlEntities, escapeContentText } from '@brand-page/utils';
|
|
22
21
|
import { useSnapshotStore } from '@brand-page-maker/stores';
|
|
23
|
-
import { getPlainTextFromRTE
|
|
22
|
+
import { getPlainTextFromRTE } from '@brand-page-maker/utils';
|
|
24
23
|
import { isDefaultContentText } from '../../utils';
|
|
25
24
|
|
|
26
25
|
const DEBOUNCE_IN_MS = 250;
|
|
@@ -235,10 +234,6 @@ export default defineComponent({
|
|
|
235
234
|
this.emitChange();
|
|
236
235
|
},
|
|
237
236
|
|
|
238
|
-
rteTextLength(value: RichTextValue): number {
|
|
239
|
-
return getRteTextLength(value);
|
|
240
|
-
},
|
|
241
|
-
|
|
242
237
|
getPlainTextValue(index: number): string {
|
|
243
238
|
const text = this.options[index]?.text || '';
|
|
244
239
|
const plainText = getPlainTextFromRTE(text);
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import type { BrandPageModel } from '../brand-page/models';
|
|
3
|
+
import { minifyBrandPageMetadata } from './minify-brand-page-metadata';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The AI builder can now create/update `support-me` sections, so the minified
|
|
7
|
+
* model it reads on update must surface the fields it can edit — otherwise it
|
|
8
|
+
* can't see the current `displayStyle` / `successMessage` when deciding what to
|
|
9
|
+
* change. `request-payment` (not AI-editable) must keep its original shape.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const makeModel = (contents: unknown[]): BrandPageModel =>
|
|
13
|
+
({
|
|
14
|
+
brandPageType: 'brandSite',
|
|
15
|
+
v: 1,
|
|
16
|
+
styles: {},
|
|
17
|
+
nav: { routes: [{ pageId: 'page-1', pageSlug: 'home' }] },
|
|
18
|
+
pages: [{ id: 'page-1', title: 'Home', contents }],
|
|
19
|
+
}) as unknown as BrandPageModel;
|
|
20
|
+
|
|
21
|
+
const supportMe = {
|
|
22
|
+
id: 'sm-1',
|
|
23
|
+
type: 'support-me',
|
|
24
|
+
title: 'Buy me a coffee',
|
|
25
|
+
description: 'Support my work',
|
|
26
|
+
successMessage: 'Thanks so much!',
|
|
27
|
+
displayStyle: 'collapsed',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const requestPayment = {
|
|
31
|
+
id: 'rp-1',
|
|
32
|
+
type: 'request-payment',
|
|
33
|
+
title: 'Pay now',
|
|
34
|
+
description: 'Invoice',
|
|
35
|
+
successMessage: 'Paid',
|
|
36
|
+
displayStyle: 'preview',
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
describe('minifyBrandPageMetadata — support-me', () => {
|
|
40
|
+
it('surfaces title, description, successMessage and displayStyle', () => {
|
|
41
|
+
const out = minifyBrandPageMetadata(makeModel([supportMe]));
|
|
42
|
+
const minified = out.pages[0].contents[0];
|
|
43
|
+
|
|
44
|
+
expect(minified.type).toBe('support-me');
|
|
45
|
+
expect(minified.title).toBe('Buy me a coffee');
|
|
46
|
+
expect(minified.description).toBe('Support my work');
|
|
47
|
+
expect(minified.successMessage).toBe('Thanks so much!');
|
|
48
|
+
expect(minified.displayStyle).toBe('collapsed');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('leaves request-payment shape unchanged (no successMessage / displayStyle)', () => {
|
|
52
|
+
const out = minifyBrandPageMetadata(makeModel([requestPayment]));
|
|
53
|
+
const minified = out.pages[0].contents[0];
|
|
54
|
+
|
|
55
|
+
expect(minified.type).toBe('request-payment');
|
|
56
|
+
expect(minified.title).toBe('Pay now');
|
|
57
|
+
expect(minified.description).toBe('Invoice');
|
|
58
|
+
expect(minified.successMessage).toBeUndefined();
|
|
59
|
+
expect(minified.displayStyle).toBeUndefined();
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from '../brand-page/models';
|
|
11
11
|
import type { Content, ContentType } from '../brand-page/models/content.model';
|
|
12
12
|
import type { TextStyleDeclaration } from '../brand-page/models/styles.model';
|
|
13
|
-
import { hasRTEContent
|
|
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
16
|
import {
|
|
@@ -106,9 +106,6 @@ const convertText = (
|
|
|
106
106
|
value: unknown,
|
|
107
107
|
editor: Editor,
|
|
108
108
|
): RichTextValue | undefined => {
|
|
109
|
-
// Already Tiptap JSON (BP-5787 storage) — keep as-is.
|
|
110
|
-
if (isTiptapDocument(value)) return value;
|
|
111
|
-
|
|
112
109
|
if (typeof value !== 'string' || !value) return undefined;
|
|
113
110
|
|
|
114
111
|
if (hasRTEContent(value)) {
|
|
@@ -510,11 +507,19 @@ const minifyContent = (
|
|
|
510
507
|
return base;
|
|
511
508
|
|
|
512
509
|
case 'request-payment':
|
|
510
|
+
return {
|
|
511
|
+
...base,
|
|
512
|
+
...st('title', c.title, editor),
|
|
513
|
+
...st('description', c.description, editor),
|
|
514
|
+
};
|
|
515
|
+
|
|
513
516
|
case 'support-me':
|
|
514
517
|
return {
|
|
515
518
|
...base,
|
|
516
519
|
...st('title', c.title, editor),
|
|
517
520
|
...st('description', c.description, editor),
|
|
521
|
+
...st('successMessage', c.successMessage, editor),
|
|
522
|
+
...(includeString(c.displayStyle) && { displayStyle: c.displayStyle }),
|
|
518
523
|
};
|
|
519
524
|
|
|
520
525
|
default:
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { hasRTEContent, toRteJson, resolveHtmlParser, } from '../../../brand-page-maker/utils';
|
|
2
|
-
/**
|
|
3
|
-
* BP-5787 — migrate existing inline-HTML RTE content to Tiptap JSON.
|
|
4
|
-
*
|
|
5
|
-
* The editor emits, and the renderer reads, Tiptap JSON. Existing
|
|
6
|
-
* published/saved sites still hold RTE fields as HTML strings. This converts
|
|
7
|
-
* them on load, in EVERY environment:
|
|
8
|
-
* - maker (browser): the loaded model is JSON, so the next save persists JSON.
|
|
9
|
-
* - web-renderer (SSR / server API routes): converted on read, so old sites
|
|
10
|
-
* are normalised without a re-save and downstream code can treat RTE as JSON.
|
|
11
|
-
*
|
|
12
|
-
* This is a one-time, versioned migration (runAlways is NOT set): legacy sites
|
|
13
|
-
* are at model version < 20 and convert on first load in any environment; once
|
|
14
|
-
* `v` is stamped to 20 the conversion is skipped. This is only safe because the
|
|
15
|
-
* AI section builders now emit Tiptap JSON directly (they no longer write HTML),
|
|
16
|
-
* so there is no recurring post-load source of HTML to normalise.
|
|
17
|
-
*
|
|
18
|
-
* HTML→JSON parsing needs a DOM parser. Rather than mutate a global `window`,
|
|
19
|
-
* the parser is resolved once (native `DOMParser` in the browser, a jsdom
|
|
20
|
-
* `DOMParser` instance on the server) and threaded explicitly through the
|
|
21
|
-
* conversion — see resolveHtmlParser / toRteJson. No global state is touched, so
|
|
22
|
-
* the pass is safe on any server runtime and under concurrent requests.
|
|
23
|
-
*
|
|
24
|
-
* Scope: only HTML-RTE strings (hasRTEContent) are converted. Plain strings,
|
|
25
|
-
* numbers, and already-JSON docs are left untouched, so the migration is
|
|
26
|
-
* idempotent (re-running on JSON content is a no-op — non-string values are
|
|
27
|
-
* skipped). Nested RTE inside item arrays (textItems, testimonials, images…)
|
|
28
|
-
* is handled by recursing into arrays; style objects are intentionally not
|
|
29
|
-
* descended into (they never hold RTE content).
|
|
30
|
-
*/
|
|
31
|
-
const convertRteStringsToJson = (value, parser) => {
|
|
32
|
-
if (Array.isArray(value)) {
|
|
33
|
-
value.forEach((item) => convertRteStringsToJson(item, parser));
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
if (!value || typeof value !== 'object')
|
|
37
|
-
return;
|
|
38
|
-
const record = value;
|
|
39
|
-
Object.keys(record).forEach((key) => {
|
|
40
|
-
const field = record[key];
|
|
41
|
-
if (typeof field === 'string' && hasRTEContent(field)) {
|
|
42
|
-
const json = toRteJson(field, parser);
|
|
43
|
-
if (json)
|
|
44
|
-
record[key] = json;
|
|
45
|
-
}
|
|
46
|
-
else if (Array.isArray(field)) {
|
|
47
|
-
convertRteStringsToJson(field, parser);
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
};
|
|
51
|
-
const convertModel = (brandPageModel, parser) => {
|
|
52
|
-
for (const page of brandPageModel.pages) {
|
|
53
|
-
for (const content of page.contents) {
|
|
54
|
-
convertRteStringsToJson(content, parser);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
export const migration20 = async (migrationResult) => {
|
|
59
|
-
const { brandPageModel } = migrationResult;
|
|
60
|
-
// Resolve a DOM parser for this environment (native in the browser, a jsdom
|
|
61
|
-
// instance on the server — never touching globals), then convert every
|
|
62
|
-
// HTML-RTE field to Tiptap JSON with it.
|
|
63
|
-
const parser = await resolveHtmlParser();
|
|
64
|
-
convertModel(brandPageModel, parser);
|
|
65
|
-
return migrationResult;
|
|
66
|
-
};
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* BP-5787 — golden-master fixture for the RTE HTML→JSON migration.
|
|
3
|
-
*
|
|
4
|
-
* A single rich-text value that exercises every mark/node the editor can emit,
|
|
5
|
-
* so one snapshot guards the whole serialisation surface. Captured as a baseline
|
|
6
|
-
* on the current HTML pipeline; after the migration the JSON pipeline must
|
|
7
|
-
* reproduce byte-identical rendered output (see rte-render-parity.test.ts).
|
|
8
|
-
*
|
|
9
|
-
* Coverage:
|
|
10
|
-
* - block nodes: heading (h2), paragraph, text-align (center / right)
|
|
11
|
-
* - inline marks: bold, italic, underline, strike
|
|
12
|
-
* - textStyle attrs: fontFamily, fontSize (em), letterSpacing (em), lineHeight (unitless)
|
|
13
|
-
* - colour: theme TOKEN ref (var(--logo-text-color)) AND a literal hex (#FF0000)
|
|
14
|
-
* - highlight (background colour via <mark>)
|
|
15
|
-
* - links: internal (target=_self) and external (target=_blank rel=noopener)
|
|
16
|
-
*/
|
|
17
|
-
export declare const COMPLEX_RTE_HTML = "<h2 style=\"text-align: center\"><span style=\"color: var(--logo-text-color)\">Themed heading</span></h2><p><strong>bold</strong> <em>italic</em> <u>underline</u> <s>strike</s> <span style=\"font-family: Montserrat; font-size: 1.5em; letter-spacing: 0.05em; line-height: 1.4\">styled span</span> <mark data-color=\"#ffe08a\" style=\"background-color: #ffe08a\">highlight</mark> <span style=\"color: #FF0000\">literal hex</span> <a target=\"_self\" href=\"/about\">internal link</a> <a target=\"_blank\" rel=\"noopener noreferrer nofollow\" href=\"https://example.com\">external link</a></p><p style=\"text-align: right\">second paragraph, right-aligned</p>";
|
|
18
|
-
/**
|
|
19
|
-
* The theme-token CSS variable that MUST survive the HTML→JSON round-trip
|
|
20
|
-
* (BP-5787 stores token refs instead of flattening them to rgb()).
|
|
21
|
-
*/
|
|
22
|
-
export declare const THEME_TOKEN_REF = "var(--logo-text-color)";
|