@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.
Files changed (176) hide show
  1. package/lib/src/{CreatedOnBrandCrowd-de-de-CS3ct0wa.js → CreatedOnBrandCrowd-de-de-CKBqjDH3.js} +1 -1
  2. package/lib/src/{CreatedOnBrandCrowd-en-us-CmBcnDu6.js → CreatedOnBrandCrowd-en-us-CRXiimvT.js} +1 -1
  3. package/lib/src/{CreatedOnBrandCrowd-es-es-BdTuJtqV.js → CreatedOnBrandCrowd-es-es-DCYdY6sj.js} +1 -1
  4. package/lib/src/{CreatedOnBrandCrowd-fr-fr-Cx_H9X6l.js → CreatedOnBrandCrowd-fr-ca-Dk7LOhxE.js} +1 -1
  5. package/lib/src/{CreatedOnBrandCrowd-fr-ca-Cx_H9X6l.js → CreatedOnBrandCrowd-fr-fr-Dk7LOhxE.js} +1 -1
  6. package/lib/src/{CreatedOnBrandCrowd-pt-pt-BZZ9hjUi.js → CreatedOnBrandCrowd-pt-br-Cp4ufHJP.js} +1 -1
  7. package/lib/src/{CreatedOnBrandCrowd-pt-br-BZZ9hjUi.js → CreatedOnBrandCrowd-pt-pt-Cp4ufHJP.js} +1 -1
  8. package/lib/src/{CreatedOnDcom-de-de-SbONDQN1.js → CreatedOnDcom-de-de-ZSLkWMwM.js} +1 -1
  9. package/lib/src/{CreatedOnDcom-en-us-Beno1_gw.js → CreatedOnDcom-en-us-BgdIGfT3.js} +1 -1
  10. package/lib/src/{CreatedOnDcom-es-es-BBE_zdRu.js → CreatedOnDcom-es-es-D0KemkCZ.js} +1 -1
  11. package/lib/src/{CreatedOnDcom-fr-ca-C6pgDB5x.js → CreatedOnDcom-fr-ca-BuwNjNbC.js} +1 -1
  12. package/lib/src/{CreatedOnDcom-fr-fr-FD1eqtg3.js → CreatedOnDcom-fr-fr-DCQcsFPf.js} +1 -1
  13. package/lib/src/{CreatedOnDcom-pt-br-BiSx3OwM.js → CreatedOnDcom-pt-br-DD-oLAS1.js} +1 -1
  14. package/lib/src/{CreatedOnDcom-pt-pt-Dn4DmKGA.js → CreatedOnDcom-pt-pt-D8qLq2wW.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-20.d.ts +2 -0
  18. package/lib/src/brand-page/utils/migration/migration-20.js +66 -0
  19. package/lib/src/brand-page/utils/migration.util.js +11 -0
  20. package/lib/src/brand-page/utils/text.util.d.ts +1 -1
  21. package/lib/src/brand-page/utils/text.util.js +32 -0
  22. package/lib/src/brand-page/utils/vcard.util.js +4 -4
  23. package/lib/src/brand-page/utils/view-model.util.d.ts +1 -0
  24. package/lib/src/brand-page/utils/view-model.util.js +68 -20
  25. package/lib/src/brand-page/view-models.d.ts +3 -3
  26. package/lib/src/brand-page-maker/__fixtures__/complex-rte.fixture.d.ts +22 -0
  27. package/lib/src/brand-page-maker/__fixtures__/complex-rte.fixture.js +22 -0
  28. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +1 -1
  29. package/lib/src/brand-page-maker/components/seo-settings/SeoPageSettings.mixin.d.ts +1 -1
  30. package/lib/src/brand-page-maker/composables/ai-chat/useAiMetadataBuilder.d.ts +0 -1
  31. package/lib/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.js +21 -9
  32. package/lib/src/brand-page-maker/composables/rich-text-editor/bubbleMenu.d.ts +2 -9
  33. package/lib/src/brand-page-maker/composables/rich-text-editor/bubbleMenu.js +6 -10
  34. package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.d.ts +14 -0
  35. package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.js +39 -0
  36. package/lib/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.d.ts +28 -0
  37. package/lib/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.js +90 -0
  38. package/lib/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.d.ts +1 -1
  39. package/lib/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.js +24 -103
  40. package/lib/src/brand-page-maker/stores/ai-thread/ai-website-builder-thread-store.d.ts +8 -2
  41. package/lib/src/brand-page-maker/stores/ai-thread/ai-website-builder-thread-store.js +20 -0
  42. package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.js +7 -0
  43. package/lib/src/brand-page-maker/utils/index.d.ts +2 -0
  44. package/lib/src/brand-page-maker/utils/index.js +2 -0
  45. package/lib/src/brand-page-maker/utils/rich-text-editor.util.d.ts +28 -5
  46. package/lib/src/brand-page-maker/utils/rich-text-editor.util.js +48 -5
  47. package/lib/src/brand-page-maker/utils/rte-render.util.d.ts +63 -0
  48. package/lib/src/brand-page-maker/utils/rte-render.util.js +95 -0
  49. package/lib/src/brand-page-maker/utils/rte-sanitize.util.d.ts +6 -0
  50. package/lib/src/brand-page-maker/utils/rte-sanitize.util.js +158 -0
  51. package/lib/src/brand-page-maker/validators/field.validator.js +10 -2
  52. package/lib/src/css/library.brand-page-brandCrowd.min.css +39 -39
  53. package/lib/src/css/library.brand-page-brandPage.min.css +39 -39
  54. package/lib/src/css/library.brand-page-designCom.min.css +39 -39
  55. package/lib/src/css/library.brand-page-preview.min.css +39 -39
  56. package/lib/src/index.d.ts +1 -0
  57. package/lib/src/index.mjs +143 -138
  58. package/lib/src/{lib-BVHFYSLL.js → lib-Db6j9fQZ.js} +56262 -52768
  59. package/lib/src/lib.js +2 -0
  60. package/lib/src/metadata-builder/ai-metadata-executor.d.ts +0 -1
  61. package/lib/src/metadata-builder/ai-metadata-executor.js +0 -19
  62. package/lib/src/metadata-builder/builders/section-builders/about-section-builder.js +2 -3
  63. package/lib/src/metadata-builder/builders/section-builders/banner-section-builder.js +2 -3
  64. package/lib/src/metadata-builder/builders/section-builders/booking-section-builder.js +2 -3
  65. package/lib/src/metadata-builder/builders/section-builders/contact-form-builder.js +3 -4
  66. package/lib/src/metadata-builder/builders/section-builders/images-section-builder.js +2 -3
  67. package/lib/src/metadata-builder/builders/section-builders/mailing-list-builder.js +1 -2
  68. package/lib/src/metadata-builder/builders/section-builders/mailing-list-section-builder.js +1 -2
  69. package/lib/src/metadata-builder/builders/section-builders/profile-section-builder.js +3 -4
  70. package/lib/src/metadata-builder/builders/section-builders/shapes-section-builder.js +2 -3
  71. package/lib/src/metadata-builder/builders/section-builders/testimonial-section-builder.js +1 -2
  72. package/lib/src/metadata-builder/builders/section-builders/text-section-builder.js +15 -11
  73. package/lib/src/metadata-builder/helpers/rte.helpers.d.ts +11 -13
  74. package/lib/src/metadata-builder/helpers/rte.helpers.js +14 -73
  75. package/lib/src/metadata-builder/metadata-executor.js +0 -4
  76. package/lib/src/metadata-builder/models.d.ts +0 -2
  77. package/lib/src/metadata-builder/models.js +0 -2
  78. package/lib/src/metadata-builder/parsers/ai-toolcall-parser.d.ts +0 -19
  79. package/lib/src/metadata-builder/parsers/ai-toolcall-parser.js +0 -4
  80. package/lib/src/metadata-builder/sanitizers/rte-schema-check.d.ts +7 -8
  81. package/lib/src/metadata-builder/sanitizers/rte-schema-check.js +21 -52
  82. package/lib/src/shared/content-settings/components/GenericTextInput.mixin.d.ts +2 -2
  83. package/lib/src/shared/content-settings/components/GenericTextInput.mixin.js +14 -1
  84. package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.d.ts +1 -1
  85. package/lib/src/shared/content-settings/components/content-settings/ReplaceImageSettingsForContent.mixin.d.ts +1 -1
  86. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.js +12 -3
  87. package/lib/src/shared/content-settings/components/email-field/EmailField.mixin.d.ts +2 -2
  88. package/lib/src/shared/content-settings/components/radio-field/RadioField.mixin.d.ts +1 -1
  89. package/lib/src/shared/content-settings/components/slider-field/SliderField.mixin.d.ts +1 -1
  90. package/lib/src/shared/content-settings/components/tel-field/TelField.mixin.d.ts +2 -2
  91. package/lib/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.mixin.d.ts +3 -2
  92. package/lib/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.mixin.js +10 -3
  93. package/lib/src/shared/content-settings/components/text-field/TextField.mixin.d.ts +2 -2
  94. package/lib/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.d.ts +2 -1
  95. package/lib/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.js +4 -1
  96. package/lib/src/shared/content-settings/components/textarea-field/TextareaField.mixin.d.ts +2 -2
  97. package/lib/src/shared/content-settings/components/url-field/UrlField.mixin.d.ts +2 -2
  98. package/lib/src/shared/content-settings/components/youtube-video-field/YoutubeVideoField.mixin.d.ts +2 -2
  99. package/lib/src/shared/side-bar/components/SideBar.mixin.d.ts +1 -1
  100. package/lib/src/utils/minify-brand-page-metadata.js +4 -1
  101. package/package.json +17 -15
  102. package/src/brand-page/components/contact-form/ContactForm.mixin.ts +5 -2
  103. package/src/brand-page/components/text-wrapper/TextWrapper.vue +17 -6
  104. package/src/brand-page/models/index.ts +35 -26
  105. package/src/brand-page/utils/__snapshots__/migration.util.test.ts.snap +3 -3
  106. package/src/brand-page/utils/migration/migration-20.server.test.ts +125 -0
  107. package/src/brand-page/utils/migration/migration-20.test.ts +72 -0
  108. package/src/brand-page/utils/migration/migration-20.ts +87 -0
  109. package/src/brand-page/utils/migration.util.ts +12 -0
  110. package/src/brand-page/utils/style.util.test.ts +51 -0
  111. package/src/brand-page/utils/text.util.test.ts +47 -0
  112. package/src/brand-page/utils/text.util.ts +43 -4
  113. package/src/brand-page/utils/vcard.util.ts +6 -4
  114. package/src/brand-page/utils/view-model.pageid-rte.test.ts +104 -0
  115. package/src/brand-page/utils/view-model.util.ts +94 -26
  116. package/src/brand-page/view-models.ts +3 -2
  117. package/src/brand-page-maker/__fixtures__/complex-rte.fixture.ts +23 -0
  118. package/src/brand-page-maker/components/rich-text-editor/RichTextEditor.vue +4 -3
  119. package/src/brand-page-maker/composables/ai-chat/useAiMetadataBuilder.ts +0 -1
  120. package/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.ts +28 -9
  121. package/src/brand-page-maker/composables/rich-text-editor/bubbleMenu.ts +6 -10
  122. package/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.test.ts +52 -0
  123. package/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.ts +42 -0
  124. package/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.ts +137 -0
  125. package/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.ts +27 -111
  126. package/src/brand-page-maker/stores/ai-thread/ai-website-builder-thread-store.ts +23 -0
  127. package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.ts +8 -0
  128. package/src/brand-page-maker/utils/__snapshots__/rte-render-parity.test.ts.snap +202 -0
  129. package/src/brand-page-maker/utils/index.ts +2 -0
  130. package/src/brand-page-maker/utils/rich-text-editor.util.test.ts +37 -3
  131. package/src/brand-page-maker/utils/rich-text-editor.util.ts +66 -11
  132. package/src/brand-page-maker/utils/rte-features.test.ts +351 -0
  133. package/src/brand-page-maker/utils/rte-render-parity.test.ts +157 -0
  134. package/src/brand-page-maker/utils/rte-render.util.ts +165 -0
  135. package/src/brand-page-maker/utils/rte-sanitize-json.test.ts +158 -0
  136. package/src/brand-page-maker/utils/rte-sanitize.util.ts +195 -0
  137. package/src/brand-page-maker/validators/field.validator.test.ts +55 -0
  138. package/src/brand-page-maker/validators/field.validator.ts +10 -2
  139. package/src/lib.ts +9 -0
  140. package/src/metadata-builder/ai-metadata-executor.ts +0 -26
  141. package/src/metadata-builder/builders/section-builders/about-section-builder.ts +2 -3
  142. package/src/metadata-builder/builders/section-builders/banner-section-builder.ts +2 -3
  143. package/src/metadata-builder/builders/section-builders/booking-section-builder.ts +2 -5
  144. package/src/metadata-builder/builders/section-builders/contact-form-builder.ts +3 -4
  145. package/src/metadata-builder/builders/section-builders/images-section-builder.ts +2 -3
  146. package/src/metadata-builder/builders/section-builders/mailing-list-builder.ts +1 -2
  147. package/src/metadata-builder/builders/section-builders/mailing-list-section-builder.ts +1 -2
  148. package/src/metadata-builder/builders/section-builders/profile-section-builder.ts +3 -6
  149. package/src/metadata-builder/builders/section-builders/shapes-section-builder.ts +2 -3
  150. package/src/metadata-builder/builders/section-builders/testimonial-section-builder.ts +1 -2
  151. package/src/metadata-builder/builders/section-builders/text-section-builder.ts +19 -13
  152. package/src/metadata-builder/helpers/rte.helpers.ts +15 -101
  153. package/src/metadata-builder/metadata-executor.ts +0 -5
  154. package/src/metadata-builder/models.ts +0 -3
  155. package/src/metadata-builder/parsers/ai-toolcall-parser.ts +0 -27
  156. package/src/metadata-builder/sanitizers/rte-schema-check.ts +24 -53
  157. package/src/shared/content-settings/components/GenericTextInput.mixin.ts +17 -1
  158. package/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.ts +16 -3
  159. package/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.mixin.ts +13 -4
  160. package/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.vue +6 -4
  161. package/src/shared/content-settings/components/text-image-field/TextImageField.vue +6 -3
  162. package/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.ts +6 -1
  163. package/src/shared/content-settings/components/text-items-field/TextItemsField.vue +1 -1
  164. package/src/utils/minify-brand-page-metadata.ts +4 -1
  165. package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontSize.d.ts +0 -20
  166. package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontSize.js +0 -44
  167. package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/LineHeight.d.ts +0 -27
  168. package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/LineHeight.js +0 -44
  169. package/lib/src/metadata-builder/builders/update-content-text-builder.d.ts +0 -17
  170. package/lib/src/metadata-builder/builders/update-content-text-builder.js +0 -103
  171. package/lib/src/metadata-builder/builders/update-content-text-style-builder.d.ts +0 -16
  172. package/lib/src/metadata-builder/builders/update-content-text-style-builder.js +0 -216
  173. package/src/brand-page-maker/composables/rich-text-editor/extensions/FontSize.ts +0 -73
  174. package/src/brand-page-maker/composables/rich-text-editor/extensions/LineHeight.ts +0 -85
  175. package/src/metadata-builder/builders/update-content-text-builder.ts +0 -148
  176. package/src/metadata-builder/builders/update-content-text-style-builder.ts +0 -352
@@ -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 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)';
@@ -59,7 +59,7 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
59
59
  selectedTabName: import("vue").ComputedRef<string>;
60
60
  isAdmin: import("vue").ComputedRef<boolean>;
61
61
  contentErrorCount: import("vue").ComputedRef<number>;
62
- mode: import("vue").ComputedRef<"images" | "pages" | "add" | "edit" | "focusedContentItem">;
62
+ mode: import("vue").ComputedRef<"images" | "add" | "pages" | "edit" | "focusedContentItem">;
63
63
  brandPageMakerStore: import("pinia").Store<"brandPageMaker", import("@brand-page-maker/stores").BrandPageMakerState, {
64
64
  contentErrorCount(): number;
65
65
  brandPageBaseUrl(): string;
@@ -480,9 +480,9 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
480
480
  }>> & Readonly<{
481
481
  "onUpdate-settings"?: ((...args: any[]) => any) | undefined;
482
482
  }>, {
483
+ brandPageType: string;
483
484
  pageTitle: string;
484
485
  designDraftToken: string;
485
- brandPageType: string;
486
486
  pageDescription: string;
487
487
  isMaker: boolean;
488
488
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
@@ -21,7 +21,6 @@ export interface AiMetadataBuilderResult {
21
21
  }
22
22
  export declare function useAiMetadataBuilder(logger: Logger): {
23
23
  applyAiToolCallsAsync: (model: BrandPageModel, aiAssistantToolCall: AiAssistantToolCall[], options?: {
24
- maxCharacters?: number;
25
24
  /**
26
25
  * Invoked by the UndoBuilder to resolve the pre-prompt snapshot.
27
26
  * Required when the response includes an `undo` tool call.
@@ -36,18 +36,17 @@ export function useAiWebsiteBuilderChat() {
36
36
  const aiMetadataBuilder = useAiMetadataBuilder(aiLogger);
37
37
  const checkpointStore = useAiCheckpointStore();
38
38
  const eventTracker = useEventTracker();
39
+ const createWelcomeMessage = () => ({
40
+ id: '1',
41
+ type: 'text',
42
+ role: 'agent',
43
+ text: 'What changes would you like to make to your website?',
44
+ timestamp: new Date(),
45
+ });
39
46
  const hasStoredMessages = aiThreadStore.messages.length > 0;
40
47
  const threadItems = ref(aiThreadStore.messages.length > 0
41
48
  ? aiThreadStore.messages
42
- : [
43
- {
44
- id: '1',
45
- type: 'text',
46
- role: 'agent',
47
- text: 'What changes would you like to make to your website?',
48
- timestamp: new Date(),
49
- },
50
- ]);
49
+ : [createWelcomeMessage()]);
51
50
  // ---------------------------------------------------------------------------
52
51
  // Undo snapshot lifecycle (WL9) — reuses the existing `checkpointStore`,
53
52
  // which stores pre-prompt BrandPageModel snapshots keyed by user message id.
@@ -100,6 +99,19 @@ export function useAiWebsiteBuilderChat() {
100
99
  const isStopped = ref(false);
101
100
  const generatedText = ref(null);
102
101
  const generatedStyle = ref(null);
102
+ // External reset (e.g. template switch, BP-5896): tear down the build WS
103
+ // client so a late push can't repopulate the fresh thread, then re-seed
104
+ // the welcome bubble. Keyed off `resetToken` — NOT `messages` — to avoid
105
+ // looping with the deep `threadItems` → `messages` watch above.
106
+ watch(() => aiThreadStore.resetToken, () => {
107
+ if (activeBuildWsClient) {
108
+ activeBuildWsClient.disconnect();
109
+ activeBuildWsClient = null;
110
+ }
111
+ currentTypingMessageId.value = null;
112
+ isStopped.value = false;
113
+ threadItems.value = [createWelcomeMessage()];
114
+ });
103
115
  const SERVICE_UNAVAILABLE_MESSAGE = 'This service is not available right now. Please try again later.';
104
116
  const addServiceUnavailableBubble = () => {
105
117
  threadItems.value.push({
@@ -5,15 +5,8 @@ export declare const useBubbleMenu: (editor: Editor, isHyperlinkPopupOpen?: Ref<
5
5
  editor: any;
6
6
  state: any;
7
7
  }) => any;
8
- tippyOptions: {
9
- duration: number;
8
+ bubbleMenuOptions: {
10
9
  placement: "bottom";
11
- interactive: boolean;
12
- appendTo: "parent";
13
- hideOnClick: boolean;
14
- arrow: boolean;
15
- offset: [number, number];
16
- trigger: "manual";
17
- zIndex: number;
10
+ offset: number;
18
11
  };
19
12
  };
@@ -13,19 +13,15 @@ export const useBubbleMenu = (editor, isHyperlinkPopupOpen) => {
13
13
  }
14
14
  return false;
15
15
  };
16
- const tippyOptions = {
17
- duration: 100,
16
+ // v3 BubbleMenu uses Floating UI (was tippy.js in v2). Only placement + offset
17
+ // carry over; the rest of the old tippy config (trigger/interactive/hideOnClick/
18
+ // zIndex) is either the v3 default or handled in CSS.
19
+ const bubbleMenuOptions = {
18
20
  placement: 'bottom',
19
- interactive: true,
20
- appendTo: 'parent',
21
- hideOnClick: false,
22
- arrow: false,
23
- offset: [0, 10],
24
- trigger: 'manual',
25
- zIndex: 10,
21
+ offset: 10,
26
22
  };
27
23
  return {
28
24
  shouldShow,
29
- tippyOptions,
25
+ bubbleMenuOptions,
30
26
  };
31
27
  };
@@ -0,0 +1,14 @@
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>;
@@ -0,0 +1,39 @@
1
+ import TiptapFontFamily from '@tiptap/extension-font-family';
2
+ /**
3
+ * BP-5787 — font-family with quote-stripping parse.
4
+ *
5
+ * The stock extension stores whatever `element.style.fontFamily` returns. For
6
+ * multi-word families (e.g. "Open Sans", "Playwrite AU") the browser quotes the
7
+ * value, so parsing HTML — pasting RTE-styled text between editors, or the
8
+ * HTML→JSON migration — yields a value that still contains the quote chars
9
+ * (`"Open Sans"`). That value then (a) no longer matches the font list and,
10
+ * worse, (b) is dropped by the native RTE sanitiser (`sanitizeRteJson`), whose
11
+ * `isSafeCssValue` rejects any value containing `"` `'` `;` — so the font is
12
+ * silently lost. Mirror the FontSize/LetterSpacing extensions and strip the
13
+ * quotes on parse so the stored value is always the bare family name.
14
+ */
15
+ export const FontFamily = TiptapFontFamily.extend({
16
+ addGlobalAttributes() {
17
+ return [
18
+ {
19
+ types: this.options.types,
20
+ attributes: {
21
+ fontFamily: {
22
+ default: null,
23
+ // Strip quotes only — keep '' for an absent value so the stored
24
+ // JSON shape matches the other textStyle attrs (FontSize etc.).
25
+ parseHTML: (element) => element.style.fontFamily?.replace(/['"]+/g, ''),
26
+ renderHTML: (attributes) => {
27
+ if (!attributes.fontFamily) {
28
+ return {};
29
+ }
30
+ return {
31
+ style: `font-family: ${attributes.fontFamily}`,
32
+ };
33
+ },
34
+ },
35
+ },
36
+ },
37
+ ];
38
+ },
39
+ });
@@ -0,0 +1,28 @@
1
+ import type { Extensions } from '@tiptap/vue-3';
2
+ /**
3
+ * Single source of truth for the Tiptap extension set (BP-5787).
4
+ *
5
+ * The live editor, the headless serialiser (useExternalRichTextEditor), and
6
+ * the upcoming render-from-JSON path must all use the SAME extensions, or
7
+ * rendered output drifts from what the user authored. This factory removes the
8
+ * previous duplication while preserving each consumer's exact configuration.
9
+ *
10
+ * Known, intentional differences are parameterised:
11
+ * - `restrictStarterKit` — the live editor disables list/hr/code nodes.
12
+ * - `includeLinkRel` — the headless/render path emits rel="noopener noreferrer";
13
+ * the live editing surface does not.
14
+ * - `editing` — only the live editor carries the Placeholder + MaxLength plugins
15
+ * (neither affects serialised output; they are editing-time only).
16
+ */
17
+ export interface RteExtensionOptions {
18
+ /** Disable StarterKit nodes the editor doesn't use (lists, hr, code). */
19
+ restrictStarterKit?: boolean;
20
+ /** Emit rel="noopener noreferrer" on link HTMLAttributes (render path). */
21
+ includeLinkRel?: boolean;
22
+ /** When present, add the live-editing-only plugins (Placeholder, MaxLength). */
23
+ editing?: {
24
+ placeholder?: string;
25
+ maxLength?: number;
26
+ };
27
+ }
28
+ export declare const getRteExtensions: (options?: RteExtensionOptions) => Extensions;
@@ -0,0 +1,90 @@
1
+ import StarterKit from '@tiptap/starter-kit';
2
+ import Underline from '@tiptap/extension-underline';
3
+ import { TextStyle, FontSize, LineHeight } from '@tiptap/extension-text-style';
4
+ import TextAlign from '@tiptap/extension-text-align';
5
+ import ColorExtension from '@tiptap/extension-color';
6
+ import Highlight from '@tiptap/extension-highlight';
7
+ import Placeholder from '@tiptap/extension-placeholder';
8
+ import Link from '@tiptap/extension-link';
9
+ import { FontFamily } from './extensions/FontFamily';
10
+ import { LetterSpacing } from './extensions/LetterSpacing';
11
+ import { MaxLength } from './extensions/MaxLength';
12
+ import { AutoLink } from './extensions/AutoLink';
13
+ import { isHrefPageId } from './hyperlink';
14
+ export const getRteExtensions = (options = {}) => {
15
+ const { restrictStarterKit = false, includeLinkRel = false, editing, } = options;
16
+ const extensions = [
17
+ // v3 StarterKit bundles Link + Underline; we register both separately (with
18
+ // custom Link config), so disable the bundled copies to avoid duplicate
19
+ // extension errors.
20
+ // trailingNode: false — v3 StarterKit adds a TrailingNode plugin (not in v2)
21
+ // that appends an empty paragraph after any non-paragraph trailing block.
22
+ // Disabled to keep editor/stored-content behaviour identical to v2 (BP-5787
23
+ // is backward-compat-first); revisit as a standalone UX change if wanted.
24
+ restrictStarterKit
25
+ ? StarterKit.configure({
26
+ link: false,
27
+ underline: false,
28
+ trailingNode: false,
29
+ bulletList: false,
30
+ orderedList: false,
31
+ listItem: false,
32
+ horizontalRule: false,
33
+ codeBlock: false,
34
+ code: false,
35
+ })
36
+ : StarterKit.configure({
37
+ link: false,
38
+ underline: false,
39
+ trailingNode: false,
40
+ }),
41
+ Underline,
42
+ // priority: 99 — render the textStyle <span> (which carries font-size) as the
43
+ // OUTERMOST inline mark, so decoration marks (<u>/<s>) nest INSIDE it and
44
+ // inherit the applied font-size. v3's extension-text-style defaults to
45
+ // priority 101, which flips this: <u>/<s> end up outside the font-size span
46
+ // and inherit only the base paragraph size. Since text-decoration thickness
47
+ // AND vertical position are computed from the decorating element's own font,
48
+ // that produced thin, mis-centred underline/strike on large text — a
49
+ // regression vs v2's serializer, which emitted the font-size span outermost
50
+ // (BP-5787, backward-compat-first).
51
+ TextStyle.extend({ priority: 99 }).configure({
52
+ mergeNestedSpanStyles: true,
53
+ }),
54
+ TextAlign.configure({
55
+ types: ['heading', 'paragraph'],
56
+ }),
57
+ FontFamily.configure({
58
+ types: ['textStyle'],
59
+ }),
60
+ // FontSize + LineHeight are now shipped natively by @tiptap/extension-text-style.
61
+ FontSize.configure({ types: ['textStyle'] }),
62
+ LetterSpacing,
63
+ LineHeight.configure({ types: ['textStyle'] }),
64
+ ColorExtension.configure({
65
+ types: ['textStyle'],
66
+ }),
67
+ Highlight.configure({
68
+ multicolor: true,
69
+ }),
70
+ ];
71
+ // Live-editing-only plugins sit between Highlight and Link (original order).
72
+ if (editing) {
73
+ extensions.push(Placeholder.configure({ placeholder: editing.placeholder }), MaxLength.configure({ maxLength: editing.maxLength }));
74
+ }
75
+ extensions.push(Link.configure({
76
+ openOnClick: false,
77
+ defaultProtocol: 'https',
78
+ autolink: true,
79
+ HTMLAttributes: (attrs) => {
80
+ const href = attrs.href;
81
+ const isExternal = href && (href.startsWith('http://') || href.startsWith('https://'));
82
+ return {
83
+ ...(includeLinkRel && { rel: 'noopener noreferrer' }),
84
+ target: isExternal ? '_blank' : null,
85
+ };
86
+ },
87
+ isAllowedUri: (url, ctx) => ctx.defaultValidate(url) || isHrefPageId(url),
88
+ }), AutoLink);
89
+ return extensions;
90
+ };
@@ -43,7 +43,7 @@ interface UseRichTextEditorReturn {
43
43
  editor: any;
44
44
  state: any;
45
45
  }) => boolean;
46
- tippyOptions: Record<string, any>;
46
+ bubbleMenuOptions: Record<string, any>;
47
47
  handleAdminColorChange: (color: {
48
48
  id: string;
49
49
  name: string;
@@ -1,72 +1,25 @@
1
1
  import { onBeforeUnmount, computed, watch, } from 'vue';
2
2
  import { Editor } from '@tiptap/vue-3';
3
- import StarterKit from '@tiptap/starter-kit';
4
- import Underline from '@tiptap/extension-underline';
5
- import TextStyle from '@tiptap/extension-text-style';
6
- import TextAlign from '@tiptap/extension-text-align';
7
- import FontFamily from '@tiptap/extension-font-family';
8
- import ColorExtension from '@tiptap/extension-color';
9
- import { FontSize } from './extensions/FontSize';
10
- import { LetterSpacing } from './extensions/LetterSpacing';
11
- import { LineHeight } from './extensions/LineHeight';
12
- import Highlight from '@tiptap/extension-highlight';
13
- import Placeholder from '@tiptap/extension-placeholder';
14
- import Link from '@tiptap/extension-link';
3
+ import { getRteExtensions } from './getRteExtensions';
15
4
  import { createFormattingActions } from './formatting';
16
5
  import { useTextAlign } from './textAlign';
17
6
  import { useFont } from './font';
18
7
  import { useSpacing } from './spacing';
19
8
  import Color from 'color';
20
- import { buildInitialMarks, emToPx, isStyleChanged, pxToEm, sanitiseClientSideRTE, skipStyleDetection, } from '@brand-page-maker/utils';
9
+ import { buildInitialMarks, emToPx, isStyleChanged, pxToEm, sanitizeRteJson, skipStyleDetection, } from '@brand-page-maker/utils';
21
10
  import { HYPERLINK_TYPE, } from '@brand-page';
22
11
  import { letterSpacingValueConvertor, colorConvertor, backgroundColorConvertor, } from '@brand-page-maker/convertors';
23
12
  import { useUiContentSettingsStore } from '@shared/content-settings/store';
24
13
  import { useHistoryStore } from '@brand-page-maker/stores/history/history-store';
25
14
  import { AllSelection } from 'prosemirror-state';
26
- import { MaxLength } from './extensions/MaxLength';
27
- import { AutoLink } from './extensions/AutoLink';
28
- import { isHrefPageId, useHyperlink } from './hyperlink';
15
+ import { useHyperlink } from './hyperlink';
29
16
  import { useBubbleMenu } from './bubbleMenu';
30
17
  import { hasLogoColorVar, isLogoColor, getRTEColor, } from '@brand-page-maker/utils/rich-text-editor.util';
31
18
  import { useEventTracker, EVENTS } from '../useEventTracker';
32
19
  export const useExternalRichTextEditor = () => {
33
20
  const externalEditor = new Editor({
34
21
  editable: false,
35
- extensions: [
36
- StarterKit,
37
- Underline,
38
- TextStyle.configure({ mergeNestedSpanStyles: true }),
39
- TextAlign.configure({
40
- types: ['heading', 'paragraph'],
41
- }),
42
- FontFamily.configure({
43
- types: ['textStyle'],
44
- }),
45
- FontSize,
46
- LetterSpacing,
47
- LineHeight,
48
- ColorExtension.configure({
49
- types: ['textStyle'],
50
- }),
51
- Highlight.configure({
52
- multicolor: true,
53
- }),
54
- Link.configure({
55
- openOnClick: false,
56
- defaultProtocol: 'https',
57
- autolink: true,
58
- HTMLAttributes: (attrs) => {
59
- const href = attrs.href;
60
- const isExternal = href && (href.startsWith('http://') || href.startsWith('https://'));
61
- return {
62
- rel: 'noopener noreferrer',
63
- target: isExternal ? '_blank' : null,
64
- };
65
- },
66
- isAllowedUri: (url, ctx) => ctx.defaultValidate(url) || isHrefPageId(url),
67
- }),
68
- AutoLink,
69
- ],
22
+ extensions: getRteExtensions({ includeLinkRel: true }),
70
23
  });
71
24
  return {
72
25
  externalEditor,
@@ -77,51 +30,13 @@ export const useRichTextEditor = (model, props, emit, fontSettings, fontStyleDro
77
30
  const editor = new Editor({
78
31
  content: model.value ?? '',
79
32
  editable: !props.readOnly,
80
- extensions: [
81
- StarterKit.configure({
82
- bulletList: false,
83
- orderedList: false,
84
- listItem: false,
85
- horizontalRule: false,
86
- codeBlock: false,
87
- code: false,
88
- }),
89
- Underline,
90
- TextStyle.configure({ mergeNestedSpanStyles: true }),
91
- TextAlign.configure({
92
- types: ['heading', 'paragraph'],
93
- }),
94
- FontFamily.configure({
95
- types: ['textStyle'],
96
- }),
97
- FontSize,
98
- LetterSpacing,
99
- LineHeight,
100
- ColorExtension.configure({
101
- types: ['textStyle'],
102
- }),
103
- Highlight.configure({
104
- multicolor: true,
105
- }),
106
- Placeholder.configure({
33
+ extensions: getRteExtensions({
34
+ restrictStarterKit: true,
35
+ editing: {
107
36
  placeholder: props.placeholder,
108
- }),
109
- MaxLength.configure({ maxLength: props.maxLength }),
110
- Link.configure({
111
- openOnClick: false,
112
- defaultProtocol: 'https',
113
- autolink: true,
114
- HTMLAttributes: (attrs) => {
115
- const href = attrs.href;
116
- const isExternal = href && (href.startsWith('http://') || href.startsWith('https://'));
117
- return {
118
- target: isExternal ? '_blank' : null,
119
- };
120
- },
121
- isAllowedUri: (url, ctx) => ctx.defaultValidate(url) || isHrefPageId(url),
122
- }),
123
- AutoLink,
124
- ],
37
+ maxLength: props.maxLength,
38
+ },
39
+ }),
125
40
  editorProps: {
126
41
  handleDOMEvents: {
127
42
  drop(view, event) {
@@ -224,7 +139,7 @@ export const useRichTextEditor = (model, props, emit, fontSettings, fontStyleDro
224
139
  const { selectedFont, toggleFont, toggleFontSize, toggleFontAll, toggleFontSizeAll, } = useFont(editor, fontSettings, fontStyleDropdownRef);
225
140
  const { letterSpacingValue, lineHeightValue, letterSpacing, lineHeight, setLetterSpaceAll, setLineHeightAll, } = useSpacing(editor);
226
141
  const { hyperlinkValue, setHyperlinkUrl, setHyperlinkUrlValue, removeHyperlink: removeHyperlinkFromComposable, applyHyperlinkToEditor, } = useHyperlink(editor);
227
- const { shouldShow, tippyOptions } = useBubbleMenu(editor, isHyperlinkPopupOpen);
142
+ const { shouldShow, bubbleMenuOptions } = useBubbleMenu(editor, isHyperlinkPopupOpen);
228
143
  watch(() => props.styles.textAlign, (newAlignment) => {
229
144
  if (newAlignment && newAlignment !== currentAlignment.value.alignment) {
230
145
  skipStyleDetection(editor.chain().selectAll().setTextAlign(newAlignment)).run();
@@ -239,8 +154,12 @@ export const useRichTextEditor = (model, props, emit, fontSettings, fontStyleDro
239
154
  const appliedSinceLast = historyStore.snapshotAppliedToken !== lastAppliedToken;
240
155
  lastAppliedToken = historyStore.snapshotAppliedToken;
241
156
  const isExternalMutation = updateRichTextEditor.value || appliedSinceLast;
242
- if (isExternalMutation && editor.getHTML() !== newValue) {
243
- editor.commands.setContent(newValue, false);
157
+ // Compare in the stored (JSON) format so a JSON model value isn't always
158
+ // seen as "changed" against the editor's HTML (which would re-run
159
+ // setContent and reset the user's just-applied styles).
160
+ const hasChanged = JSON.stringify(editor.getJSON()) !== JSON.stringify(newValue);
161
+ if (isExternalMutation && hasChanged) {
162
+ editor.commands.setContent(newValue, { emitUpdate: false });
244
163
  updateRichTextEditor.value = false;
245
164
  //text style type is not switching when reordering
246
165
  textStyleTypeOptions.forEach((item) => {
@@ -267,11 +186,13 @@ export const useRichTextEditor = (model, props, emit, fontSettings, fontStyleDro
267
186
  });
268
187
  }
269
188
  });
270
- // Update model when editor content changes
189
+ // Update model when editor content changes. RTE content is stored as Tiptap
190
+ // JSON (BP-5787). getJSON() is already in hand (the editor holds the doc) and
191
+ // is sanitised natively — no getHTML(), no DOMPurify, no extra editor — so
192
+ // the per-keystroke path allocates nothing heavy.
271
193
  editor.on('update', () => {
272
- const sanitizedData = sanitiseClientSideRTE(editor.getHTML());
273
194
  // An update fired while the editor is unfocused is TipTap rewriting the
274
- // model back into its canonical HTML during mount rehydration or a
195
+ // model back into its canonical form during mount rehydration or a
275
196
  // programmatic setContent — not a user edit. Tag the emit so the write
276
197
  // folds into the current undo head instead of pushing a phantom entry
277
198
  // (BP-5810). The flag travels WITH the value through the debounced
@@ -285,7 +206,7 @@ export const useRichTextEditor = (model, props, emit, fontSettings, fontStyleDro
285
206
  // the RTE only mounts inside an already-open settings drawer and the user
286
207
  // focuses it before formatting, so this doesn't occur — revisit if a
287
208
  // consumer ever drives toolbar actions on an unfocused editor.
288
- emit('update:modelValue', sanitizedData.sanitizedHtml, {
209
+ emit('update:modelValue', sanitizeRteJson(editor.getJSON()), {
289
210
  isHydrationWrite: !editor.isFocused,
290
211
  });
291
212
  });
@@ -725,7 +646,7 @@ export const useRichTextEditor = (model, props, emit, fontSettings, fontStyleDro
725
646
  hyperlinkValue,
726
647
  setHyperlinkUrlValue,
727
648
  shouldShow,
728
- tippyOptions,
649
+ bubbleMenuOptions,
729
650
  handleAdminColorChange,
730
651
  };
731
652
  };
@@ -8,7 +8,9 @@ export declare const useAiWebsiteBuilderThreadStore: import("pinia").StoreDefini
8
8
  buildSectionsCompleted: import("vue").Ref<number, number>;
9
9
  promptText: import("vue").Ref<string, string>;
10
10
  stopGeneration: import("vue").Ref<(() => void) | null, (() => void) | null>;
11
- }, "threadId" | "runId" | "messages" | "isProcessing" | "buildId" | "isBuildInProgress" | "buildSectionsCompleted" | "promptText" | "stopGeneration">, Pick<{
11
+ resetToken: import("vue").Ref<number, number>;
12
+ reset: () => void;
13
+ }, "threadId" | "runId" | "messages" | "isProcessing" | "buildId" | "isBuildInProgress" | "buildSectionsCompleted" | "promptText" | "stopGeneration" | "resetToken">, Pick<{
12
14
  threadId: import("vue").Ref<string | null, string | null>;
13
15
  runId: import("vue").Ref<string | null, string | null>;
14
16
  messages: import("vue").Ref<any[], any[]>;
@@ -18,6 +20,8 @@ export declare const useAiWebsiteBuilderThreadStore: import("pinia").StoreDefini
18
20
  buildSectionsCompleted: import("vue").Ref<number, number>;
19
21
  promptText: import("vue").Ref<string, string>;
20
22
  stopGeneration: import("vue").Ref<(() => void) | null, (() => void) | null>;
23
+ resetToken: import("vue").Ref<number, number>;
24
+ reset: () => void;
21
25
  }, never>, Pick<{
22
26
  threadId: import("vue").Ref<string | null, string | null>;
23
27
  runId: import("vue").Ref<string | null, string | null>;
@@ -28,4 +32,6 @@ export declare const useAiWebsiteBuilderThreadStore: import("pinia").StoreDefini
28
32
  buildSectionsCompleted: import("vue").Ref<number, number>;
29
33
  promptText: import("vue").Ref<string, string>;
30
34
  stopGeneration: import("vue").Ref<(() => void) | null, (() => void) | null>;
31
- }, never>>;
35
+ resetToken: import("vue").Ref<number, number>;
36
+ reset: () => void;
37
+ }, "reset">>;
@@ -10,6 +10,24 @@ export const useAiWebsiteBuilderThreadStore = defineStore('aiWebsiteBuilderThrea
10
10
  const buildSectionsCompleted = ref(0);
11
11
  const promptText = ref('');
12
12
  const stopGeneration = ref(null);
13
+ // Bumped on every reset so consumers (useAiWebsiteBuilderChat) can react
14
+ // without watching `messages`, which would loop with its own deep watch.
15
+ const resetToken = ref(0);
16
+ const reset = () => {
17
+ if (isProcessing.value || isBuildInProgress.value) {
18
+ stopGeneration.value?.();
19
+ }
20
+ threadId.value = null;
21
+ runId.value = null;
22
+ messages.value = [];
23
+ isProcessing.value = false;
24
+ buildId.value = null;
25
+ isBuildInProgress.value = false;
26
+ buildSectionsCompleted.value = 0;
27
+ promptText.value = '';
28
+ stopGeneration.value = null;
29
+ resetToken.value += 1;
30
+ };
13
31
  return {
14
32
  threadId,
15
33
  runId,
@@ -20,5 +38,7 @@ export const useAiWebsiteBuilderThreadStore = defineStore('aiWebsiteBuilderThrea
20
38
  buildSectionsCompleted,
21
39
  promptText,
22
40
  stopGeneration,
41
+ resetToken,
42
+ reset,
23
43
  };
24
44
  });
@@ -1,6 +1,8 @@
1
1
  import { EMPTY_LOGO_IMAGE_MODEL, SENSITIVE_CONTENT_TYPES, TEXT_STYLE_TYPES, VIEWPORTS, } from '@brand-page';
2
2
  import { BackgroundStyles, BRAND_PAGE_TYPES, BrandPageModel, CONTENT_TYPES, NavigationDesktop, NavigationMobile, } from '@brand-page';
3
3
  import { useBrandPageMakerUserStore } from '../user';
4
+ import { useAiWebsiteBuilderThreadStore } from '../ai-thread/ai-website-builder-thread-store';
5
+ import { useAiCheckpointStore } from '../ai-checkpoint/ai-checkpoint-store';
4
6
  import { defineStore } from 'pinia';
5
7
  import { toRaw } from 'vue';
6
8
  import { useUiStore } from '../ui';
@@ -353,6 +355,11 @@ export const useBrandPageMakerStore = defineStore('brandPageMaker', {
353
355
  backgroundStyles: brandPageModel.styles.backgroundStyles,
354
356
  });
355
357
  await this.recalculateLogoSizes(true);
358
+ // A template switch invalidates the AI conversation — the server-side
359
+ // thread's history still describes the previous template (BP-5896).
360
+ // reset() also stops any in-flight generation before clearing.
361
+ useAiWebsiteBuilderThreadStore().reset();
362
+ useAiCheckpointStore().clearAll();
356
363
  this.setIsModifiedAfterTemplateApplied({
357
364
  isModified: false,
358
365
  });
@@ -5,4 +5,6 @@ export * from './url.util';
5
5
  export * from './slug.util';
6
6
  export * from './text.util';
7
7
  export * from './rich-text-editor.util';
8
+ export * from './rte-render.util';
9
+ export * from './rte-sanitize.util';
8
10
  export declare const isColorMatched: (color: string | null | undefined) => boolean;
@@ -6,6 +6,8 @@ export * from './url.util';
6
6
  export * from './slug.util';
7
7
  export * from './text.util';
8
8
  export * from './rich-text-editor.util';
9
+ export * from './rte-render.util';
10
+ export * from './rte-sanitize.util';
9
11
  export const isColorMatched = (color) => {
10
12
  if (!color)
11
13
  return false;