@designcrowd/library.brand-page 6.1.1-json-prereq-3 → 6.1.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.
Files changed (216) hide show
  1. package/lib/src/{CreatedOnBrandCrowd-de-de-hj_ievMc.js → CreatedOnBrandCrowd-de-de-B5B5ThJ6.js} +1 -1
  2. package/lib/src/{CreatedOnBrandCrowd-en-us-jAOSpfCw.js → CreatedOnBrandCrowd-en-us-a48CaORy.js} +1 -1
  3. package/lib/src/{CreatedOnBrandCrowd-es-es-DZMVc-y3.js → CreatedOnBrandCrowd-es-es-xpuZ317-.js} +1 -1
  4. package/lib/src/{CreatedOnBrandCrowd-fr-fr-DvB54H38.js → CreatedOnBrandCrowd-fr-ca-V-iUMArW.js} +1 -1
  5. package/lib/src/{CreatedOnBrandCrowd-fr-ca-DvB54H38.js → CreatedOnBrandCrowd-fr-fr-V-iUMArW.js} +1 -1
  6. package/lib/src/{CreatedOnBrandCrowd-pt-pt-bf-x1xhi.js → CreatedOnBrandCrowd-pt-br-DZ0NKSAy.js} +1 -1
  7. package/lib/src/{CreatedOnBrandCrowd-pt-br-bf-x1xhi.js → CreatedOnBrandCrowd-pt-pt-DZ0NKSAy.js} +1 -1
  8. package/lib/src/{CreatedOnDcom-de-de-B-NY1B44.js → CreatedOnDcom-de-de-_950cgGp.js} +1 -1
  9. package/lib/src/{CreatedOnDcom-en-us-Cd4C6KQf.js → CreatedOnDcom-en-us-DogKzH-g.js} +1 -1
  10. package/lib/src/{CreatedOnDcom-es-es-DONlsQsm.js → CreatedOnDcom-es-es-CmGM3IPX.js} +1 -1
  11. package/lib/src/{CreatedOnDcom-fr-ca-Dsv3FRpZ.js → CreatedOnDcom-fr-ca-BonEDkWZ.js} +1 -1
  12. package/lib/src/{CreatedOnDcom-fr-fr-DOgyvY5C.js → CreatedOnDcom-fr-fr-DQHtKrd_.js} +1 -1
  13. package/lib/src/{CreatedOnDcom-pt-br-DX66MhJS.js → CreatedOnDcom-pt-br-CtYJSBjs.js} +1 -1
  14. package/lib/src/{CreatedOnDcom-pt-pt-COe2G0bU.js → CreatedOnDcom-pt-pt-DoO9r4lA.js} +1 -1
  15. package/lib/src/brand-page/components/contact-form/ContactForm.mixin.js +7 -8
  16. package/lib/src/brand-page/components/mailing-list/MailingList.mixin.js +6 -4
  17. package/lib/src/brand-page/components/page-footer/PageFooter.mixin.d.ts +1 -0
  18. package/lib/src/brand-page/components/page-footer/PageFooter.mixin.js +4 -1
  19. package/lib/src/brand-page/components/payment/Payment.mixin.js +4 -3
  20. package/lib/src/brand-page/components/payment/__fixtures__/request-payment.d.ts +1 -0
  21. package/lib/src/brand-page/components/payment/__fixtures__/support-me.d.ts +1 -0
  22. package/lib/src/brand-page/components/payment/request-payment/RequestPayment.mixin.d.ts +1 -0
  23. package/lib/src/brand-page/components/payment/request-payment/RequestPayment.mixin.js +4 -1
  24. package/lib/src/brand-page/composables/consent.composable.d.ts +2 -0
  25. package/lib/src/brand-page/composables/consent.composable.js +9 -3
  26. package/lib/src/brand-page/models/brand-page-context.model.d.ts +7 -0
  27. package/lib/src/brand-page/models/index.d.ts +26 -34
  28. package/lib/src/brand-page/utils/context.util.d.ts +8 -0
  29. package/lib/src/brand-page/utils/context.util.js +8 -0
  30. package/lib/src/brand-page/utils/migration.util.js +0 -11
  31. package/lib/src/brand-page/utils/text.util.d.ts +1 -13
  32. package/lib/src/brand-page/utils/text.util.js +0 -64
  33. package/lib/src/brand-page/utils/vcard.util.js +4 -4
  34. package/lib/src/brand-page/utils/view-model.util.d.ts +0 -1
  35. package/lib/src/brand-page/utils/view-model.util.js +20 -68
  36. package/lib/src/brand-page/view-models.d.ts +3 -3
  37. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +1 -1
  38. package/lib/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.js +51 -18
  39. package/lib/src/brand-page-maker/composables/ai-chat/useAiMetadataBuilder.d.ts +1 -0
  40. package/lib/src/brand-page-maker/composables/rich-text-editor/bubbleMenu.d.ts +9 -2
  41. package/lib/src/brand-page-maker/composables/rich-text-editor/bubbleMenu.js +10 -6
  42. package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontSize.d.ts +20 -0
  43. package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontSize.js +44 -0
  44. package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/LineHeight.d.ts +27 -0
  45. package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/LineHeight.js +44 -0
  46. package/lib/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.d.ts +1 -1
  47. package/lib/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.js +103 -24
  48. package/lib/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.js +8 -0
  49. package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.js +9 -3
  50. package/lib/src/brand-page-maker/stores/ui/ui-store.d.ts +1 -1
  51. package/lib/src/brand-page-maker/utils/index.d.ts +0 -2
  52. package/lib/src/brand-page-maker/utils/index.js +0 -2
  53. package/lib/src/brand-page-maker/utils/rich-text-editor.util.d.ts +5 -28
  54. package/lib/src/brand-page-maker/utils/rich-text-editor.util.js +5 -48
  55. package/lib/src/brand-page-maker/validators/field.validator.js +2 -10
  56. package/lib/src/clients/models.d.ts +2 -0
  57. package/lib/src/css/library.brand-page-brandCrowd.min.css +99 -102
  58. package/lib/src/css/library.brand-page-brandPage.min.css +99 -102
  59. package/lib/src/css/library.brand-page-designCom.min.css +99 -102
  60. package/lib/src/css/library.brand-page-preview.min.css +99 -102
  61. package/lib/src/index.d.ts +1 -1
  62. package/lib/src/index.mjs +219 -221
  63. package/lib/src/{lib-scDtk-Pi.js → lib-DtalJKI3.js} +72445 -75920
  64. package/lib/src/lib.js +1 -2
  65. package/lib/src/metadata-builder/ai-metadata-executor.d.ts +1 -0
  66. package/lib/src/metadata-builder/ai-metadata-executor.js +19 -0
  67. package/lib/src/metadata-builder/builders/section-builders/about-section-builder.js +3 -2
  68. package/lib/src/metadata-builder/builders/section-builders/banner-section-builder.js +3 -2
  69. package/lib/src/metadata-builder/builders/section-builders/booking-section-builder.js +3 -2
  70. package/lib/src/metadata-builder/builders/section-builders/contact-form-builder.js +4 -3
  71. package/lib/src/metadata-builder/builders/section-builders/images-section-builder.js +3 -2
  72. package/lib/src/metadata-builder/builders/section-builders/mailing-list-builder.js +2 -1
  73. package/lib/src/metadata-builder/builders/section-builders/mailing-list-section-builder.js +2 -1
  74. package/lib/src/metadata-builder/builders/section-builders/profile-section-builder.js +4 -3
  75. package/lib/src/metadata-builder/builders/section-builders/shapes-section-builder.js +3 -2
  76. package/lib/src/metadata-builder/builders/section-builders/testimonial-section-builder.js +2 -1
  77. package/lib/src/metadata-builder/builders/section-builders/text-section-builder.js +11 -15
  78. package/lib/src/metadata-builder/builders/update-content-text-builder.d.ts +17 -0
  79. package/lib/src/metadata-builder/builders/update-content-text-builder.js +103 -0
  80. package/lib/src/metadata-builder/builders/update-content-text-style-builder.d.ts +16 -0
  81. package/lib/src/metadata-builder/builders/update-content-text-style-builder.js +216 -0
  82. package/lib/src/metadata-builder/helpers/rte.helpers.d.ts +13 -11
  83. package/lib/src/metadata-builder/helpers/rte.helpers.js +73 -14
  84. package/lib/src/metadata-builder/metadata-executor.js +4 -0
  85. package/lib/src/metadata-builder/models.d.ts +2 -0
  86. package/lib/src/metadata-builder/models.js +2 -0
  87. package/lib/src/metadata-builder/parsers/ai-toolcall-parser.d.ts +19 -0
  88. package/lib/src/metadata-builder/parsers/ai-toolcall-parser.js +4 -0
  89. package/lib/src/metadata-builder/sanitizers/rte-schema-check.d.ts +8 -7
  90. package/lib/src/metadata-builder/sanitizers/rte-schema-check.js +52 -21
  91. package/lib/src/shared/content-settings/components/GenericTextInput.mixin.d.ts +1 -1
  92. package/lib/src/shared/content-settings/components/GenericTextInput.mixin.js +10 -35
  93. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.d.ts +1 -1
  94. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.js +3 -12
  95. package/lib/src/shared/content-settings/components/email-field/EmailField.mixin.d.ts +1 -1
  96. package/lib/src/shared/content-settings/components/tel-field/TelField.mixin.d.ts +1 -1
  97. package/lib/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.mixin.d.ts +2 -3
  98. package/lib/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.mixin.js +3 -10
  99. package/lib/src/shared/content-settings/components/text-field/TextField.mixin.d.ts +1 -1
  100. package/lib/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.d.ts +1 -2
  101. package/lib/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.js +1 -4
  102. package/lib/src/shared/content-settings/components/textarea-field/TextareaField.mixin.d.ts +1 -1
  103. package/lib/src/shared/content-settings/components/url-field/UrlField.mixin.d.ts +1 -1
  104. package/lib/src/shared/content-settings/components/youtube-video-field/YoutubeVideoField.mixin.d.ts +1 -1
  105. package/lib/src/shared/logo-settings/components/EditLogoSettings.mixin.d.ts +1 -1
  106. package/lib/src/shared/logo-versions/LogoVersions.mixin.d.ts +1 -1
  107. package/lib/src/shared/my-designs/MyDesigns.mixin.d.ts +1 -1
  108. package/lib/src/shared/my-uploads/MyMedia.mixin.d.ts +1 -1
  109. package/lib/src/shared/my-uploads/MyUploads.mixin.d.ts +1 -1
  110. package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +1 -1
  111. package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +1 -1
  112. package/lib/src/shared/style-settings/components/StyleSettings.mixin.d.ts +1 -1
  113. package/lib/src/shared/template-settings/components/TemplatesSettings.mixin.d.ts +1 -1
  114. package/lib/src/shared/template-settings/store/template-settings-store.d.ts +2 -0
  115. package/lib/src/shared/template-settings/store/template-settings-store.js +72 -24
  116. package/lib/src/shared/unsplash-library/UnsplashLibrary.mixin.d.ts +1 -1
  117. package/lib/src/utils/minify-brand-page-metadata.js +1 -4
  118. package/package.json +15 -17
  119. package/src/brand-page/components/contact-form/ContactForm.mixin.ts +9 -7
  120. package/src/brand-page/components/cookie-consent/CookieConsent.vue +1 -0
  121. package/src/brand-page/components/mailing-list/MailingList.mixin.ts +8 -3
  122. package/src/brand-page/components/navigation/navigation-carousel/NavigationCarousel.vue +5 -1
  123. package/src/brand-page/components/page-footer/PageFooter.mixin.ts +4 -1
  124. package/src/brand-page/components/page-footer/PageFooter.vue +1 -1
  125. package/src/brand-page/components/payment/Payment.mixin.ts +4 -3
  126. package/src/brand-page/components/payment/request-payment/RequestPayment.mixin.ts +8 -1
  127. package/src/brand-page/components/payment/request-payment/RequestPayment.vue +1 -1
  128. package/src/brand-page/components/save-contact/SaveContactWidget.vue +2 -1
  129. package/src/brand-page/components/shop/Shop.vue +6 -3
  130. package/src/brand-page/components/social-links/SocialLinks.vue +5 -2
  131. package/src/brand-page/components/text-wrapper/TextWrapper.vue +6 -17
  132. package/src/brand-page/composables/consent.composable.ts +18 -4
  133. package/src/brand-page/models/brand-page-context.model.ts +7 -0
  134. package/src/brand-page/models/index.ts +26 -35
  135. package/src/brand-page/utils/__snapshots__/migration.util.test.ts.snap +3 -3
  136. package/src/brand-page/utils/context.util.test.ts +43 -0
  137. package/src/brand-page/utils/context.util.ts +10 -0
  138. package/src/brand-page/utils/migration.util.ts +0 -12
  139. package/src/brand-page/utils/style.util.test.ts +0 -51
  140. package/src/brand-page/utils/text.util.ts +4 -83
  141. package/src/brand-page/utils/vcard.util.ts +4 -6
  142. package/src/brand-page/utils/view-model.util.ts +26 -94
  143. package/src/brand-page/view-models.ts +2 -3
  144. package/src/brand-page-maker/components/rich-text-editor/RichTextEditor.vue +4 -5
  145. package/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.test.ts +135 -0
  146. package/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.ts +62 -21
  147. package/src/brand-page-maker/composables/ai-chat/useAiMetadataBuilder.ts +1 -0
  148. package/src/brand-page-maker/composables/rich-text-editor/bubbleMenu.ts +10 -6
  149. package/src/brand-page-maker/composables/rich-text-editor/extensions/FontSize.ts +73 -0
  150. package/src/brand-page-maker/composables/rich-text-editor/extensions/LineHeight.ts +85 -0
  151. package/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.ts +111 -27
  152. package/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.ts +9 -0
  153. package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.ts +11 -3
  154. package/src/brand-page-maker/stores/ui/ui-store.ts +1 -1
  155. package/src/brand-page-maker/utils/index.ts +0 -2
  156. package/src/brand-page-maker/utils/rich-text-editor.util.test.ts +3 -37
  157. package/src/brand-page-maker/utils/rich-text-editor.util.ts +11 -66
  158. package/src/brand-page-maker/validators/field.validator.ts +2 -10
  159. package/src/clients/models.ts +3 -0
  160. package/src/lib.ts +1 -9
  161. package/src/metadata-builder/ai-metadata-executor.ts +26 -0
  162. package/src/metadata-builder/builders/section-builders/about-section-builder.ts +3 -2
  163. package/src/metadata-builder/builders/section-builders/banner-section-builder.ts +3 -2
  164. package/src/metadata-builder/builders/section-builders/booking-section-builder.ts +5 -2
  165. package/src/metadata-builder/builders/section-builders/contact-form-builder.ts +4 -3
  166. package/src/metadata-builder/builders/section-builders/images-section-builder.ts +3 -2
  167. package/src/metadata-builder/builders/section-builders/mailing-list-builder.ts +2 -1
  168. package/src/metadata-builder/builders/section-builders/mailing-list-section-builder.ts +2 -1
  169. package/src/metadata-builder/builders/section-builders/profile-section-builder.ts +6 -3
  170. package/src/metadata-builder/builders/section-builders/shapes-section-builder.ts +3 -2
  171. package/src/metadata-builder/builders/section-builders/testimonial-section-builder.ts +2 -1
  172. package/src/metadata-builder/builders/section-builders/text-section-builder.ts +13 -19
  173. package/src/metadata-builder/builders/update-content-text-builder.ts +148 -0
  174. package/src/metadata-builder/builders/update-content-text-style-builder.ts +352 -0
  175. package/src/metadata-builder/helpers/rte.helpers.ts +101 -15
  176. package/src/metadata-builder/metadata-executor.ts +5 -0
  177. package/src/metadata-builder/models.ts +3 -0
  178. package/src/metadata-builder/parsers/ai-toolcall-parser.ts +27 -0
  179. package/src/metadata-builder/sanitizers/rte-schema-check.ts +53 -24
  180. package/src/shared/content-settings/components/GenericTextInput.mixin.ts +11 -42
  181. package/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.ts +3 -16
  182. package/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.mixin.ts +4 -13
  183. package/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.vue +4 -6
  184. package/src/shared/content-settings/components/text-image-field/TextImageField.vue +3 -6
  185. package/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.ts +1 -6
  186. package/src/shared/content-settings/components/text-items-field/TextItemsField.vue +1 -1
  187. package/src/shared/template-settings/store/template-settings-store.ts +93 -27
  188. package/src/utils/minify-brand-page-metadata.ts +1 -4
  189. package/lib/src/brand-page/utils/migration/migration-20.d.ts +0 -2
  190. package/lib/src/brand-page/utils/migration/migration-20.js +0 -66
  191. package/lib/src/brand-page-maker/__fixtures__/complex-rte.fixture.d.ts +0 -22
  192. package/lib/src/brand-page-maker/__fixtures__/complex-rte.fixture.js +0 -22
  193. package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.d.ts +0 -14
  194. package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.js +0 -39
  195. package/lib/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.d.ts +0 -28
  196. package/lib/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.js +0 -117
  197. package/lib/src/brand-page-maker/utils/rte-render.util.d.ts +0 -63
  198. package/lib/src/brand-page-maker/utils/rte-render.util.js +0 -95
  199. package/lib/src/brand-page-maker/utils/rte-sanitize.util.d.ts +0 -6
  200. package/lib/src/brand-page-maker/utils/rte-sanitize.util.js +0 -156
  201. package/src/brand-page/utils/migration/migration-20.server.test.ts +0 -125
  202. package/src/brand-page/utils/migration/migration-20.test.ts +0 -72
  203. package/src/brand-page/utils/migration/migration-20.ts +0 -87
  204. package/src/brand-page/utils/text.util.test.ts +0 -106
  205. package/src/brand-page/utils/view-model.pageid-rte.test.ts +0 -104
  206. package/src/brand-page-maker/__fixtures__/complex-rte.fixture.ts +0 -23
  207. package/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.test.ts +0 -52
  208. package/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.ts +0 -42
  209. package/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.ts +0 -162
  210. package/src/brand-page-maker/utils/__snapshots__/rte-render-parity.test.ts.snap +0 -202
  211. package/src/brand-page-maker/utils/rte-features.test.ts +0 -378
  212. package/src/brand-page-maker/utils/rte-render-parity.test.ts +0 -164
  213. package/src/brand-page-maker/utils/rte-render.util.ts +0 -165
  214. package/src/brand-page-maker/utils/rte-sanitize-json.test.ts +0 -172
  215. package/src/brand-page-maker/utils/rte-sanitize.util.ts +0 -192
  216. package/src/brand-page-maker/validators/field.validator.test.ts +0 -55
@@ -6,7 +6,7 @@ import { TextInput, Textarea } from '@designcrowd/fe-shared-lib';
6
6
  import type { ContentItemFieldSettings } from '../models';
7
7
  import {
8
8
  encodeContentText,
9
- getContentTextLength,
9
+ escapeContentText,
10
10
  unescapeContentText,
11
11
  } from '@brand-page/utils';
12
12
  import { genericTextInputTr } from '@src/composables/useTranslate';
@@ -107,6 +107,7 @@ export default defineComponent({
107
107
  _oldValue: '' as string,
108
108
  isDirty: false,
109
109
  emitChangeDebounce: undefined as DebounceHandle | undefined,
110
+ lengthError: false,
110
111
  // Whether every write in the current debounce burst was an RTE
111
112
  // hydration write-back (see UpdateItemValuePayload.isHydrationWrite).
112
113
  // undefined = no burst pending; any user write downgrades it to false.
@@ -129,30 +130,6 @@ export default defineComponent({
129
130
  return this.settings?.isValid;
130
131
  },
131
132
 
132
- // Derived from the current value + maxLength (not hand-synced in a watcher):
133
- // stays correct when maxLength or showRichTextEditor change and on first
134
- // render, and flips the instant `_value` updates rather than after the emit
135
- // round-trips back through the `value` prop. `>=` (not `>`) because the RTE
136
- // MaxLength extension hard-gates the length, so "reached the cap" is the
137
- // signal to surface. Length is measured with getContentTextLength so it
138
- // matches the editor's own char count (ProseMirror textContent) — NOT
139
- // escapeContentText, whose space-join over-counts by one per mark boundary
140
- // (e.g. hyperlinking a word), reporting length+1 at the cap (BP-5787).
141
- lengthError(): boolean {
142
- const value = this._value;
143
- const maxLength = this.maxLength;
144
-
145
- if (maxLength == null || value === false || value == null) {
146
- return false;
147
- }
148
-
149
- const length = this.showRichTextEditor
150
- ? getContentTextLength(value)
151
- : value.length;
152
-
153
- return length >= maxLength;
154
- },
155
-
156
133
  label(): string {
157
134
  return this.name.replace(/./, (c: string) => c.toUpperCase());
158
135
  },
@@ -198,24 +175,16 @@ export default defineComponent({
198
175
  },
199
176
  },
200
177
  watch: {
201
- value(newVal: string | undefined, oldVal: string | undefined) {
202
- // BP-5787: RTE values are Tiptap JSON objects. The parent (TextItemsField)
203
- // re-clones its items array (cloneDeep) on ANY change — including a sibling
204
- // field's style sync — yielding a NEW object reference with identical text
205
- // content. For objects Vue fires this watcher on reference change, so that
206
- // spurious re-clone would overwrite `_value` and clobber an in-flight edit
207
- // (a mark applied but not yet debounced to the store), silently dropping
208
- // the user's formatting. Strings never hit this (same content = same
209
- // reference = no fire), so mirror that: ignore re-clones whose content is
210
- // unchanged from the previous prop value.
211
- if (
212
- this.showRichTextEditor &&
213
- JSON.stringify(newVal) === JSON.stringify(oldVal)
214
- ) {
215
- return;
216
- }
217
-
178
+ value(newVal: string | undefined) {
218
179
  this._value = newVal;
180
+
181
+ if (this.showRichTextEditor) {
182
+ const textValue = escapeContentText(this._value);
183
+
184
+ this.lengthError = textValue && textValue.length >= this.maxLength;
185
+ } else {
186
+ this.lengthError = this._value.length >= this.maxLength;
187
+ }
219
188
  },
220
189
 
221
190
  isToggled(newVal: boolean) {
@@ -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: unknown = generatedText;
330
+ let formattedText = generatedText;
334
331
 
335
- // BP-5787: preserve the field's formatting when dropping in AI text.
336
- // replaceTextInRTE is HTML-based, so JSON is rendered/parsed around it
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, getRteTextLength } from '@brand-page-maker/utils';
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) => getPlainTextFromRTE(option.testimonials).length === 0,
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: RichTextValue): string {
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
- value.replace(new RegExp(ESCAPED_NEWLINE_CHARACTER, 'g'), ' '),
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);
@@ -26,8 +26,7 @@
26
26
  :error="
27
27
  isEmpty[option.index] ||
28
28
  (options[option.index].testimonials &&
29
- rteTextLength(options[option.index].testimonials) >=
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
- rteTextLength(options[option.index].testimonials) >=
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
- rteTextLength(options[option.index].name) >= maxTitleLength
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
- rteTextLength(options[option.index].position) >= maxTitleLength
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="rteTextLength(options[option.index].title) >= titleLength"
100
+ :error="options[option.index].title.length >= titleLength"
101
101
  />
102
102
  <p
103
- v-if="rteTextLength(options[option.index].title) >= titleLength"
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, getRteTextLength } from '@brand-page-maker/utils';
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);
@@ -32,7 +32,7 @@
32
32
  size="sm"
33
33
  :error="
34
34
  isEmpty[option.index] ||
35
- rteTextLength(options[option.index].text) >= maxTextLength
35
+ options[option.index].text.length >= maxTextLength
36
36
  "
37
37
  :resize="false"
38
38
  class="tw-w-full"
@@ -26,7 +26,10 @@ import {
26
26
  useUiStore,
27
27
  } from '@brand-page-maker/stores';
28
28
  import { useUiSideBarStore } from '../../side-bar/store';
29
- import { BrandPageModel } from '@brand-page/models';
29
+ import {
30
+ BrandPageModel,
31
+ type UserPersonalisationContent,
32
+ } from '@brand-page/models';
30
33
  import { migrateBrandPageModel } from '@brand-page/utils';
31
34
  import { getAiContentIndexByTemplateTokenAndNumberOfVariations } from '@brand-page-maker/utils/ai-content-selection.util';
32
35
  import {
@@ -50,6 +53,7 @@ const POST_PURCHASE_PAGE_SECTION_BY_TAB: Record<string, string> = {
50
53
 
51
54
  const PRE_PURCHASE_PAGE_SECTION_BY_TAB: Record<string, string> = {
52
55
  [TEMPLATE_TABS.all]: 'MakerPrePurchaseSidebarTemplatesAllTab',
56
+ [TEMPLATE_TABS.AI]: 'MakerPrePurchaseSidebarTemplatesAiTab',
53
57
  [TEMPLATE_TABS.categories]: 'MakerPrePurchaseSidebarTemplatesCategoryTab',
54
58
  };
55
59
  const LOAD_PAGE_RETRY_DELAY = 250;
@@ -76,6 +80,7 @@ export interface TemplatesSettingState {
76
80
  selectedTab: SelectedTabType;
77
81
  selectedCategory: string | undefined;
78
82
  searchTerm: string | undefined;
83
+ searchPersonalisationData: UserPersonalisationContent[] | undefined;
79
84
  tabsWithTranslations: SelectedTabType[];
80
85
  logoChangeWatcherInitialized: boolean;
81
86
  initialSearchTerm: string | undefined;
@@ -98,6 +103,7 @@ export const useUiTemplateSettingsStore = defineStore('uiTemplateSettings', {
98
103
  displayName: templateSettingsTr(`headerAll`),
99
104
  },
100
105
  searchTerm: undefined,
106
+ searchPersonalisationData: undefined,
101
107
  selectedCategory: undefined,
102
108
  templateCategories: [],
103
109
  tabsWithTranslations: [
@@ -321,17 +327,25 @@ export const useUiTemplateSettingsStore = defineStore('uiTemplateSettings', {
321
327
  const isBrandSite =
322
328
  this.templateType === BP_BRAND_SITE_TEMPLATE_TYPE_NAME;
323
329
 
324
- // Pre-purchase has no logo draft for logo-based recommendation, so fall back to the seeded
325
- // search term (the keyword from the URL/cookie - never the business name) so the All tab
326
- // stays relevant instead of generic (bp-5454).
327
- const effectiveSearchTerm =
330
+ // With no user search, the All tab must still show industry-relevant templates rather than
331
+ // generic ones (searchIndustries alone doesn't rank the non-AI half). Seed searchTerm as an
332
+ // invisible default: pre-purchase has no logo to read an industry from, so it uses the seeded
333
+ // keyword from the URL/cookie (bp-5454); post-purchase falls back to the logo draft's industry
334
+ // (bp-5789). Once the user searches, this.searchTerm takes over. this.searchTerm stays unset
335
+ // here, so AI content-key resolution below is unaffected.
336
+ const useSeededSearchTerm =
328
337
  !this.searchTerm &&
329
338
  !this.selectedCategory &&
330
- isPrePurchase &&
331
339
  isBrandSite &&
332
- this.selectedTab.key === TEMPLATE_TABS.all
333
- ? this.initialSearchTerm
334
- : this.searchTerm;
340
+ this.selectedTab.key === TEMPLATE_TABS.all;
341
+
342
+ const seededSearchTerm = isPrePurchase
343
+ ? this.initialSearchTerm
344
+ : brandPageMakerStore.industry;
345
+
346
+ const effectiveSearchTerm = useSeededSearchTerm
347
+ ? seededSearchTerm
348
+ : this.searchTerm;
335
349
 
336
350
  const pageSection = isBrandSite
337
351
  ? (isPrePurchase
@@ -397,6 +411,23 @@ export const useUiTemplateSettingsStore = defineStore('uiTemplateSettings', {
397
411
  }
398
412
  }
399
413
 
414
+ // searchPersonalisation is a per-search payload established on page 1; only set it there
415
+ // (like abTestValuesToBePushed above) so paginating to page 2+ can't reset it to undefined.
416
+ // Every context change (search/clear/category/logo swap) reloads from page 1, so this still
417
+ // clears stale search content on a new search.
418
+ //
419
+ // Set BEFORE the mapping loop so tiles and the apply path read the same value — reading the
420
+ // per-page `result.searchPersonalisation` here made page-2 tiles fall back to page-load
421
+ // content while applying used page-1 search content.
422
+ //
423
+ // Empty [] is stored as undefined: it wins `??` but fails `?.length`, so it would strand
424
+ // the downstream fallback to uiStore.personalisationData and silently skip the apply.
425
+ if (pageNo === 1) {
426
+ this.searchPersonalisationData = result.searchPersonalisation?.length
427
+ ? result.searchPersonalisation
428
+ : undefined;
429
+ }
430
+
400
431
  const templates = await Promise.all(
401
432
  result.paginatedItems.map(async (item) => {
402
433
  try {
@@ -411,17 +442,20 @@ export const useUiTemplateSettingsStore = defineStore('uiTemplateSettings', {
411
442
  let personalisationContentIndex: number | undefined;
412
443
  let contentKey: string | undefined;
413
444
 
414
- if (uiStore.personalisationData?.length) {
445
+ const activePersonalisation =
446
+ this.searchPersonalisationData ?? uiStore.personalisationData;
447
+
448
+ if (activePersonalisation?.length) {
415
449
  personalisationContentIndex =
416
450
  await getAiContentIndexByTemplateTokenAndNumberOfVariations(
417
451
  item.designTemplateToken,
418
- uiStore.personalisationData.length,
452
+ activePersonalisation.length,
419
453
  );
420
454
 
421
455
  contentKey =
422
- uiStore.personalisationData[personalisationContentIndex]
456
+ activePersonalisation[personalisationContentIndex]
423
457
  .contentKey;
424
- } else if (item.isPersonalisable) {
458
+ } else if (item.isPersonalisable && !this.searchTerm) {
425
459
  // Pre-purchase has no personalisationData; personalise AI
426
460
  // template previews with the prompt content key (bp-5742).
427
461
  contentKey = uiStore.userPromptContentKey;
@@ -431,10 +465,9 @@ export const useUiTemplateSettingsStore = defineStore('uiTemplateSettings', {
431
465
  id: item.designTemplateToken,
432
466
  isPersonalisable: item.isPersonalisable,
433
467
  contentKey,
434
- // A userPromptContentKey signals this design's real AI images live
435
- // in the userPrompt bucket (post- or pre-purchase). The preview must
436
- // then resolve via userPromptContentKey, not the regular bucket which
437
- // holds placeholders post-purchase (bp-5454).
468
+ // userPromptContentKey signals the content lives in the userPrompt S3
469
+ // bucket (post- or pre-purchase); the preview must resolve via
470
+ // userPromptContentKey, not the regular bucket (bp-5454).
438
471
  usesUserPromptContentKey: !!uiStore.userPromptContentKey,
439
472
  previewImageUrl: item.previewImageUrl,
440
473
  } as TemplateSettingsTemplate;
@@ -450,13 +483,25 @@ export const useUiTemplateSettingsStore = defineStore('uiTemplateSettings', {
450
483
  (t): t is TemplateSettingsTemplate => t !== null,
451
484
  );
452
485
 
453
- // The Categories tab must never surface AI templates or the AI badge
454
- // (bp-5454 scenario 5). The badge is driven purely by isPersonalisable,
455
- // so clear it for category results regardless of the template's catalog flag.
456
- const displayTemplates =
457
- this.selectedTab.key === TEMPLATE_TABS.categories
458
- ? validTemplates.map((t) => ({ ...t, isPersonalisable: false }))
459
- : validTemplates;
486
+ let displayTemplates = validTemplates;
487
+
488
+ // The All tab shows backend-interleaved AI templates (gated server-side on the AB
489
+ // variation) even when the dedicated AI tab is hidden — e.g. regular templates
490
+ // pre-purchase (bp-5789). So only strip AI when the AI tab is hidden AND we're not on
491
+ // the All tab; the backend is the interleave gate, control buckets return no AI here.
492
+ if (
493
+ !uiStore.showAiTemplatesTab &&
494
+ this.selectedTab.key !== TEMPLATE_TABS.all
495
+ ) {
496
+ displayTemplates = displayTemplates.filter(
497
+ (t) => !t.isPersonalisable,
498
+ );
499
+ } else if (this.selectedTab.key === TEMPLATE_TABS.categories) {
500
+ displayTemplates = displayTemplates.map((t) => ({
501
+ ...t,
502
+ isPersonalisable: false,
503
+ }));
504
+ }
460
505
 
461
506
  this._addTemplates({ templates: displayTemplates });
462
507
  this._setPageNoLoaded({ pageNoLoaded: pageNo });
@@ -529,6 +574,11 @@ export const useUiTemplateSettingsStore = defineStore('uiTemplateSettings', {
529
574
  searchTerm: payload.searchTerm,
530
575
  });
531
576
 
577
+ // Drive AI industry selection from the typed term; fall back to the
578
+ // seeded keyword on clear so the AI tab doesn't go generic (bp-5789).
579
+ this.aiTemplateKeywords =
580
+ payload.searchTerm || this.initialSearchTerm || '';
581
+
532
582
  this.onSearchTermChanged?.(payload.searchTerm);
533
583
 
534
584
  await this._setPageNo({ pageNo: 1 });
@@ -617,7 +667,8 @@ export const useUiTemplateSettingsStore = defineStore('uiTemplateSettings', {
617
667
  return;
618
668
  }
619
669
 
620
- // Only refetch for post-purchase users and when logo actually changes
670
+ // Refetch previews on any logo change (pre- and post-purchase) so the new logo
671
+ // shows in the thumbnails.
621
672
  const hasLogoChanged =
622
673
  newLogoImage.logoDraftToken !== oldLogoImage.logoDraftToken ||
623
674
  newLogoImage.logoVariation !== oldLogoImage.logoVariation;
@@ -627,8 +678,23 @@ export const useUiTemplateSettingsStore = defineStore('uiTemplateSettings', {
627
678
  newLogoImage.logoDraftToken &&
628
679
  hasLogoChanged
629
680
  ) {
630
- // Reset templates state and reload with new logo
631
- // Use the same pattern as onCategorySelected and onSearch
681
+ const uiStore = useUiStore();
682
+ const isPrePurchase = useBrandPageContainerStore().isPrePurchase;
683
+
684
+ uiStore.setUserPersonalisationData({
685
+ personalisationData: undefined,
686
+ });
687
+
688
+ if (
689
+ !isPrePurchase &&
690
+ newLogoImage.logoDraftToken !== oldLogoImage.logoDraftToken
691
+ ) {
692
+ uiStore.setUserPromptContentKey({
693
+ userPromptContentKey: undefined,
694
+ });
695
+ uiStore.setShowAiTemplatesTab({ showAiTemplatesTab: false });
696
+ }
697
+
632
698
  await this._setTemplates({ templates: [] });
633
699
  await this._setPageNoLoaded({ pageNoLoaded: 0 });
634
700
  await this._setPageNo({ pageNo: 1 });
@@ -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, isTiptapDocument } from '../brand-page-maker/utils';
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)) {
@@ -1,2 +0,0 @@
1
- import type { MigrationData } from '../migration.util';
2
- export declare const migration20: (migrationResult: MigrationData) => Promise<MigrationData>;
@@ -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)";
@@ -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 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 const THEME_TOKEN_REF = 'var(--logo-text-color)';
@@ -1,14 +0,0 @@
1
- /**
2
- * BP-5787 — font-family with quote-stripping parse.
3
- *
4
- * The stock extension stores whatever `element.style.fontFamily` returns. For
5
- * multi-word families (e.g. "Open Sans", "Playwrite AU") the browser quotes the
6
- * value, so parsing HTML — pasting RTE-styled text between editors, or the
7
- * HTML→JSON migration — yields a value that still contains the quote chars
8
- * (`"Open Sans"`). That value then (a) no longer matches the font list and,
9
- * worse, (b) is dropped by the native RTE sanitiser (`sanitizeRteJson`), whose
10
- * `isSafeCssValue` rejects any value containing `"` `'` `;` — so the font is
11
- * silently lost. Mirror the FontSize/LetterSpacing extensions and strip the
12
- * quotes on parse so the stored value is always the bare family name.
13
- */
14
- export declare const FontFamily: import("@tiptap/core").Extension<import("@tiptap/extension-text-style").FontFamilyOptions, any>;