@designcrowd/library.brand-page 6.0.19-ast-btns-4 → 6.0.19-ast-btns-6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/lib/src/{CreatedOnBrandCrowd-de-de-sjNsYRcW.js → CreatedOnBrandCrowd-de-de-Dqv1385w.js} +1 -1
  2. package/lib/src/{CreatedOnBrandCrowd-en-us-AzNub21R.js → CreatedOnBrandCrowd-en-us-DuA7uGNz.js} +1 -1
  3. package/lib/src/{CreatedOnBrandCrowd-es-es-5JGE8i4f.js → CreatedOnBrandCrowd-es-es-CaSTc5HM.js} +1 -1
  4. package/lib/src/{CreatedOnBrandCrowd-fr-fr-D_s0VxsF.js → CreatedOnBrandCrowd-fr-ca-C9VKny9y.js} +1 -1
  5. package/lib/src/{CreatedOnBrandCrowd-fr-ca-D_s0VxsF.js → CreatedOnBrandCrowd-fr-fr-C9VKny9y.js} +1 -1
  6. package/lib/src/{CreatedOnBrandCrowd-pt-pt-DNzVKTkD.js → CreatedOnBrandCrowd-pt-br-ft2V8-xm.js} +1 -1
  7. package/lib/src/{CreatedOnBrandCrowd-pt-br-DNzVKTkD.js → CreatedOnBrandCrowd-pt-pt-ft2V8-xm.js} +1 -1
  8. package/lib/src/{CreatedOnDcom-de-de-m9T1oefT.js → CreatedOnDcom-de-de-BdZxdxoi.js} +1 -1
  9. package/lib/src/{CreatedOnDcom-en-us-DyN0S9k3.js → CreatedOnDcom-en-us-Dl0XbKU2.js} +1 -1
  10. package/lib/src/{CreatedOnDcom-es-es-BvR812ln.js → CreatedOnDcom-es-es-WciMRblK.js} +1 -1
  11. package/lib/src/{CreatedOnDcom-fr-ca-BcmRHpDd.js → CreatedOnDcom-fr-ca-CvjUqr4M.js} +1 -1
  12. package/lib/src/{CreatedOnDcom-fr-fr-BY7TQCJy.js → CreatedOnDcom-fr-fr-CMALFpBz.js} +1 -1
  13. package/lib/src/{CreatedOnDcom-pt-br-B_bocMtD.js → CreatedOnDcom-pt-br-C0MjZ-_y.js} +1 -1
  14. package/lib/src/{CreatedOnDcom-pt-pt-Fg8JoWmO.js → CreatedOnDcom-pt-pt-DV9I0m0I.js} +1 -1
  15. package/lib/src/brand-page/components/contact-form/ContactForm.mixin.js +5 -2
  16. package/lib/src/brand-page/models/index.d.ts +34 -26
  17. package/lib/src/brand-page/utils/migration/migration-19.js +60 -29
  18. package/lib/src/brand-page/utils/migration/migration-20.js +11 -7
  19. package/lib/src/brand-page/utils/migration/migration-21.d.ts +2 -0
  20. package/lib/src/brand-page/utils/migration/migration-21.js +27 -0
  21. package/lib/src/brand-page/utils/migration.util.js +17 -2
  22. package/lib/src/brand-page/utils/text.util.d.ts +1 -1
  23. package/lib/src/brand-page/utils/text.util.js +32 -0
  24. package/lib/src/brand-page/utils/vcard.util.js +4 -4
  25. package/lib/src/brand-page/utils/view-model.util.d.ts +1 -0
  26. package/lib/src/brand-page/utils/view-model.util.js +68 -20
  27. package/lib/src/brand-page/view-models.d.ts +3 -3
  28. package/lib/src/brand-page-maker/__fixtures__/complex-rte.fixture.d.ts +22 -0
  29. package/lib/src/brand-page-maker/__fixtures__/complex-rte.fixture.js +22 -0
  30. package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.d.ts +14 -0
  31. package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.js +39 -0
  32. package/lib/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.d.ts +28 -0
  33. package/lib/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.js +66 -0
  34. package/lib/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.js +21 -100
  35. package/lib/src/brand-page-maker/models/content-settings-configs.d.ts +1 -0
  36. package/lib/src/brand-page-maker/models/content-settings.js +12 -6
  37. package/lib/src/brand-page-maker/utils/index.d.ts +2 -0
  38. package/lib/src/brand-page-maker/utils/index.js +2 -0
  39. package/lib/src/brand-page-maker/utils/rich-text-editor.util.d.ts +19 -5
  40. package/lib/src/brand-page-maker/utils/rich-text-editor.util.js +35 -5
  41. package/lib/src/brand-page-maker/utils/rte-render.util.d.ts +33 -0
  42. package/lib/src/brand-page-maker/utils/rte-render.util.js +50 -0
  43. package/lib/src/brand-page-maker/utils/rte-sanitize.util.d.ts +6 -0
  44. package/lib/src/brand-page-maker/utils/rte-sanitize.util.js +158 -0
  45. package/lib/src/brand-page-maker/utils/rte-serializer.d.ts +5 -0
  46. package/lib/src/brand-page-maker/utils/rte-serializer.js +112 -0
  47. package/lib/src/brand-page-maker/validators/field.validator.js +10 -2
  48. package/lib/src/css/library.brand-page-brandCrowd.min.css +16 -16
  49. package/lib/src/css/library.brand-page-brandPage.min.css +16 -16
  50. package/lib/src/css/library.brand-page-designCom.min.css +16 -16
  51. package/lib/src/css/library.brand-page-preview.min.css +16 -16
  52. package/lib/src/index.d.ts +1 -0
  53. package/lib/src/index.mjs +143 -138
  54. package/lib/src/{lib-D_Zb6GIA.js → lib-CYYPMTsG.js} +41993 -41710
  55. package/lib/src/lib.js +2 -0
  56. package/lib/src/metadata-builder/builders/update-content-text-builder.js +20 -15
  57. package/lib/src/metadata-builder/builders/update-content-text-style-builder.js +8 -3
  58. package/lib/src/metadata-builder/helpers/rte.helpers.d.ts +2 -2
  59. package/lib/src/metadata-builder/helpers/rte.helpers.js +6 -3
  60. package/lib/src/shared/content-settings/components/GenericTextInput.mixin.js +14 -1
  61. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.js +12 -3
  62. package/lib/src/shared/content-settings/models.d.ts +1 -0
  63. package/lib/src/shared/content-settings/store/content-settings-store.js +3 -0
  64. package/lib/src/utils/minify-brand-page-metadata.js +4 -1
  65. package/package.json +1 -1
  66. package/src/brand-page/components/contact-form/ContactForm.mixin.ts +5 -2
  67. package/src/brand-page/components/contact-form/ContactForm.vue +3 -0
  68. package/src/brand-page/components/text-wrapper/TextWrapper.vue +17 -6
  69. package/src/brand-page/models/index.ts +35 -26
  70. package/src/brand-page/utils/__snapshots__/migration.util.test.ts.snap +3 -3
  71. package/src/brand-page/utils/migration/migration-19.test.ts +47 -90
  72. package/src/brand-page/utils/migration/migration-19.ts +61 -34
  73. package/src/brand-page/utils/migration/migration-20.test.ts +49 -52
  74. package/src/brand-page/utils/migration/migration-20.ts +17 -27
  75. package/src/brand-page/utils/migration/migration-21.test.ts +112 -0
  76. package/src/brand-page/utils/migration/migration-21.ts +45 -0
  77. package/src/brand-page/utils/migration.util.ts +18 -2
  78. package/src/brand-page/utils/style.util.test.ts +51 -0
  79. package/src/brand-page/utils/text.util.test.ts +47 -0
  80. package/src/brand-page/utils/text.util.ts +43 -4
  81. package/src/brand-page/utils/vcard.util.ts +6 -4
  82. package/src/brand-page/utils/view-model.pageid-rte.test.ts +104 -0
  83. package/src/brand-page/utils/view-model.util.ts +94 -26
  84. package/src/brand-page/view-models.ts +3 -2
  85. package/src/brand-page-maker/__fixtures__/complex-rte.fixture.ts +23 -0
  86. package/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.test.ts +52 -0
  87. package/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.ts +42 -0
  88. package/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.ts +113 -0
  89. package/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.ts +23 -107
  90. package/src/brand-page-maker/models/content-settings-configs.ts +3 -0
  91. package/src/brand-page-maker/models/content-settings.ts +12 -6
  92. package/src/brand-page-maker/utils/__snapshots__/rte-render-parity.test.ts.snap +200 -0
  93. package/src/brand-page-maker/utils/index.ts +2 -0
  94. package/src/brand-page-maker/utils/rich-text-editor.util.test.ts +4 -3
  95. package/src/brand-page-maker/utils/rich-text-editor.util.ts +50 -11
  96. package/src/brand-page-maker/utils/rte-features.test.ts +330 -0
  97. package/src/brand-page-maker/utils/rte-render-parity.test.ts +156 -0
  98. package/src/brand-page-maker/utils/rte-render.util.ts +77 -0
  99. package/src/brand-page-maker/utils/rte-sanitize-json.test.ts +158 -0
  100. package/src/brand-page-maker/utils/rte-sanitize.util.ts +195 -0
  101. package/src/brand-page-maker/utils/rte-serializer.ts +172 -0
  102. package/src/brand-page-maker/validators/field.validator.test.ts +55 -0
  103. package/src/brand-page-maker/validators/field.validator.ts +10 -2
  104. package/src/lib.ts +9 -0
  105. package/src/metadata-builder/builders/update-content-text-builder.ts +32 -18
  106. package/src/metadata-builder/builders/update-content-text-style-builder.ts +14 -4
  107. package/src/metadata-builder/helpers/rte.helpers.ts +11 -7
  108. package/src/shared/content-settings/components/GenericTextInput.mixin.ts +17 -1
  109. package/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.ts +16 -3
  110. package/src/shared/content-settings/models.ts +3 -0
  111. package/src/shared/content-settings/store/content-settings-store.ts +3 -0
  112. package/src/utils/minify-brand-page-metadata.ts +4 -1
@@ -1,5 +1,5 @@
1
1
  import { openBlock as e, createElementBlock as l, createStaticVNode as a } from "vue";
2
- import { _ as t } from "./lib-D_Zb6GIA.js";
2
+ import { _ as t } from "./lib-CYYPMTsG.js";
3
3
  const i = {}, h = {
4
4
  width: "275",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { openBlock as e, createElementBlock as l, createStaticVNode as a } from "vue";
2
- import { _ as t } from "./lib-D_Zb6GIA.js";
2
+ import { _ as t } from "./lib-CYYPMTsG.js";
3
3
  const i = {}, h = {
4
4
  width: "251",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { openBlock as e, createElementBlock as l, createStaticVNode as a } from "vue";
2
- import { _ as t } from "./lib-D_Zb6GIA.js";
2
+ import { _ as t } from "./lib-CYYPMTsG.js";
3
3
  const i = {}, h = {
4
4
  width: "250",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { openBlock as e, createElementBlock as l, createStaticVNode as a } from "vue";
2
- import { _ as t } from "./lib-D_Zb6GIA.js";
2
+ import { _ as t } from "./lib-CYYPMTsG.js";
3
3
  const i = {}, h = {
4
4
  width: "237",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { openBlock as e, createElementBlock as l, createStaticVNode as a } from "vue";
2
- import { _ as t } from "./lib-D_Zb6GIA.js";
2
+ import { _ as t } from "./lib-CYYPMTsG.js";
3
3
  const i = {}, h = {
4
4
  width: "237",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { openBlock as e, createElementBlock as l, createStaticVNode as a } from "vue";
2
- import { _ as t } from "./lib-D_Zb6GIA.js";
2
+ import { _ as t } from "./lib-CYYPMTsG.js";
3
3
  const i = {}, h = {
4
4
  width: "263",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { openBlock as e, createElementBlock as l, createStaticVNode as a } from "vue";
2
- import { _ as t } from "./lib-D_Zb6GIA.js";
2
+ import { _ as t } from "./lib-CYYPMTsG.js";
3
3
  const i = {}, h = {
4
4
  width: "263",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { openBlock as H, createElementBlock as V, createStaticVNode as t } from "vue";
2
- import { _ as e } from "./lib-D_Zb6GIA.js";
2
+ import { _ as e } from "./lib-CYYPMTsG.js";
3
3
  const M = {}, Z = {
4
4
  width: "242",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { openBlock as H, createElementBlock as V, createStaticVNode as t } from "vue";
2
- import { _ as e } from "./lib-D_Zb6GIA.js";
2
+ import { _ as e } from "./lib-CYYPMTsG.js";
3
3
  const i = {}, M = {
4
4
  width: "231",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { openBlock as H, createElementBlock as V, createStaticVNode as t } from "vue";
2
- import { _ as e } from "./lib-D_Zb6GIA.js";
2
+ import { _ as e } from "./lib-CYYPMTsG.js";
3
3
  const M = {}, Z = {
4
4
  width: "230",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { openBlock as H, createElementBlock as V, createStaticVNode as t } from "vue";
2
- import { _ as e } from "./lib-D_Zb6GIA.js";
2
+ import { _ as e } from "./lib-CYYPMTsG.js";
3
3
  const i = {}, l = {
4
4
  width: "216",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { openBlock as H, createElementBlock as V, createStaticVNode as t } from "vue";
2
- import { _ as e } from "./lib-D_Zb6GIA.js";
2
+ import { _ as e } from "./lib-CYYPMTsG.js";
3
3
  const i = {}, l = {
4
4
  width: "216",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { openBlock as H, createElementBlock as V, createStaticVNode as t } from "vue";
2
- import { _ as M } from "./lib-D_Zb6GIA.js";
2
+ import { _ as M } from "./lib-CYYPMTsG.js";
3
3
  const Z = {}, e = {
4
4
  width: "232",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { openBlock as H, createElementBlock as V, createStaticVNode as t } from "vue";
2
- import { _ as M } from "./lib-D_Zb6GIA.js";
2
+ import { _ as M } from "./lib-CYYPMTsG.js";
3
3
  const Z = {}, e = {
4
4
  width: "232",
5
5
  height: "36",
@@ -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
- return this.viewModel.title && this.viewModel.title.trim() !== ''
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: string;
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: string;
175
- title: string;
176
- description: string;
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: string;
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: string;
247
- description: string;
254
+ title: RichTextValue;
255
+ description: RichTextValue;
248
256
  buttonText: string;
249
- successMessage: string;
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: string;
269
- description: string;
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: string;
297
- paragraph: string;
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?: string;
317
- paragraph?: string;
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: string;
326
- position: string;
327
- testimonials: string;
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: string;
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: string;
358
- description: string;
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: string;
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: string;
390
- paragraph: string;
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: string;
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: string;
438
- description: string;
445
+ title: RichTextValue;
446
+ description: RichTextValue;
439
447
  buttonText: string;
440
448
  url: string;
441
449
  email?: string;
@@ -1,40 +1,71 @@
1
+ import { hasRTEContent } from '../../../brand-page-maker/utils';
2
+ import { useExternalRichTextEditor } from '../../../brand-page-maker/composables/rich-text-editor/useRichTextEditor';
1
3
  /**
2
- * BP-5800 — decouple the section container background from the global button fill.
4
+ * BP-5787 — migrate existing inline-HTML RTE content to Tiptap JSON.
3
5
  *
4
- * These blocks' outer container historically took its background from the button
5
- * fill colour (`containerStyle = { ...buttonStyles }`). They now own a
6
- * `backgroundStyle`; seed its `backgroundColor` from the current button fill so
7
- * existing sites render identically while the colour becomes independently editable.
6
+ * The editor now emits, and the renderer now reads, Tiptap JSON. Existing
7
+ * published/saved sites still hold RTE fields as HTML strings. This converts
8
+ * them on load:
9
+ * - maker: the loaded model is JSON, so the next save persists JSON.
10
+ * - web-renderer: lazy migrate-on-read — old sites render from JSON without
11
+ * needing a re-save.
8
12
  *
9
- * runAlways + idempotent: only seeds when the colour is unset, so it never overrides
10
- * a colour the user has set. (contact-form is handled by migration-20 — its BP/BC
11
- * container colour moved to `cardStyle`.)
13
+ * Runs on EVERY load (runAlways) so it also normalises content the AI section
14
+ * builders still emit as HTML — there is no need to change those builders or
15
+ * widen the content model types.
16
+ *
17
+ * Scope: only HTML-RTE strings (hasRTEContent) are converted. Plain strings,
18
+ * numbers, and already-JSON docs are left untouched, so the migration is
19
+ * idempotent (re-running on JSON content is a no-op — non-string values are
20
+ * skipped). Nested RTE inside item arrays (textItems, testimonials, images…)
21
+ * is handled by recursing into arrays; style objects are intentionally not
22
+ * descended into (they never hold RTE content).
12
23
  */
13
- const CONTAINER_BACKGROUND_TYPES = new Set([
14
- 'mailing-list',
15
- 'request-payment',
16
- 'support-me',
17
- ]);
24
+ const convertRteStringsToJson = (value, editor) => {
25
+ if (Array.isArray(value)) {
26
+ value.forEach((item) => convertRteStringsToJson(item, editor));
27
+ return;
28
+ }
29
+ if (!value || typeof value !== 'object')
30
+ return;
31
+ const record = value;
32
+ Object.keys(record).forEach((key) => {
33
+ const field = record[key];
34
+ if (typeof field === 'string' && hasRTEContent(field)) {
35
+ editor.commands.setContent(field);
36
+ record[key] = editor.getJSON();
37
+ }
38
+ else if (Array.isArray(field)) {
39
+ convertRteStringsToJson(field, editor);
40
+ }
41
+ });
42
+ };
18
43
  export const migration19 = async (migrationResult) => {
19
44
  const { brandPageModel } = migrationResult;
20
- const buttonFill = brandPageModel.styles?.buttonStyles?.style?.backgroundColor;
21
- if (buttonFill === undefined) {
45
+ // HTML→JSON parsing needs a DOM (Tiptap's editor). In a DOM-less context
46
+ // (pure-node SSR or unit tests) we can't parse HTML, so we leave the RTE
47
+ // fields as HTML strings — the renderer and the fraud/translation extractors
48
+ // all dual-read HTML, so this is safe. Conversion happens wherever a DOM is
49
+ // available (the browser editor, jsdom).
50
+ if (typeof window === 'undefined' || typeof document === 'undefined') {
22
51
  return migrationResult;
23
52
  }
24
- brandPageModel.pages.forEach((page) => {
25
- page.contents.forEach((content) => {
26
- if (!CONTAINER_BACKGROUND_TYPES.has(content.type)) {
27
- return;
28
- }
29
- const block = content;
30
- if (block.backgroundStyle?.backgroundColor !== undefined) {
31
- return;
53
+ let externalEditor;
54
+ try {
55
+ externalEditor = useExternalRichTextEditor().externalEditor;
56
+ }
57
+ catch {
58
+ return migrationResult;
59
+ }
60
+ try {
61
+ for (const page of brandPageModel.pages) {
62
+ for (const content of page.contents) {
63
+ convertRteStringsToJson(content, externalEditor);
32
64
  }
33
- block.backgroundStyle = {
34
- ...block.backgroundStyle,
35
- backgroundColor: buttonFill,
36
- };
37
- });
38
- });
65
+ }
66
+ }
67
+ finally {
68
+ externalEditor.destroy();
69
+ }
39
70
  return migrationResult;
40
71
  };
@@ -1,21 +1,25 @@
1
- import { BRAND_PAGE_TYPES } from '../../models';
1
+ const CONTAINER_BACKGROUND_TYPES = new Set([
2
+ 'mailing-list',
3
+ 'request-payment',
4
+ 'support-me',
5
+ ]);
2
6
  export const migration20 = async (migrationResult) => {
3
7
  const { brandPageModel } = migrationResult;
4
- if (brandPageModel.brandPageType === BRAND_PAGE_TYPES.brandSite) {
5
- return migrationResult;
6
- }
7
8
  const buttonFill = brandPageModel.styles?.buttonStyles?.style?.backgroundColor;
8
9
  if (buttonFill === undefined) {
9
10
  return migrationResult;
10
11
  }
11
12
  brandPageModel.pages.forEach((page) => {
12
13
  page.contents.forEach((content) => {
13
- if (content.type !== 'contact-form') {
14
+ if (!CONTAINER_BACKGROUND_TYPES.has(content.type)) {
14
15
  return;
15
16
  }
16
17
  const block = content;
17
- block.cardStyle = {
18
- ...block.cardStyle,
18
+ if (block.backgroundStyle?.backgroundColor !== undefined) {
19
+ return;
20
+ }
21
+ block.backgroundStyle = {
22
+ ...block.backgroundStyle,
19
23
  backgroundColor: buttonFill,
20
24
  };
21
25
  });
@@ -0,0 +1,2 @@
1
+ import type { MigrationData } from '../migration.util';
2
+ export declare const migration21: (migrationResult: MigrationData) => Promise<MigrationData>;
@@ -0,0 +1,27 @@
1
+ import { BRAND_PAGE_TYPES } from '../../models';
2
+ export const migration21 = async (migrationResult) => {
3
+ const { brandPageModel } = migrationResult;
4
+ if (brandPageModel.brandPageType === BRAND_PAGE_TYPES.brandSite) {
5
+ return migrationResult;
6
+ }
7
+ const buttonFill = brandPageModel.styles?.buttonStyles?.style?.backgroundColor;
8
+ if (buttonFill === undefined) {
9
+ return migrationResult;
10
+ }
11
+ brandPageModel.pages.forEach((page) => {
12
+ page.contents.forEach((content) => {
13
+ if (content.type !== 'contact-form') {
14
+ return;
15
+ }
16
+ const block = content;
17
+ if (block.cardStyle?.backgroundColor !== undefined) {
18
+ return;
19
+ }
20
+ block.cardStyle = {
21
+ ...block.cardStyle,
22
+ backgroundColor: buttonFill,
23
+ };
24
+ });
25
+ });
26
+ return migrationResult;
27
+ };
@@ -23,6 +23,7 @@ import { migration17 } from './migration/migration-17';
23
23
  import { migration18 } from './migration/migration-18';
24
24
  import { migration19 } from './migration/migration-19';
25
25
  import { migration20 } from './migration/migration-20';
26
+ import { migration21 } from './migration/migration-21';
26
27
  const MIGRATION_TABLE = [
27
28
  {
28
29
  version: 1,
@@ -143,14 +144,28 @@ const MIGRATION_TABLE = [
143
144
  {
144
145
  version: 19,
145
146
  migrateFn: migration19,
147
+ // runAlways: normalise RTE to JSON on every load. Idempotent (only HTML
148
+ // strings are converted; JSON is skipped), so it also catches content the
149
+ // AI section builders still emit as HTML — converting it on the next read
150
+ // without a new migration or changes to those builders.
151
+ runAlways: true,
152
+ description: 'BP-5787: convert inline-HTML RTE content to Tiptap JSON on load (lazy migrate-on-read for web-renderer; persisted on next maker save; also normalises AI-built HTML)',
153
+ },
154
+ {
155
+ version: 20,
156
+ migrateFn: migration20,
146
157
  // runAlways: re-apply on every load so already-published sites pick up the seed
147
158
  // without a version bump. Idempotent — only seeds backgroundColor when unset.
148
159
  runAlways: true,
149
160
  description: 'BP-5800: seed own container backgroundStyle from the button fill colour for mailing-list, request-payment and support-me so the container background is independent of the site button (existing sites render unchanged)',
150
161
  },
151
162
  {
152
- version: 20,
153
- migrateFn: migration20,
163
+ version: 21,
164
+ migrateFn: migration21,
165
+ // runAlways: seed on every load so content already at the latest version
166
+ // (skipped by a version gate) still detaches. Idempotent — only seeds
167
+ // cardStyle.backgroundColor when unset, so a user colour is never clobbered.
168
+ runAlways: true,
154
169
  description: 'BP-5800: contact-form BP/BC container colour moves to cardStyle — seed cardStyle from the button fill (its long-standing rendered colour) so existing sites render unchanged',
155
170
  },
156
171
  ];
@@ -1,5 +1,5 @@
1
1
  export declare const getRenderText: (text: string) => string;
2
- export declare const escapeContentText: (text: string | undefined) => string | undefined;
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;