@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
@@ -1,73 +1,42 @@
1
- import { Editor } from '@tiptap/vue-3';
2
- import StarterKit from '@tiptap/starter-kit';
3
- import Underline from '@tiptap/extension-underline';
4
- import TextStyle from '@tiptap/extension-text-style';
5
- import TextAlign from '@tiptap/extension-text-align';
6
- import FontFamily from '@tiptap/extension-font-family';
7
- import ColorExtension from '@tiptap/extension-color';
8
- import Highlight from '@tiptap/extension-highlight';
9
- import Link from '@tiptap/extension-link';
10
- import { FontSize } from '../../brand-page-maker/composables/rich-text-editor/extensions/FontSize';
11
- import { LetterSpacing } from '../../brand-page-maker/composables/rich-text-editor/extensions/LetterSpacing';
12
- import { LineHeight } from '../../brand-page-maker/composables/rich-text-editor/extensions/LineHeight';
13
- import { AutoLink } from '../../brand-page-maker/composables/rich-text-editor/extensions/AutoLink';
1
+ import { getSchema } from '@tiptap/core';
2
+ import { getRteExtensions } from '../../brand-page-maker/composables/rich-text-editor/getRteExtensions';
14
3
  import { isTiptapDocument } from '../parsers/section-tool-arguments';
15
4
  import { rteStyleCheck, textCheck, } from './section-args-validator';
16
5
  /**
17
- * Constructs a headless Tiptap Editor with the same extension set as
18
- * `useExternalRichTextEditor` in the maker. We import the Tiptap extension
19
- * files directly (they are leaf modules) instead of going through the maker
20
- * composable, which would pull in the AI executor → section-builders →
21
- * section-rules graph and re-enter this file before its exports are bound.
6
+ * The ProseMirror schema built from the SAME extension set the editor and
7
+ * renderer use (`getRteExtensions`), so validation matches real editor output.
8
+ * `getSchema` is DOM-less (no Editor instance) and the schema is pure, so it is
9
+ * built once and cached.
10
+ *
11
+ * We import `getRteExtensions` (a leaf module — @tiptap + prosemirror only), not
12
+ * the maker composable (`useExternalRichTextEditor`), which would pull in the
13
+ * AI executor → section-builders → section-rules graph and re-enter this file
14
+ * before its exports are bound.
22
15
  */
23
- const buildHeadlessEditor = () => new Editor({
24
- editable: false,
25
- extensions: [
26
- StarterKit,
27
- Underline,
28
- TextStyle.configure({ mergeNestedSpanStyles: true }),
29
- TextAlign.configure({ types: ['heading', 'paragraph'] }),
30
- FontFamily.configure({ types: ['textStyle'] }),
31
- FontSize,
32
- LetterSpacing,
33
- LineHeight,
34
- ColorExtension.configure({ types: ['textStyle'] }),
35
- Highlight.configure({ multicolor: true }),
36
- Link.configure({
37
- openOnClick: false,
38
- defaultProtocol: 'https',
39
- autolink: true,
40
- }),
41
- AutoLink,
42
- ],
43
- });
16
+ let cachedSchema;
17
+ const getRteSchema = () => (cachedSchema ??= getSchema(getRteExtensions({ includeLinkRel: true })));
44
18
  /**
45
- * Validates a Tiptap doc against the headless editor's real ProseMirror
46
- * schema (`schema.nodeFromJSON`). Catches malformed AI input — stray
47
- * non-node children in `content`, unknown node types, malformed attrs —
48
- * before the builder runs and the renderer's silent `setContent` drops
49
- * the doc to `<p></p>` (BP-5669).
19
+ * Validates a Tiptap doc against the real ProseMirror schema
20
+ * (`schema.nodeFromJSON`). Catches malformed AI input — stray non-node children
21
+ * in `content`, unknown node types, malformed attrs — before the builder runs
22
+ * and the renderer's silent `setContent` drops the doc to `<p></p>` (BP-5669).
50
23
  *
51
- * Returns the field-prefixed error string so the AI's tool-call retry
52
- * loop reads it via `BuilderResult.errors` → `tool_result.errorMessage`
53
- * and re-emits a corrected payload.
24
+ * Returns the field-prefixed error string so the AI's tool-call retry loop reads
25
+ * it via `BuilderResult.errors` → `tool_result.errorMessage` and re-emits a
26
+ * corrected payload.
54
27
  */
55
28
  export const rteSchemaCheck = (field) => (record) => {
56
29
  const value = record[field];
57
30
  if (!isTiptapDocument(value))
58
31
  return null;
59
- const editor = buildHeadlessEditor();
60
32
  try {
61
- editor.schema.nodeFromJSON(value);
33
+ getRteSchema().nodeFromJSON(value);
62
34
  return null;
63
35
  }
64
36
  catch (err) {
65
37
  const detail = err instanceof Error ? err.message : String(err);
66
38
  return `"${field}" is not valid Tiptap JSON: ${detail}`;
67
39
  }
68
- finally {
69
- editor.destroy();
70
- }
71
40
  };
72
41
  /**
73
42
  * Returns [rteSchemaCheck(field), textCheck(field, max), rteStyleCheck(field)].
@@ -185,12 +185,12 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
185
185
  }>, {
186
186
  index: number;
187
187
  settings: ContentItemFieldSettings;
188
- role: string;
188
+ value: string | false | undefined;
189
189
  styles: Record<string, any>;
190
+ role: string;
190
191
  placeholder: string;
191
192
  minLength: number;
192
193
  maxLength: number;
193
- value: string | false | undefined;
194
194
  attrs: Record<string, any>;
195
195
  isRequired: boolean;
196
196
  isToggled: boolean;
@@ -158,7 +158,20 @@ export default defineComponent({
158
158
  },
159
159
  },
160
160
  watch: {
161
- value(newVal) {
161
+ value(newVal, oldVal) {
162
+ // BP-5787: RTE values are Tiptap JSON objects. The parent (TextItemsField)
163
+ // re-clones its items array (cloneDeep) on ANY change — including a sibling
164
+ // field's style sync — yielding a NEW object reference with identical text
165
+ // content. For objects Vue fires this watcher on reference change, so that
166
+ // spurious re-clone would overwrite `_value` and clobber an in-flight edit
167
+ // (a mark applied but not yet debounced to the store), silently dropping
168
+ // the user's formatting. Strings never hit this (same content = same
169
+ // reference = no fire), so mirror that: ignore re-clones whose content is
170
+ // unchanged from the previous prop value.
171
+ if (this.showRichTextEditor &&
172
+ JSON.stringify(newVal) === JSON.stringify(oldVal)) {
173
+ return;
174
+ }
162
175
  this._value = newVal;
163
176
  if (this.showRichTextEditor) {
164
177
  const textValue = escapeContentText(this._value);
@@ -6,7 +6,7 @@ interface CategoryTab {
6
6
  label: string;
7
7
  }
8
8
  declare const _default: import("vue").DefineComponent<{}, {
9
- mode: import("vue").ComputedRef<"images" | "pages" | "add" | "edit" | "focusedContentItem">;
9
+ mode: import("vue").ComputedRef<"images" | "add" | "pages" | "edit" | "focusedContentItem">;
10
10
  pages: import("vue").ComputedRef<{
11
11
  id: string;
12
12
  title: string;
@@ -34,7 +34,7 @@ declare const _default: import("vue").DefineComponent<{}, {
34
34
  selectedItemIndex: import("vue").ComputedRef<number | null>;
35
35
  selectedItemValue: import("vue").ComputedRef<string | null>;
36
36
  contentType: import("vue").ComputedRef<"website" | "image" | "email" | "phone" | "logo" | "general-link" | "profile" | "video" | "text" | "facebook" | "x" | "instagram" | "youtube" | "linkedin" | "pinterest" | "twitch" | "tiktok" | "map" | "image-gallery" | "mailing-list" | "request-payment" | "support-me" | "contact-form" | "banner-section" | "about-section" | "divider-section" | "images-section" | "testimonial-section" | "social-links" | "shapes-section" | "shop-section" | "booking-section" | undefined>;
37
- mode: import("vue").ComputedRef<"images" | "pages" | "add" | "edit" | "focusedContentItem">;
37
+ mode: import("vue").ComputedRef<"images" | "add" | "pages" | "edit" | "focusedContentItem">;
38
38
  isAdmin: import("vue").ComputedRef<boolean>;
39
39
  contentSettingsStore: import("pinia").Store<"uiContentSettings", import("../../store").ContentSettingsState, {
40
40
  contentErrorCount(): number;
@@ -8,7 +8,7 @@ import { SIDE_BAR_TAB_NAMES } from '@brand-page-maker/constants';
8
8
  import { ITEM_FIELD_TYPES } from '@brand-page-maker/models';
9
9
  import { useUiPageSettingsStore } from '../../../page-settings/store';
10
10
  import { PAGE_SECTIONS } from '../../../page-settings/models';
11
- import { getPlainTextFromRTE, replaceTextInRTE, RTE_ALLOWED_SECTION_TYPES, isRTEManagedStyleField, } from '@brand-page-maker/utils';
11
+ import { getPlainTextFromRTE, replaceTextInRTE, RTE_ALLOWED_SECTION_TYPES, isRTEManagedStyleField, isTiptapDocument, renderRteToHtml, toRteJson, } from '@brand-page-maker/utils';
12
12
  import { isDefaultContentText } from '@shared/content-settings/utils';
13
13
  import { useUiLogoSettingsStore } from '../../../logo-settings/store';
14
14
  const RTE_DISALLOWED_FIELD_NAMES = ['buttonText'];
@@ -267,8 +267,17 @@ export default defineComponent({
267
267
  },
268
268
  onUseGeneratedText(generatedText) {
269
269
  let formattedText = generatedText;
270
- if (this.showRichTextEditor && typeof this.value === 'string') {
271
- formattedText = replaceTextInRTE(this.value, generatedText);
270
+ // BP-5787: preserve the field's formatting when dropping in AI text.
271
+ // replaceTextInRTE is HTML-based, so JSON is rendered/parsed around it
272
+ // (cold AI path, shared editor); string fields use it directly.
273
+ if (this.showRichTextEditor) {
274
+ if (isTiptapDocument(this.value)) {
275
+ formattedText =
276
+ toRteJson(replaceTextInRTE(renderRteToHtml(this.value), generatedText)) ?? generatedText;
277
+ }
278
+ else if (typeof this.value === 'string') {
279
+ formattedText = replaceTextInRTE(this.value, generatedText);
280
+ }
272
281
  }
273
282
  this.contentSettingsStore.updateItemValue({
274
283
  id: this.id,
@@ -182,12 +182,12 @@ declare const _default: import("vue").DefineComponent<{}, {}, {}, {}, {}, import
182
182
  }>, {
183
183
  index: number;
184
184
  settings: import("../../models").ContentItemFieldSettings;
185
- role: string;
185
+ value: string | false | undefined;
186
186
  styles: Record<string, any>;
187
+ role: string;
187
188
  placeholder: string;
188
189
  minLength: number;
189
190
  maxLength: number;
190
- value: string | false | undefined;
191
191
  attrs: Record<string, any>;
192
192
  isRequired: boolean;
193
193
  isToggled: boolean;
@@ -107,9 +107,9 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
107
107
  "onUpdate:modelValue"?: ((...args: any[]) => any) | undefined;
108
108
  }>, {
109
109
  fields: Record<string, MultiFieldOption>;
110
+ value: string | boolean | Record<string, any>;
110
111
  placeholder: string;
111
112
  variant: string;
112
- value: string | boolean | Record<string, any>;
113
113
  multiple: boolean;
114
114
  optionsConvertor: OptionsConvertor<unknown>;
115
115
  isRequired: boolean;
@@ -69,9 +69,9 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
69
69
  }>> & Readonly<{
70
70
  "onUpdate:modelValue"?: ((...args: any[]) => any) | undefined;
71
71
  }>, {
72
+ value: string;
72
73
  minLength: number;
73
74
  maxLength: number;
74
75
  valueConvertor: ValueConvertor<BrandPageCssStyle, number, BrandPageCssStyle>;
75
- value: string;
76
76
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
77
77
  export default _default;
@@ -182,12 +182,12 @@ declare const _default: import("vue").DefineComponent<{}, {}, {}, {}, {}, import
182
182
  }>, {
183
183
  index: number;
184
184
  settings: import("../../models").ContentItemFieldSettings;
185
- role: string;
185
+ value: string | false | undefined;
186
186
  styles: Record<string, any>;
187
+ role: string;
187
188
  placeholder: string;
188
189
  minLength: number;
189
190
  maxLength: number;
190
- value: string | false | undefined;
191
191
  attrs: Record<string, any>;
192
192
  isRequired: boolean;
193
193
  isToggled: boolean;
@@ -1,5 +1,5 @@
1
1
  import type { PropType } from 'vue';
2
- import type { ColorReplacement, Content, LogoTheme, TestimonialItems } from '@brand-page';
2
+ import type { ColorReplacement, Content, LogoTheme, RichTextValue, TestimonialItems } from '@brand-page';
3
3
  import type { DebounceHandle } from 'lodash.debounce';
4
4
  import type { UpdateItemValuePayload } from '../../models';
5
5
  declare const _default: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
@@ -64,7 +64,8 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
64
64
  name: string;
65
65
  value: string;
66
66
  }, index: number): void;
67
- getItemTextPreview(text: string): string;
67
+ getItemTextPreview(text: RichTextValue): string;
68
+ rteTextLength(value: RichTextValue): number;
68
69
  getPlainTextValue(index: number): string;
69
70
  onCaptureSnapshot(): void;
70
71
  }, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("update:modelValue" | "capture-preview-snapshot" | "on-intent-image-replace")[], "update:modelValue" | "capture-preview-snapshot" | "on-intent-image-replace", import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
@@ -3,7 +3,7 @@ import { EMPTY_TESTIMONIAL, MAX_DESCRIPTION_LENGTH, MAX_LENGTH_ERROR_MESSAGE, MA
3
3
  import debounce from 'lodash.debounce';
4
4
  import cloneDeep from 'lodash.clonedeep';
5
5
  import { useSnapshotStore } from '@brand-page-maker/stores';
6
- import { getPlainTextFromRTE } from '@brand-page-maker/utils';
6
+ import { getPlainTextFromRTE, getRteTextLength } from '@brand-page-maker/utils';
7
7
  import { isDefaultContentText } from '../../utils';
8
8
  import { decodeHtmlEntities } from '@brand-page/utils';
9
9
  import { ESCAPED_NEWLINE_CHARACTER } from '../../../../constants/ui';
@@ -73,7 +73,9 @@ export default defineComponent({
73
73
  },
74
74
  created() {
75
75
  this.options = cloneDeep(this.value);
76
- this.isEmpty = this.options.map((option) => option.testimonials.trim().length === 0);
76
+ // BP-5787: testimonials may be Tiptap JSON (migrated legacy content), so
77
+ // read visible text via getPlainTextFromRTE rather than `.trim()` on a doc.
78
+ this.isEmpty = this.options.map((option) => getPlainTextFromRTE(option.testimonials).length === 0);
77
79
  if (!this.changeDebounce) {
78
80
  this.changeDebounce = debounce(this.emitChange, DEBOUNCE_IN_MS);
79
81
  }
@@ -160,7 +162,12 @@ export default defineComponent({
160
162
  this.emitChange();
161
163
  },
162
164
  getItemTextPreview(text) {
163
- return decodeHtmlEntities(text.replace(new RegExp(ESCAPED_NEWLINE_CHARACTER, 'g'), ' '));
165
+ // BP-5787: a JSON doc has no `.replace` — take its visible text first.
166
+ const value = typeof text === 'string' ? text : getPlainTextFromRTE(text);
167
+ return decodeHtmlEntities(value.replace(new RegExp(ESCAPED_NEWLINE_CHARACTER, 'g'), ' '));
168
+ },
169
+ rteTextLength(value) {
170
+ return getRteTextLength(value);
164
171
  },
165
172
  getPlainTextValue(index) {
166
173
  const text = this.options[index]?.testimonials || '';
@@ -182,12 +182,12 @@ declare const _default: import("vue").DefineComponent<{}, {}, {}, {}, {}, import
182
182
  }>, {
183
183
  index: number;
184
184
  settings: import("../../models").ContentItemFieldSettings;
185
- role: string;
185
+ value: string | false | undefined;
186
186
  styles: Record<string, any>;
187
+ role: string;
187
188
  placeholder: string;
188
189
  minLength: number;
189
190
  maxLength: number;
190
- value: string | false | undefined;
191
191
  attrs: Record<string, any>;
192
192
  isRequired: boolean;
193
193
  isToggled: boolean;
@@ -1,5 +1,5 @@
1
1
  import type { PropType } from 'vue';
2
- import type { Content, LogoTheme, TextItem, TextStyleDeclaration } from '@brand-page/models';
2
+ import type { Content, LogoTheme, RichTextValue, TextItem, TextStyleDeclaration } from '@brand-page/models';
3
3
  import type { DebounceHandle } from 'lodash.debounce';
4
4
  declare const _default: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
5
5
  id: {
@@ -79,6 +79,7 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
79
79
  name: string;
80
80
  value: string;
81
81
  }, index: number): void;
82
+ rteTextLength(value: RichTextValue): number;
82
83
  getPlainTextValue(index: number): string;
83
84
  onCaptureSnapshot(): void;
84
85
  }, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("capture-preview-snapshot" | "update:model-value")[], "capture-preview-snapshot" | "update:model-value", import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
@@ -4,7 +4,7 @@ import debounce from 'lodash.debounce';
4
4
  import cloneDeep from 'lodash.clonedeep';
5
5
  import { decodeHtmlEntities, escapeContentText } from '@brand-page/utils';
6
6
  import { useSnapshotStore } from '@brand-page-maker/stores';
7
- import { getPlainTextFromRTE } from '@brand-page-maker/utils';
7
+ import { getPlainTextFromRTE, getRteTextLength } from '@brand-page-maker/utils';
8
8
  import { isDefaultContentText } from '../../utils';
9
9
  const DEBOUNCE_IN_MS = 250;
10
10
  export default defineComponent({
@@ -172,6 +172,9 @@ export default defineComponent({
172
172
  this.options[index][payload.name] = payload.value;
173
173
  this.emitChange();
174
174
  },
175
+ rteTextLength(value) {
176
+ return getRteTextLength(value);
177
+ },
175
178
  getPlainTextValue(index) {
176
179
  const text = this.options[index]?.text || '';
177
180
  const plainText = getPlainTextFromRTE(text);
@@ -196,12 +196,12 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
196
196
  }>, {
197
197
  index: number;
198
198
  settings: import("../../models").ContentItemFieldSettings;
199
- role: string;
199
+ value: string | false | undefined;
200
200
  styles: Record<string, any>;
201
+ role: string;
201
202
  placeholder: string;
202
203
  minLength: number;
203
204
  maxLength: number;
204
- value: string | false | undefined;
205
205
  attrs: Record<string, any>;
206
206
  isRequired: boolean;
207
207
  isToggled: boolean;
@@ -189,12 +189,12 @@ declare const _default: import("vue").DefineComponent<{}, {}, Record<string, any
189
189
  }>, {
190
190
  index: number;
191
191
  settings: import("../../models").ContentItemFieldSettings;
192
- role: string;
192
+ value: string | false | undefined;
193
193
  styles: Record<string, any>;
194
+ role: string;
194
195
  placeholder: string;
195
196
  minLength: number;
196
197
  maxLength: number;
197
- value: string | false | undefined;
198
198
  attrs: Record<string, any>;
199
199
  isRequired: boolean;
200
200
  isToggled: boolean;
@@ -185,12 +185,12 @@ declare const _default: import("vue").DefineComponent<{}, {}, Record<string, any
185
185
  }>, {
186
186
  index: number;
187
187
  settings: import("../../models").ContentItemFieldSettings;
188
- role: string;
188
+ value: string | false | undefined;
189
189
  styles: Record<string, any>;
190
+ role: string;
190
191
  placeholder: string;
191
192
  minLength: number;
192
193
  maxLength: number;
193
- value: string | false | undefined;
194
194
  attrs: Record<string, any>;
195
195
  isRequired: boolean;
196
196
  isToggled: boolean;
@@ -286,7 +286,7 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
286
286
  }[]>;
287
287
  selectedTabName: import("vue").ComputedRef<string>;
288
288
  isAdmin: import("vue").ComputedRef<boolean>;
289
- contentSettingsMode: import("vue").ComputedRef<"images" | "pages" | "add" | "edit" | "focusedContentItem">;
289
+ contentSettingsMode: import("vue").ComputedRef<"images" | "add" | "pages" | "edit" | "focusedContentItem">;
290
290
  contentSettingsSelectedItemId: import("vue").ComputedRef<string | null>;
291
291
  sideBarStore: import("pinia").Store<"uiSideBar", import("../store").SideBarState, {}, {
292
292
  setBrandPageSettingsTabVisibility(payload: {
@@ -1,5 +1,5 @@
1
1
  import { BRAND_PAGE_TYPES, } from '../brand-page/models';
2
- import { hasRTEContent } from '../brand-page-maker/utils';
2
+ import { hasRTEContent, isTiptapDocument } from '../brand-page-maker/utils';
3
3
  import { useExternalRichTextEditor } from '../brand-page-maker/composables/rich-text-editor/useRichTextEditor';
4
4
  import { findAssetKey, resolveLogoData, } from './build-asset-manifest';
5
5
  const BUTTON_TYPES = [
@@ -17,6 +17,9 @@ const BUTTON_TYPES = [
17
17
  'tiktok',
18
18
  ];
19
19
  const convertText = (value, editor) => {
20
+ // Already Tiptap JSON (BP-5787 storage) — keep as-is.
21
+ if (isTiptapDocument(value))
22
+ return value;
20
23
  if (typeof value !== 'string' || !value)
21
24
  return undefined;
22
25
  if (hasRTEContent(value)) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@designcrowd/library.brand-page",
3
3
  "description": "A collection of Maker/Designer applications",
4
- "version": "6.0.25",
4
+ "version": "6.0.26-json-prereq-2",
5
5
  "author": "Design.com Eng",
6
6
  "license": "UNLICENSED",
7
7
  "type": "module",
@@ -74,19 +74,20 @@
74
74
  "@designcrowd/fe-shared-lib": "2.0.7",
75
75
  "@pinia/nuxt": "0.11.3",
76
76
  "@storybook/react": "9.0.4",
77
- "@tiptap/extension-bubble-menu": "^2.14.0",
78
- "@tiptap/extension-character-count": "2.11.7",
79
- "@tiptap/extension-color": "2.11.5",
80
- "@tiptap/extension-font-family": "2.11.5",
81
- "@tiptap/extension-hard-break": "2.12.0",
82
- "@tiptap/extension-highlight": "2.11.5",
83
- "@tiptap/extension-link": "2.11.5",
84
- "@tiptap/extension-placeholder": "2.11.5",
85
- "@tiptap/extension-text-align": "2.11.5",
86
- "@tiptap/extension-text-style": "2.11.5",
87
- "@tiptap/extension-underline": "2.11.5",
88
- "@tiptap/starter-kit": "2.11.5",
89
- "@tiptap/vue-3": "2.11.5",
77
+ "@tiptap/extension-bubble-menu": "3.28.0",
78
+ "@tiptap/extension-character-count": "3.28.0",
79
+ "@tiptap/extension-color": "3.28.0",
80
+ "@tiptap/extension-font-family": "3.28.0",
81
+ "@tiptap/extension-hard-break": "3.28.0",
82
+ "@tiptap/extension-highlight": "3.28.0",
83
+ "@tiptap/extension-link": "3.28.0",
84
+ "@tiptap/extension-placeholder": "3.28.0",
85
+ "@tiptap/extension-text-align": "3.28.0",
86
+ "@tiptap/extension-text-style": "3.28.0",
87
+ "@tiptap/extension-underline": "3.28.0",
88
+ "@tiptap/starter-kit": "3.28.0",
89
+ "@tiptap/static-renderer": "3.28.0",
90
+ "@tiptap/vue-3": "3.28.0",
90
91
  "axios": "1.13.5",
91
92
  "chroma-js": "2.4.2",
92
93
  "click-outside-vue3": "4.0.1",
@@ -104,6 +105,7 @@
104
105
  "http-proxy-middleware": "2.0.6",
105
106
  "i18next": "25.8.5",
106
107
  "jquery": "3.7.1",
108
+ "jsdom": "27.4.0",
107
109
  "lodash.clonedeep": "4.5.0",
108
110
  "lodash.debounce": "4.0.8",
109
111
  "lodash.throttle": "4.1.1",
@@ -160,6 +162,7 @@
160
162
  "@types/google.maps": "3.65.1",
161
163
  "@types/istanbul-lib-report": "3.0.3",
162
164
  "@types/jquery": "3.5.33",
165
+ "@types/jsdom": "21.1.6",
163
166
  "@types/lodash.clonedeep": "4.5.9",
164
167
  "@types/mssql": "9.1.9",
165
168
  "@types/multer": "1.4.11",
@@ -198,7 +201,6 @@
198
201
  "imports-loader": "5.0.0",
199
202
  "jest-image-snapshot": "6.5.1",
200
203
  "js-base64": "3.7.8",
201
- "jsdom": "27.4.0",
202
204
  "jsdom-global": "3.0.2",
203
205
  "lint-staged": "15.2.0",
204
206
  "mini-css-extract-plugin": "2.10.0",
@@ -32,7 +32,7 @@ import {
32
32
  mutedTextForBackground,
33
33
  backgroundColorFromStyle,
34
34
  } from '@brand-page/utils';
35
- import { hasRTEContent } from '@brand-page-maker/utils';
35
+ import { hasRTEContent, getPlainTextFromRTE } from '@brand-page-maker/utils';
36
36
  import { useReCaptcha } from '../../composables/recaptcha.composable';
37
37
 
38
38
  export interface FieldValue {
@@ -180,7 +180,10 @@ export default defineComponent({
180
180
  },
181
181
 
182
182
  title(): string {
183
- return this.viewModel.title && this.viewModel.title.trim() !== ''
183
+ // BP-5787: title may be RTE JSON (not a string), so `.trim()` would
184
+ // throw — read the visible text via getPlainTextFromRTE to decide
185
+ // whether to fall back to the default.
186
+ return getPlainTextFromRTE(this.viewModel.title)
184
187
  ? this.viewModel.title
185
188
  : 'Contact Us';
186
189
  },
@@ -21,7 +21,13 @@
21
21
  </div>
22
22
  </template>
23
23
  <script setup lang="ts">
24
- import { hasRTEContent, getPlainTextFromRTE } from '@brand-page-maker/utils';
24
+ import {
25
+ hasRTEContent,
26
+ getPlainTextFromRTE,
27
+ renderRteToHtml,
28
+ isTiptapDocument,
29
+ type RichTextValue,
30
+ } from '@brand-page-maker/utils';
25
31
  import {
26
32
  type FluidTextData,
27
33
  type TextStyleDeclaration,
@@ -37,7 +43,8 @@ import {
37
43
 
38
44
  interface Props {
39
45
  textStyle: TextStyleDeclaration | undefined;
40
- text: string;
46
+ // BP-5787: an RTE field is a legacy inline-HTML string or a Tiptap JSON doc.
47
+ text: RichTextValue;
41
48
  element?: string;
42
49
  slug?: string;
43
50
  }
@@ -79,16 +86,20 @@ const textStyleRTE = computed(() => {
79
86
  };
80
87
  });
81
88
 
89
+ // Render either form (legacy HTML string or Tiptap JSON) to final HTML.
90
+ // For a string this returns it unchanged, so the legacy path is unaffected.
91
+ const renderedHtml = computed(() => renderRteToHtml(props.text));
92
+
82
93
  const isRTEContent = computed(() => {
83
- return hasRTEContent(props.text);
94
+ return isTiptapDocument(props.text) || hasRTEContent(renderedHtml.value);
84
95
  });
85
96
 
86
97
  const sanitizeText = computed(() => {
87
- if (isRTEContent.value && !getPlainTextFromRTE(props.text)) {
98
+ if (isRTEContent.value && !getPlainTextFromRTE(renderedHtml.value)) {
88
99
  return '';
89
100
  }
90
101
 
91
- return props.text;
102
+ return renderedHtml.value;
92
103
  });
93
104
 
94
105
  const fontSize = computed<any>(() => props.textStyle.fontSize);
@@ -160,7 +171,7 @@ const applyScale = () => {
160
171
  }
161
172
 
162
173
  const fontSizeMatchInRTEText = [
163
- ...props.text.matchAll(
174
+ ...renderedHtml.value.matchAll(
164
175
  /style="(?:[^"]*?)\bfont-size\s*:\s*(?<fontSize>[^;"']+)[^"]*?"/gm,
165
176
  ),
166
177
  ].map((x) => parseFloat(x.groups?.fontSize || '1em'));