@designcrowd/library.brand-page 6.0.25 → 6.0.26-json-prereq-2
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-CS3ct0wa.js → CreatedOnBrandCrowd-de-de-CKBqjDH3.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-en-us-CmBcnDu6.js → CreatedOnBrandCrowd-en-us-CRXiimvT.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-es-es-BdTuJtqV.js → CreatedOnBrandCrowd-es-es-DCYdY6sj.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-fr-Cx_H9X6l.js → CreatedOnBrandCrowd-fr-ca-Dk7LOhxE.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-ca-Cx_H9X6l.js → CreatedOnBrandCrowd-fr-fr-Dk7LOhxE.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-pt-BZZ9hjUi.js → CreatedOnBrandCrowd-pt-br-Cp4ufHJP.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-br-BZZ9hjUi.js → CreatedOnBrandCrowd-pt-pt-Cp4ufHJP.js} +1 -1
- package/lib/src/{CreatedOnDcom-de-de-SbONDQN1.js → CreatedOnDcom-de-de-ZSLkWMwM.js} +1 -1
- package/lib/src/{CreatedOnDcom-en-us-Beno1_gw.js → CreatedOnDcom-en-us-BgdIGfT3.js} +1 -1
- package/lib/src/{CreatedOnDcom-es-es-BBE_zdRu.js → CreatedOnDcom-es-es-D0KemkCZ.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-ca-C6pgDB5x.js → CreatedOnDcom-fr-ca-BuwNjNbC.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-fr-FD1eqtg3.js → CreatedOnDcom-fr-fr-DCQcsFPf.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-br-BiSx3OwM.js → CreatedOnDcom-pt-br-DD-oLAS1.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-pt-Dn4DmKGA.js → CreatedOnDcom-pt-pt-D8qLq2wW.js} +1 -1
- package/lib/src/brand-page/components/contact-form/ContactForm.mixin.js +5 -2
- package/lib/src/brand-page/models/index.d.ts +34 -26
- package/lib/src/brand-page/utils/migration/migration-20.d.ts +2 -0
- package/lib/src/brand-page/utils/migration/migration-20.js +66 -0
- package/lib/src/brand-page/utils/migration.util.js +11 -0
- package/lib/src/brand-page/utils/text.util.d.ts +1 -1
- package/lib/src/brand-page/utils/text.util.js +32 -0
- package/lib/src/brand-page/utils/vcard.util.js +4 -4
- package/lib/src/brand-page/utils/view-model.util.d.ts +1 -0
- package/lib/src/brand-page/utils/view-model.util.js +68 -20
- package/lib/src/brand-page/view-models.d.ts +3 -3
- package/lib/src/brand-page-maker/__fixtures__/complex-rte.fixture.d.ts +22 -0
- package/lib/src/brand-page-maker/__fixtures__/complex-rte.fixture.js +22 -0
- package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +1 -1
- package/lib/src/brand-page-maker/components/seo-settings/SeoPageSettings.mixin.d.ts +1 -1
- package/lib/src/brand-page-maker/composables/ai-chat/useAiMetadataBuilder.d.ts +0 -1
- package/lib/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.js +21 -9
- package/lib/src/brand-page-maker/composables/rich-text-editor/bubbleMenu.d.ts +2 -9
- package/lib/src/brand-page-maker/composables/rich-text-editor/bubbleMenu.js +6 -10
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.d.ts +14 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.js +39 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.d.ts +28 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.js +90 -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 +24 -103
- package/lib/src/brand-page-maker/stores/ai-thread/ai-website-builder-thread-store.d.ts +8 -2
- package/lib/src/brand-page-maker/stores/ai-thread/ai-website-builder-thread-store.js +20 -0
- package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.js +7 -0
- package/lib/src/brand-page-maker/utils/index.d.ts +2 -0
- package/lib/src/brand-page-maker/utils/index.js +2 -0
- package/lib/src/brand-page-maker/utils/rich-text-editor.util.d.ts +28 -5
- package/lib/src/brand-page-maker/utils/rich-text-editor.util.js +48 -5
- package/lib/src/brand-page-maker/utils/rte-render.util.d.ts +63 -0
- package/lib/src/brand-page-maker/utils/rte-render.util.js +95 -0
- package/lib/src/brand-page-maker/utils/rte-sanitize.util.d.ts +6 -0
- package/lib/src/brand-page-maker/utils/rte-sanitize.util.js +158 -0
- package/lib/src/brand-page-maker/validators/field.validator.js +10 -2
- 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 +1 -0
- package/lib/src/index.mjs +143 -138
- package/lib/src/{lib-BVHFYSLL.js → lib-Db6j9fQZ.js} +56262 -52768
- package/lib/src/lib.js +2 -0
- package/lib/src/metadata-builder/ai-metadata-executor.d.ts +0 -1
- package/lib/src/metadata-builder/ai-metadata-executor.js +0 -19
- package/lib/src/metadata-builder/builders/section-builders/about-section-builder.js +2 -3
- package/lib/src/metadata-builder/builders/section-builders/banner-section-builder.js +2 -3
- package/lib/src/metadata-builder/builders/section-builders/booking-section-builder.js +2 -3
- package/lib/src/metadata-builder/builders/section-builders/contact-form-builder.js +3 -4
- package/lib/src/metadata-builder/builders/section-builders/images-section-builder.js +2 -3
- package/lib/src/metadata-builder/builders/section-builders/mailing-list-builder.js +1 -2
- package/lib/src/metadata-builder/builders/section-builders/mailing-list-section-builder.js +1 -2
- package/lib/src/metadata-builder/builders/section-builders/profile-section-builder.js +3 -4
- package/lib/src/metadata-builder/builders/section-builders/shapes-section-builder.js +2 -3
- package/lib/src/metadata-builder/builders/section-builders/testimonial-section-builder.js +1 -2
- package/lib/src/metadata-builder/builders/section-builders/text-section-builder.js +15 -11
- package/lib/src/metadata-builder/helpers/rte.helpers.d.ts +11 -13
- package/lib/src/metadata-builder/helpers/rte.helpers.js +14 -73
- package/lib/src/metadata-builder/metadata-executor.js +0 -4
- package/lib/src/metadata-builder/models.d.ts +0 -2
- package/lib/src/metadata-builder/models.js +0 -2
- package/lib/src/metadata-builder/parsers/ai-toolcall-parser.d.ts +0 -19
- package/lib/src/metadata-builder/parsers/ai-toolcall-parser.js +0 -4
- package/lib/src/metadata-builder/sanitizers/rte-schema-check.d.ts +7 -8
- package/lib/src/metadata-builder/sanitizers/rte-schema-check.js +21 -52
- package/lib/src/shared/content-settings/components/GenericTextInput.mixin.d.ts +2 -2
- package/lib/src/shared/content-settings/components/GenericTextInput.mixin.js +14 -1
- package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.d.ts +1 -1
- package/lib/src/shared/content-settings/components/content-settings/ReplaceImageSettingsForContent.mixin.d.ts +1 -1
- package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.js +12 -3
- package/lib/src/shared/content-settings/components/email-field/EmailField.mixin.d.ts +2 -2
- package/lib/src/shared/content-settings/components/radio-field/RadioField.mixin.d.ts +1 -1
- package/lib/src/shared/content-settings/components/slider-field/SliderField.mixin.d.ts +1 -1
- package/lib/src/shared/content-settings/components/tel-field/TelField.mixin.d.ts +2 -2
- package/lib/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.mixin.d.ts +3 -2
- package/lib/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.mixin.js +10 -3
- package/lib/src/shared/content-settings/components/text-field/TextField.mixin.d.ts +2 -2
- package/lib/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.d.ts +2 -1
- package/lib/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.js +4 -1
- package/lib/src/shared/content-settings/components/textarea-field/TextareaField.mixin.d.ts +2 -2
- package/lib/src/shared/content-settings/components/url-field/UrlField.mixin.d.ts +2 -2
- package/lib/src/shared/content-settings/components/youtube-video-field/YoutubeVideoField.mixin.d.ts +2 -2
- package/lib/src/shared/side-bar/components/SideBar.mixin.d.ts +1 -1
- package/lib/src/utils/minify-brand-page-metadata.js +4 -1
- package/package.json +17 -15
- package/src/brand-page/components/contact-form/ContactForm.mixin.ts +5 -2
- package/src/brand-page/components/text-wrapper/TextWrapper.vue +17 -6
- package/src/brand-page/models/index.ts +35 -26
- package/src/brand-page/utils/__snapshots__/migration.util.test.ts.snap +3 -3
- package/src/brand-page/utils/migration/migration-20.server.test.ts +125 -0
- package/src/brand-page/utils/migration/migration-20.test.ts +72 -0
- package/src/brand-page/utils/migration/migration-20.ts +87 -0
- package/src/brand-page/utils/migration.util.ts +12 -0
- package/src/brand-page/utils/style.util.test.ts +51 -0
- package/src/brand-page/utils/text.util.test.ts +47 -0
- package/src/brand-page/utils/text.util.ts +43 -4
- package/src/brand-page/utils/vcard.util.ts +6 -4
- package/src/brand-page/utils/view-model.pageid-rte.test.ts +104 -0
- package/src/brand-page/utils/view-model.util.ts +94 -26
- package/src/brand-page/view-models.ts +3 -2
- package/src/brand-page-maker/__fixtures__/complex-rte.fixture.ts +23 -0
- package/src/brand-page-maker/components/rich-text-editor/RichTextEditor.vue +4 -3
- package/src/brand-page-maker/composables/ai-chat/useAiMetadataBuilder.ts +0 -1
- package/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.ts +28 -9
- package/src/brand-page-maker/composables/rich-text-editor/bubbleMenu.ts +6 -10
- package/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.test.ts +52 -0
- package/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.ts +42 -0
- package/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.ts +137 -0
- package/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.ts +27 -111
- package/src/brand-page-maker/stores/ai-thread/ai-website-builder-thread-store.ts +23 -0
- package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.ts +8 -0
- package/src/brand-page-maker/utils/__snapshots__/rte-render-parity.test.ts.snap +202 -0
- package/src/brand-page-maker/utils/index.ts +2 -0
- package/src/brand-page-maker/utils/rich-text-editor.util.test.ts +37 -3
- package/src/brand-page-maker/utils/rich-text-editor.util.ts +66 -11
- package/src/brand-page-maker/utils/rte-features.test.ts +351 -0
- package/src/brand-page-maker/utils/rte-render-parity.test.ts +157 -0
- package/src/brand-page-maker/utils/rte-render.util.ts +165 -0
- package/src/brand-page-maker/utils/rte-sanitize-json.test.ts +158 -0
- package/src/brand-page-maker/utils/rte-sanitize.util.ts +195 -0
- package/src/brand-page-maker/validators/field.validator.test.ts +55 -0
- package/src/brand-page-maker/validators/field.validator.ts +10 -2
- package/src/lib.ts +9 -0
- package/src/metadata-builder/ai-metadata-executor.ts +0 -26
- package/src/metadata-builder/builders/section-builders/about-section-builder.ts +2 -3
- package/src/metadata-builder/builders/section-builders/banner-section-builder.ts +2 -3
- package/src/metadata-builder/builders/section-builders/booking-section-builder.ts +2 -5
- package/src/metadata-builder/builders/section-builders/contact-form-builder.ts +3 -4
- package/src/metadata-builder/builders/section-builders/images-section-builder.ts +2 -3
- package/src/metadata-builder/builders/section-builders/mailing-list-builder.ts +1 -2
- package/src/metadata-builder/builders/section-builders/mailing-list-section-builder.ts +1 -2
- package/src/metadata-builder/builders/section-builders/profile-section-builder.ts +3 -6
- package/src/metadata-builder/builders/section-builders/shapes-section-builder.ts +2 -3
- package/src/metadata-builder/builders/section-builders/testimonial-section-builder.ts +1 -2
- package/src/metadata-builder/builders/section-builders/text-section-builder.ts +19 -13
- package/src/metadata-builder/helpers/rte.helpers.ts +15 -101
- package/src/metadata-builder/metadata-executor.ts +0 -5
- package/src/metadata-builder/models.ts +0 -3
- package/src/metadata-builder/parsers/ai-toolcall-parser.ts +0 -27
- package/src/metadata-builder/sanitizers/rte-schema-check.ts +24 -53
- package/src/shared/content-settings/components/GenericTextInput.mixin.ts +17 -1
- package/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.ts +16 -3
- package/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.mixin.ts +13 -4
- package/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.vue +6 -4
- package/src/shared/content-settings/components/text-image-field/TextImageField.vue +6 -3
- package/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.ts +6 -1
- package/src/shared/content-settings/components/text-items-field/TextItemsField.vue +1 -1
- package/src/utils/minify-brand-page-metadata.ts +4 -1
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontSize.d.ts +0 -20
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontSize.js +0 -44
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/LineHeight.d.ts +0 -27
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/LineHeight.js +0 -44
- package/lib/src/metadata-builder/builders/update-content-text-builder.d.ts +0 -17
- package/lib/src/metadata-builder/builders/update-content-text-builder.js +0 -103
- package/lib/src/metadata-builder/builders/update-content-text-style-builder.d.ts +0 -16
- package/lib/src/metadata-builder/builders/update-content-text-style-builder.js +0 -216
- package/src/brand-page-maker/composables/rich-text-editor/extensions/FontSize.ts +0 -73
- package/src/brand-page-maker/composables/rich-text-editor/extensions/LineHeight.ts +0 -85
- package/src/metadata-builder/builders/update-content-text-builder.ts +0 -148
- package/src/metadata-builder/builders/update-content-text-style-builder.ts +0 -352
|
@@ -6,7 +6,7 @@ import brandPageClient from '../../clients/brand-page.client';
|
|
|
6
6
|
import { isEmail } from '../../../utils/validator.util';
|
|
7
7
|
import { ALIGN_CARD_CENTER, ALIGN_CARD_CENTER_IMAGE, ALIGN_CENTERED, ALIGN_LEFT_FULL_IMAGE, ALIGN_RIGHT_FULL_IMAGE, DISPLAY_STYLE_COLLAPSED, RECAPTCHA_ACTION_CONTACT_FORM, TEXT_ONLY, } from '../../constants';
|
|
8
8
|
import { getRenderText, contentPaddingStyle as toContentPaddingStyle, mutedTextForBackground, backgroundColorFromStyle, } from '@brand-page/utils';
|
|
9
|
-
import { hasRTEContent } from '@brand-page-maker/utils';
|
|
9
|
+
import { hasRTEContent, getPlainTextFromRTE } from '@brand-page-maker/utils';
|
|
10
10
|
import { useReCaptcha } from '../../composables/recaptcha.composable';
|
|
11
11
|
export default defineComponent({
|
|
12
12
|
props: {
|
|
@@ -120,7 +120,10 @@ export default defineComponent({
|
|
|
120
120
|
isEmail(this.viewModel.responseEmail));
|
|
121
121
|
},
|
|
122
122
|
title() {
|
|
123
|
-
|
|
123
|
+
// BP-5787: title may be RTE JSON (not a string), so `.trim()` would
|
|
124
|
+
// throw — read the visible text via getPlainTextFromRTE to decide
|
|
125
|
+
// whether to fall back to the default.
|
|
126
|
+
return getPlainTextFromRTE(this.viewModel.title)
|
|
124
127
|
? this.viewModel.title
|
|
125
128
|
: 'Contact Us';
|
|
126
129
|
},
|
|
@@ -7,6 +7,14 @@ import { TextStyles } from './styles.model';
|
|
|
7
7
|
import { LogoStyles } from './logo.model';
|
|
8
8
|
import type { LogoImageModel, LogoTemplateImageModel } from './logo.model';
|
|
9
9
|
import type { BrandPagePaymentConfig } from './payment.model';
|
|
10
|
+
import type { JSONContent } from '@tiptap/vue-3';
|
|
11
|
+
/**
|
|
12
|
+
* BP-5787 — a rich-text field is either a legacy inline-HTML string or a
|
|
13
|
+
* Tiptap JSON document. Typing RTE content fields as RichTextValue (instead of
|
|
14
|
+
* `string`) forces every consumer to handle both forms — surfacing at compile
|
|
15
|
+
* time any code that assumes a plain string (e.g. `.replace`, `.length`).
|
|
16
|
+
*/
|
|
17
|
+
export type RichTextValue = string | JSONContent;
|
|
10
18
|
export * from './brand-page-context.model';
|
|
11
19
|
export * from './payment.model';
|
|
12
20
|
export * from './content.model';
|
|
@@ -148,7 +156,7 @@ export interface TextContent extends Content {
|
|
|
148
156
|
/**
|
|
149
157
|
* For BrandPage/BrandContact only
|
|
150
158
|
*/
|
|
151
|
-
text:
|
|
159
|
+
text: RichTextValue;
|
|
152
160
|
/**
|
|
153
161
|
* For BrandSite
|
|
154
162
|
*/
|
|
@@ -171,9 +179,9 @@ export interface TextContent extends Content {
|
|
|
171
179
|
}
|
|
172
180
|
export interface ProfileContent extends Content {
|
|
173
181
|
avatarImageUrl: string;
|
|
174
|
-
name:
|
|
175
|
-
title:
|
|
176
|
-
description:
|
|
182
|
+
name: RichTextValue;
|
|
183
|
+
title: RichTextValue;
|
|
184
|
+
description: RichTextValue;
|
|
177
185
|
backgroundColor: BrandPageCssStyle;
|
|
178
186
|
colorReplacements: ColorReplacement[];
|
|
179
187
|
mediaType?: string;
|
|
@@ -191,7 +199,7 @@ export interface MapContent extends Content {
|
|
|
191
199
|
}
|
|
192
200
|
export interface MailingListContent extends Content {
|
|
193
201
|
acceptedFields?: 'emailOnly' | 'nameAndEmail';
|
|
194
|
-
title:
|
|
202
|
+
title: RichTextValue;
|
|
195
203
|
buttonText: string;
|
|
196
204
|
titleStyle: TextStyleDeclaration;
|
|
197
205
|
buttonTextStyle: TextStyleDeclaration;
|
|
@@ -243,10 +251,10 @@ export interface FormField {
|
|
|
243
251
|
export interface ContactFormContent extends Content {
|
|
244
252
|
type: 'contact-form';
|
|
245
253
|
displayStyle: string;
|
|
246
|
-
title:
|
|
247
|
-
description:
|
|
254
|
+
title: RichTextValue;
|
|
255
|
+
description: RichTextValue;
|
|
248
256
|
buttonText: string;
|
|
249
|
-
successMessage:
|
|
257
|
+
successMessage: RichTextValue;
|
|
250
258
|
fields: FormField[];
|
|
251
259
|
responseEmail: string;
|
|
252
260
|
imageUrl?: string;
|
|
@@ -265,8 +273,8 @@ export interface ContactFormContent extends Content {
|
|
|
265
273
|
export interface BannerSectionContent extends Content {
|
|
266
274
|
type: 'banner-section';
|
|
267
275
|
imageUrl: string;
|
|
268
|
-
title:
|
|
269
|
-
description:
|
|
276
|
+
title: RichTextValue;
|
|
277
|
+
description: RichTextValue;
|
|
270
278
|
buttonText: string | false;
|
|
271
279
|
url: string;
|
|
272
280
|
email?: string;
|
|
@@ -293,8 +301,8 @@ export declare const MEDIA_TYPES: {
|
|
|
293
301
|
};
|
|
294
302
|
export type MediaType = keyof typeof MEDIA_TYPES;
|
|
295
303
|
export interface TextMediaItem extends ImageItem {
|
|
296
|
-
title:
|
|
297
|
-
paragraph:
|
|
304
|
+
title: RichTextValue;
|
|
305
|
+
paragraph: RichTextValue;
|
|
298
306
|
itemsId: number;
|
|
299
307
|
buttonText?: string | false;
|
|
300
308
|
buttonPageId?: string;
|
|
@@ -313,8 +321,8 @@ export interface TextShapeItem extends TextMediaItem {
|
|
|
313
321
|
export interface ShapeItem extends ImageItem {
|
|
314
322
|
colorReplacements: ColorReplacement[];
|
|
315
323
|
mediaType: MediaType;
|
|
316
|
-
title?:
|
|
317
|
-
paragraph?:
|
|
324
|
+
title?: RichTextValue;
|
|
325
|
+
paragraph?: RichTextValue;
|
|
318
326
|
titleOverride?: string;
|
|
319
327
|
paragraphOverride?: string;
|
|
320
328
|
}
|
|
@@ -322,9 +330,9 @@ export interface TestimonialItems {
|
|
|
322
330
|
/** Stable per-item identifier. See ImageItem.id. */
|
|
323
331
|
id: string;
|
|
324
332
|
imageUrl: string;
|
|
325
|
-
name:
|
|
326
|
-
position:
|
|
327
|
-
testimonials:
|
|
333
|
+
name: RichTextValue;
|
|
334
|
+
position: RichTextValue;
|
|
335
|
+
testimonials: RichTextValue;
|
|
328
336
|
colorReplacements: ColorReplacement[];
|
|
329
337
|
mediaType: string;
|
|
330
338
|
size?: string;
|
|
@@ -337,7 +345,7 @@ export interface TestimonialItems {
|
|
|
337
345
|
export interface TextItem {
|
|
338
346
|
/** Stable per-item identifier. See ImageItem.id. */
|
|
339
347
|
id: string;
|
|
340
|
-
text:
|
|
348
|
+
text: RichTextValue;
|
|
341
349
|
textStyle: TextStyleDeclaration;
|
|
342
350
|
textOverride?: string;
|
|
343
351
|
}
|
|
@@ -354,8 +362,8 @@ export interface ImagesSectionContent extends Content, MediaSection {
|
|
|
354
362
|
enablePopup: boolean;
|
|
355
363
|
}
|
|
356
364
|
export interface MediaSection extends Content {
|
|
357
|
-
title:
|
|
358
|
-
description:
|
|
365
|
+
title: RichTextValue;
|
|
366
|
+
description: RichTextValue;
|
|
359
367
|
displayStyle: ImagesSectionContentDisplayStyle;
|
|
360
368
|
items: TextShapeItem[] | TextMediaItem[];
|
|
361
369
|
titleStyle: TextStyleDeclaration;
|
|
@@ -372,7 +380,7 @@ export interface ColorReplacement {
|
|
|
372
380
|
}
|
|
373
381
|
export interface TestimonialSectionContent extends Content {
|
|
374
382
|
type: 'testimonial-section';
|
|
375
|
-
title:
|
|
383
|
+
title: RichTextValue;
|
|
376
384
|
displayStyle: string;
|
|
377
385
|
items: TestimonialItems[];
|
|
378
386
|
titleStyle: TextStyleDeclaration;
|
|
@@ -386,8 +394,8 @@ export interface TestimonialSectionContent extends Content {
|
|
|
386
394
|
export interface AboutSectionContent extends Content {
|
|
387
395
|
type: 'about-section';
|
|
388
396
|
imageUrl: string;
|
|
389
|
-
title:
|
|
390
|
-
paragraph:
|
|
397
|
+
title: RichTextValue;
|
|
398
|
+
paragraph: RichTextValue;
|
|
391
399
|
displayStyle: string;
|
|
392
400
|
titleStyle: TextStyleDeclaration;
|
|
393
401
|
paragraphStyle: TextStyleDeclaration;
|
|
@@ -411,7 +419,7 @@ export declare const SOCIAL_LINKS_ICON_DISPLAY_STYLES: {
|
|
|
411
419
|
export type SocialLinksIconDisplayStyle = keyof typeof SOCIAL_LINKS_ICON_DISPLAY_STYLES;
|
|
412
420
|
export interface SocialLinksContent extends Content {
|
|
413
421
|
type: 'social-links';
|
|
414
|
-
title:
|
|
422
|
+
title: RichTextValue;
|
|
415
423
|
links: LinksItem[];
|
|
416
424
|
iconDisplayStyle: SocialLinksIconDisplayStyle;
|
|
417
425
|
iconFillStyles: BrandPageCssStyle;
|
|
@@ -434,8 +442,8 @@ export interface DividerSectionContent extends Content {
|
|
|
434
442
|
}
|
|
435
443
|
export interface BookingSectionContent extends Content {
|
|
436
444
|
type: 'booking-section';
|
|
437
|
-
title:
|
|
438
|
-
description:
|
|
445
|
+
title: RichTextValue;
|
|
446
|
+
description: RichTextValue;
|
|
439
447
|
buttonText: string;
|
|
440
448
|
url: string;
|
|
441
449
|
email?: string;
|
|
@@ -0,0 +1,66 @@
|
|
|
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
|
+
};
|
|
@@ -22,6 +22,7 @@ import { migration16 } from './migration/migration-16';
|
|
|
22
22
|
import { migration17 } from './migration/migration-17';
|
|
23
23
|
import { migration18 } from './migration/migration-18';
|
|
24
24
|
import { migration19 } from './migration/migration-19';
|
|
25
|
+
import { migration20 } from './migration/migration-20';
|
|
25
26
|
const MIGRATION_TABLE = [
|
|
26
27
|
{
|
|
27
28
|
version: 1,
|
|
@@ -149,6 +150,16 @@ const MIGRATION_TABLE = [
|
|
|
149
150
|
runAlways: true,
|
|
150
151
|
description: 'BP-5800: seed the button fill colour as each block’s own container colour so it is independent of the site button (existing sites render unchanged) — backgroundStyle for mailing-list, request-payment and support-me (all types); cardStyle for the BP/BC contact form (skipped on brand-site)',
|
|
151
152
|
},
|
|
153
|
+
{
|
|
154
|
+
version: 20,
|
|
155
|
+
migrateFn: migration20,
|
|
156
|
+
// One-time versioned migration (NOT runAlways): legacy sites (v < 20)
|
|
157
|
+
// convert on their first load in any environment — the maker (browser DOM)
|
|
158
|
+
// and web-renderer SSR (migration-20 installs a jsdom window). It no longer
|
|
159
|
+
// needs to re-run every load because the AI section builders now emit Tiptap
|
|
160
|
+
// JSON directly, so there is no recurring post-load source of HTML to catch.
|
|
161
|
+
description: 'BP-5787: convert inline-HTML RTE content to Tiptap JSON on first load (client or server); AI builders emit JSON directly, so this runs once per site',
|
|
162
|
+
},
|
|
152
163
|
];
|
|
153
164
|
export const GetLatestModelVersion = () => Math.max(...MIGRATION_TABLE.map((x) => x.version));
|
|
154
165
|
export const migrateBrandPageModel = async (brandPageModel, options) => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare const getRenderText: (text: string) => string;
|
|
2
|
-
export declare const escapeContentText: (text:
|
|
2
|
+
export declare const escapeContentText: (text: unknown) => string | undefined;
|
|
3
3
|
export declare const decodeHtmlEntities: (text: string) => string;
|
|
4
4
|
export declare const unescapeContentText: (text: string) => string;
|
|
5
5
|
export declare const encodeContentText: (text: string | undefined) => string | undefined;
|
|
@@ -1,11 +1,43 @@
|
|
|
1
1
|
import { ESCAPED_NEWLINE_CHARACTER } from '../../constants/ui';
|
|
2
|
+
/**
|
|
3
|
+
* BP-5787 — a Tiptap JSON document is an object with `type: 'doc'`.
|
|
4
|
+
* Inlined here (rather than importing from brand-page-maker) to avoid a
|
|
5
|
+
* circular import: rich-text-editor.util imports getRenderText from this file.
|
|
6
|
+
*/
|
|
7
|
+
const isTiptapDoc = (value) => typeof value === 'object' &&
|
|
8
|
+
value !== null &&
|
|
9
|
+
value.type === 'doc';
|
|
10
|
+
const jsonDocPlainText = (doc) => {
|
|
11
|
+
const parts = [];
|
|
12
|
+
const walk = (node) => {
|
|
13
|
+
if (typeof node?.text === 'string')
|
|
14
|
+
parts.push(node.text);
|
|
15
|
+
node?.content?.forEach(walk);
|
|
16
|
+
};
|
|
17
|
+
walk(doc);
|
|
18
|
+
return parts.join(' ').replace(/\s+/g, ' ').trim();
|
|
19
|
+
};
|
|
2
20
|
export const getRenderText = (text) => {
|
|
21
|
+
// BP-5787: RTE fields may now be Tiptap JSON objects. Non-strings pass
|
|
22
|
+
// through untouched so the renderer (TextWrapper) handles them via its
|
|
23
|
+
// JSON-aware path, instead of throwing on `.replace`.
|
|
24
|
+
if (typeof text !== 'string') {
|
|
25
|
+
return text;
|
|
26
|
+
}
|
|
3
27
|
return text.replace(new RegExp(`(${ESCAPED_NEWLINE_CHARACTER}|\\n)`, 'g'), '<br/>');
|
|
4
28
|
};
|
|
29
|
+
// Accepts a legacy inline-HTML string or a Tiptap JSON document (BP-5787):
|
|
30
|
+
// for JSON it returns the concatenated visible text; for HTML it strips tags.
|
|
5
31
|
export const escapeContentText = (text) => {
|
|
6
32
|
if (!text) {
|
|
7
33
|
return text;
|
|
8
34
|
}
|
|
35
|
+
if (isTiptapDoc(text)) {
|
|
36
|
+
return jsonDocPlainText(text);
|
|
37
|
+
}
|
|
38
|
+
if (typeof text !== 'string') {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
9
41
|
return text
|
|
10
42
|
.replace(/(<([^>]+)>)/gi, '')
|
|
11
43
|
.replace(/(\r\n|\r|\n)/g, ESCAPED_NEWLINE_CHARACTER);
|
|
@@ -150,9 +150,9 @@ const processSocialLinks = (socialLinksContent, vcardLines, addedEntries) => {
|
|
|
150
150
|
});
|
|
151
151
|
};
|
|
152
152
|
const addProfileInfo = (profile, vcardLines) => {
|
|
153
|
-
const name = sanitizeForVCard(profile.name || '');
|
|
154
|
-
const title = sanitizeForVCard(profile.title || '');
|
|
155
|
-
const description = sanitizeForVCard(profile.description || '');
|
|
153
|
+
const name = sanitizeForVCard(getPlainTextFromRTE(profile.name) || '');
|
|
154
|
+
const title = sanitizeForVCard(getPlainTextFromRTE(profile.title) || '');
|
|
155
|
+
const description = sanitizeForVCard(getPlainTextFromRTE(profile.description) || '');
|
|
156
156
|
if (name) {
|
|
157
157
|
const nameParts = name.trim().split(/\s+/);
|
|
158
158
|
const lastName = nameParts.length > 1 ? nameParts.pop() || '' : '';
|
|
@@ -205,6 +205,6 @@ const downloadVCard = (vcard, filename) => {
|
|
|
205
205
|
export const saveContact = (brandPageData) => {
|
|
206
206
|
const vcard = generateVCard(brandPageData);
|
|
207
207
|
const profile = findProfileContent(brandPageData);
|
|
208
|
-
const filename = generateVCardFilename(profile?.name);
|
|
208
|
+
const filename = generateVCardFilename(getPlainTextFromRTE(profile?.name));
|
|
209
209
|
downloadVCard(vcard, filename);
|
|
210
210
|
};
|
|
@@ -28,6 +28,7 @@ interface ViewModelMappingOptions {
|
|
|
28
28
|
setVideoUrlsWithToken?: (token: string, videoUrl: string, previewImageUrl: string, assetId: string) => void;
|
|
29
29
|
}
|
|
30
30
|
export declare const getBrandPageViewModelFromBrandPageModel: (model: BrandPageModel, brandPageToken: string | undefined, showBrandCrowdLogo: boolean | undefined, options: ViewModelMappingOptions) => BrandPageViewModel;
|
|
31
|
+
export declare const resolvePageIdsInRteJson: (doc: unknown, nav: NavViewModel, options: ViewModelMappingOptions) => unknown;
|
|
31
32
|
export declare const isLogoViewModel: (viewModel: ContentViewModel) => viewModel is LogoViewModel;
|
|
32
33
|
/** for service.brand-page website preview by logo draft token */
|
|
33
34
|
export declare const applyLogoImageToBrandPageViewModel: (viewModel: BrandPageViewModel, logoImage: LogoImageModel) => BrandPageViewModel;
|
|
@@ -10,7 +10,7 @@ import { isValidSvgUrl } from './image.util';
|
|
|
10
10
|
import { isLogoContent } from './model.util';
|
|
11
11
|
import { getBoxShadowValue } from './button.util';
|
|
12
12
|
import { findNavItemByPageId, getRouteNameByPageIndex, getRouteParamsByPage, } from './route.util';
|
|
13
|
-
import { hasRTEContent } from '../../brand-page-maker/utils';
|
|
13
|
+
import { hasRTEContent, isTiptapDocument } from '../../brand-page-maker/utils';
|
|
14
14
|
import { getPageIdFromHref, isHrefPageId, } from '../../brand-page-maker/composables/rich-text-editor/hyperlink';
|
|
15
15
|
const BUTTON_BORDER_RADIUS = '9999px';
|
|
16
16
|
const CONTENT_BORDER_RADIUS = '48px';
|
|
@@ -864,13 +864,75 @@ const mapPageIdReferencesToRelativePaths = (pages, nav, options) => {
|
|
|
864
864
|
});
|
|
865
865
|
});
|
|
866
866
|
};
|
|
867
|
+
// Resolve a `pageid:<id>` internal-link href to the published relative path.
|
|
868
|
+
// Returns the resolved href, or null when `href` is not an internal pageid link
|
|
869
|
+
// (external/absolute links are left untouched). Falls back to the site root when
|
|
870
|
+
// the pageid can't be matched to a nav route.
|
|
871
|
+
const resolvePageIdHref = (href, nav, options) => {
|
|
872
|
+
// Only process internal links (no protocol, doesn't start with /)
|
|
873
|
+
if (!href || href.match(/^(https?:\/\/|\/)/) || !isHrefPageId(href)) {
|
|
874
|
+
return null;
|
|
875
|
+
}
|
|
876
|
+
const pageId = getPageIdFromHref(href);
|
|
877
|
+
const route = findNavItemByPageId(nav.navItems, pageId);
|
|
878
|
+
const brandPageSlugRoute = options.brandPageSlug
|
|
879
|
+
? `/${options.brandPageSlug}`
|
|
880
|
+
: '/';
|
|
881
|
+
if (route) {
|
|
882
|
+
if (route.isRoot) {
|
|
883
|
+
return brandPageSlugRoute;
|
|
884
|
+
}
|
|
885
|
+
return `${brandPageSlugRoute}${brandPageSlugRoute === '/' ? '' : '/'}${route.relativePath}`;
|
|
886
|
+
}
|
|
887
|
+
// fallback to root if pageid cannot be resolved.
|
|
888
|
+
return brandPageSlugRoute;
|
|
889
|
+
};
|
|
890
|
+
// BP-5847: walk a Tiptap JSON doc and rewrite every link mark whose href is a
|
|
891
|
+
// `pageid:` reference into its published relative path. Returns a NEW doc when
|
|
892
|
+
// something changed (so the shared model doc is never mutated), else the same
|
|
893
|
+
// reference.
|
|
894
|
+
export const resolvePageIdsInRteJson = (doc, nav, options) => {
|
|
895
|
+
let changed = false;
|
|
896
|
+
const walk = (node) => {
|
|
897
|
+
if (!node || typeof node !== 'object') {
|
|
898
|
+
return node;
|
|
899
|
+
}
|
|
900
|
+
let next = node;
|
|
901
|
+
if (Array.isArray(node.marks)) {
|
|
902
|
+
const marks = node.marks.map((mark) => {
|
|
903
|
+
if (mark?.type === 'link' && typeof mark.attrs?.href === 'string') {
|
|
904
|
+
const resolved = resolvePageIdHref(mark.attrs.href, nav, options);
|
|
905
|
+
if (resolved !== null) {
|
|
906
|
+
changed = true;
|
|
907
|
+
return { ...mark, attrs: { ...mark.attrs, href: resolved } };
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
return mark;
|
|
911
|
+
});
|
|
912
|
+
next = { ...next, marks };
|
|
913
|
+
}
|
|
914
|
+
if (Array.isArray(node.content)) {
|
|
915
|
+
next = { ...next, content: node.content.map(walk) };
|
|
916
|
+
}
|
|
917
|
+
return next;
|
|
918
|
+
};
|
|
919
|
+
const result = walk(doc);
|
|
920
|
+
return changed ? result : doc;
|
|
921
|
+
};
|
|
867
922
|
const dynamicallyMapPageIdInHyperlinksToRelativePaths = (content, nav, options) => {
|
|
868
923
|
Object.keys(content).forEach((key) => {
|
|
869
924
|
const value = content[key];
|
|
870
925
|
if (value === undefined || value === null) {
|
|
871
926
|
return;
|
|
872
927
|
}
|
|
873
|
-
if (
|
|
928
|
+
if (isTiptapDocument(value)) {
|
|
929
|
+
// BP-5847: RTE fields may now be Tiptap JSON. Resolve pageid link marks
|
|
930
|
+
// here too, or internal links render as raw `pageid:` and don't navigate.
|
|
931
|
+
content[key] =
|
|
932
|
+
resolvePageIdsInRteJson(value, nav, options);
|
|
933
|
+
}
|
|
934
|
+
else if (typeof value === 'object' &&
|
|
935
|
+
(key === 'items' || key === 'textItems')) {
|
|
874
936
|
if (Array.isArray(value)) {
|
|
875
937
|
value.forEach((item) => {
|
|
876
938
|
dynamicallyMapPageIdInHyperlinksToRelativePaths(item, nav, options);
|
|
@@ -882,24 +944,10 @@ const dynamicallyMapPageIdInHyperlinksToRelativePaths = (content, nav, options)
|
|
|
882
944
|
}
|
|
883
945
|
else if (typeof value === 'string' && hasRTEContent(value)) {
|
|
884
946
|
content[key] = value.replace(/<a\s+([^>]*?)href="([^"]*?)"([^>]*?)>/gi, (match, existingAttrsStart, href, existingAttrsEnd) => {
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
const brandPageSlugRoute = options.brandPageSlug
|
|
890
|
-
? `/${options.brandPageSlug}`
|
|
891
|
-
: '/';
|
|
892
|
-
if (route) {
|
|
893
|
-
let href = `${brandPageSlugRoute}`;
|
|
894
|
-
if (!route.isRoot) {
|
|
895
|
-
href = `${brandPageSlugRoute}${brandPageSlugRoute === '/' ? '' : '/'}${route.relativePath}`;
|
|
896
|
-
}
|
|
897
|
-
return `<a ${existingAttrsStart}href="${href}"${existingAttrsEnd}>`;
|
|
898
|
-
}
|
|
899
|
-
// fallback to root if pageid cannot be resolved.
|
|
900
|
-
return `<a ${existingAttrsStart}href="${brandPageSlugRoute}"${existingAttrsEnd}>`;
|
|
901
|
-
}
|
|
902
|
-
return match;
|
|
947
|
+
const resolved = resolvePageIdHref(href, nav, options);
|
|
948
|
+
return resolved === null
|
|
949
|
+
? match
|
|
950
|
+
: `<a ${existingAttrsStart}href="${resolved}"${existingAttrsEnd}>`;
|
|
903
951
|
});
|
|
904
952
|
}
|
|
905
953
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ButtonWithModalContent, ProfileContent, TextContent, VideoContent, ImageContent, ContentType, ButtonStyles, LogoStyles, MapContent, ImageGalleryContent, GalleryStyles, MailingListContent, RequestPaymentContent, ShopContent, SupportMeContent, ContactFormContent, BannerSectionContent, BrandPageType, SocialLinksContent, TestimonialSectionContent, AboutSectionContent, DividerSectionContent, ShapesSectionContent, ButtonLinkType, PageHeaderMobile, PageHeaderDesktop, PageFooterMobile, PageFooterDesktop, TextMediaItem, TextShapeItem, TextStyleDeclaration, AspectRatio, LogoImageModel, LogoTemplateImageModel, CookieOptions, BookingSectionContent, MediaTypes, TextStyles, BackgroundStyle, BackgroundShape, PersonalisationRole, SaveContactOptions } from './models';
|
|
1
|
+
import type { ButtonWithModalContent, ProfileContent, TextContent, VideoContent, ImageContent, ContentType, ButtonStyles, LogoStyles, MapContent, ImageGalleryContent, GalleryStyles, MailingListContent, RequestPaymentContent, ShopContent, SupportMeContent, ContactFormContent, BannerSectionContent, BrandPageType, SocialLinksContent, TestimonialSectionContent, AboutSectionContent, DividerSectionContent, ShapesSectionContent, ButtonLinkType, PageHeaderMobile, PageHeaderDesktop, PageFooterMobile, PageFooterDesktop, TextMediaItem, TextShapeItem, TextStyleDeclaration, AspectRatio, LogoImageModel, LogoTemplateImageModel, CookieOptions, BookingSectionContent, MediaTypes, TextStyles, BackgroundStyle, BackgroundShape, PersonalisationRole, SaveContactOptions, RichTextValue } from './models';
|
|
2
2
|
import type { ImagesSectionContentDisplayStyle } from './constants';
|
|
3
3
|
export type ViewModelType = 'button' | 'image' | 'image-gallery' | 'logo' | 'map' | 'mailing-list' | 'profile' | 'support-me' | 'request-payment' | 'text' | 'video' | 'contact-form' | 'banner-section' | 'images-section' | 'about-section' | 'divider-section' | 'testimonial-section' | 'social-links' | 'shapes-section' | 'shop-section' | 'booking-section';
|
|
4
4
|
export interface ContentViewModel {
|
|
@@ -94,8 +94,8 @@ export interface TextImageSectionViewModel {
|
|
|
94
94
|
id: string;
|
|
95
95
|
type: 'images-section';
|
|
96
96
|
role: ContentType;
|
|
97
|
-
title:
|
|
98
|
-
description:
|
|
97
|
+
title: RichTextValue;
|
|
98
|
+
description: RichTextValue;
|
|
99
99
|
displayStyle: ImagesSectionContentDisplayStyle;
|
|
100
100
|
aspectRatio: AspectRatio;
|
|
101
101
|
areImagesFullWidth: boolean;
|
|
@@ -0,0 +1,22 @@
|
|
|
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)";
|