@designcrowd/library.brand-page 6.0.6-json-prereq-7 → 6.0.6-tracker-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 (99) hide show
  1. package/lib/src/{CreatedOnBrandCrowd-de-de-SPVLPi8n.js → CreatedOnBrandCrowd-de-de-DD0OBnij.js} +1 -1
  2. package/lib/src/{CreatedOnBrandCrowd-en-us-CvtrCPHd.js → CreatedOnBrandCrowd-en-us-DysrpsmM.js} +1 -1
  3. package/lib/src/{CreatedOnBrandCrowd-es-es-BSsac7jB.js → CreatedOnBrandCrowd-es-es-B6twGr9h.js} +1 -1
  4. package/lib/src/{CreatedOnBrandCrowd-fr-fr-BauENV2a.js → CreatedOnBrandCrowd-fr-ca-yxl0ih_4.js} +1 -1
  5. package/lib/src/{CreatedOnBrandCrowd-fr-ca-BauENV2a.js → CreatedOnBrandCrowd-fr-fr-yxl0ih_4.js} +1 -1
  6. package/lib/src/{CreatedOnBrandCrowd-pt-pt-Be0hawmr.js → CreatedOnBrandCrowd-pt-br-BtXDrIcx.js} +1 -1
  7. package/lib/src/{CreatedOnBrandCrowd-pt-br-Be0hawmr.js → CreatedOnBrandCrowd-pt-pt-BtXDrIcx.js} +1 -1
  8. package/lib/src/{CreatedOnDcom-de-de-CTjE2Tmn.js → CreatedOnDcom-de-de-ho_ROy1L.js} +1 -1
  9. package/lib/src/{CreatedOnDcom-en-us-DnZRp5Pg.js → CreatedOnDcom-en-us-C91p9kls.js} +1 -1
  10. package/lib/src/{CreatedOnDcom-es-es-J-9qvjy8.js → CreatedOnDcom-es-es-B3FYmObE.js} +1 -1
  11. package/lib/src/{CreatedOnDcom-fr-ca-BDNHqm4G.js → CreatedOnDcom-fr-ca-D40m7fPx.js} +1 -1
  12. package/lib/src/{CreatedOnDcom-fr-fr-B_2-ceuO.js → CreatedOnDcom-fr-fr-Cd57oYyc.js} +1 -1
  13. package/lib/src/{CreatedOnDcom-pt-br-76lJVrAY.js → CreatedOnDcom-pt-br-Du57mWxG.js} +1 -1
  14. package/lib/src/{CreatedOnDcom-pt-pt-TQp3CNcu.js → CreatedOnDcom-pt-pt-fYoPZWwr.js} +1 -1
  15. package/lib/src/brand-page/models/index.d.ts +26 -34
  16. package/lib/src/brand-page/utils/migration.util.js +0 -11
  17. package/lib/src/brand-page/utils/text.util.d.ts +1 -1
  18. package/lib/src/brand-page/utils/text.util.js +0 -32
  19. package/lib/src/brand-page/utils/vcard.util.js +4 -4
  20. package/lib/src/brand-page/utils/view-model.util.d.ts +0 -1
  21. package/lib/src/brand-page/utils/view-model.util.js +20 -68
  22. package/lib/src/brand-page/view-models.d.ts +3 -3
  23. package/lib/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.js +99 -20
  24. package/lib/src/brand-page-maker/utils/index.d.ts +0 -2
  25. package/lib/src/brand-page-maker/utils/index.js +0 -2
  26. package/lib/src/brand-page-maker/utils/rich-text-editor.util.d.ts +5 -19
  27. package/lib/src/brand-page-maker/utils/rich-text-editor.util.js +5 -35
  28. package/lib/src/brand-page-maker/validators/field.validator.js +2 -10
  29. package/lib/src/css/library.brand-page-brandCrowd.min.css +1 -1
  30. package/lib/src/css/library.brand-page-brandPage.min.css +1 -1
  31. package/lib/src/css/library.brand-page-designCom.min.css +1 -1
  32. package/lib/src/css/library.brand-page-preview.min.css +1 -1
  33. package/lib/src/index.d.ts +0 -1
  34. package/lib/src/index.mjs +137 -141
  35. package/lib/src/{lib-Bh9Y5_-x.js → lib-lMNy66QO.js} +43580 -44184
  36. package/lib/src/lib.js +0 -2
  37. package/lib/src/metadata-builder/builders/update-content-text-builder.js +15 -20
  38. package/lib/src/metadata-builder/builders/update-content-text-style-builder.js +3 -5
  39. package/lib/src/metadata-builder/helpers/rte.helpers.d.ts +1 -1
  40. package/lib/src/metadata-builder/helpers/rte.helpers.js +3 -6
  41. package/lib/src/shared/content-settings/components/GenericTextInput.mixin.js +1 -14
  42. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.js +3 -12
  43. package/lib/src/tracker.d.ts +2 -0
  44. package/lib/src/tracker.js +12 -0
  45. package/lib/src/tracker.mjs +12 -0
  46. package/lib/src/useEventTracker-DmQ6AkNn.js +356 -0
  47. package/lib/src/utils/minify-brand-page-metadata.js +1 -4
  48. package/package.json +16 -1
  49. package/src/brand-page/components/text-wrapper/TextWrapper.vue +6 -17
  50. package/src/brand-page/models/index.ts +26 -35
  51. package/src/brand-page/utils/__snapshots__/migration.util.test.ts.snap +3 -3
  52. package/src/brand-page/utils/migration.util.ts +0 -12
  53. package/src/brand-page/utils/style.util.test.ts +0 -51
  54. package/src/brand-page/utils/text.util.ts +4 -43
  55. package/src/brand-page/utils/vcard.util.ts +4 -6
  56. package/src/brand-page/utils/view-model.util.ts +26 -94
  57. package/src/brand-page/view-models.ts +2 -3
  58. package/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.ts +106 -22
  59. package/src/brand-page-maker/utils/index.ts +0 -2
  60. package/src/brand-page-maker/utils/rich-text-editor.util.test.ts +3 -4
  61. package/src/brand-page-maker/utils/rich-text-editor.util.ts +11 -50
  62. package/src/brand-page-maker/validators/field.validator.ts +2 -10
  63. package/src/lib.ts +0 -9
  64. package/src/metadata-builder/builders/update-content-text-builder.ts +18 -32
  65. package/src/metadata-builder/builders/update-content-text-style-builder.ts +4 -6
  66. package/src/metadata-builder/helpers/rte.helpers.ts +7 -11
  67. package/src/shared/content-settings/components/GenericTextInput.mixin.ts +1 -17
  68. package/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.ts +3 -16
  69. package/src/tracker.ts +28 -0
  70. package/src/utils/minify-brand-page-metadata.ts +1 -4
  71. package/lib/src/brand-page/utils/migration/migration-18.d.ts +0 -2
  72. package/lib/src/brand-page/utils/migration/migration-18.js +0 -71
  73. package/lib/src/brand-page-maker/__fixtures__/complex-rte.fixture.d.ts +0 -22
  74. package/lib/src/brand-page-maker/__fixtures__/complex-rte.fixture.js +0 -22
  75. package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.d.ts +0 -14
  76. package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.js +0 -39
  77. package/lib/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.d.ts +0 -28
  78. package/lib/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.js +0 -66
  79. package/lib/src/brand-page-maker/utils/rte-render.util.d.ts +0 -33
  80. package/lib/src/brand-page-maker/utils/rte-render.util.js +0 -50
  81. package/lib/src/brand-page-maker/utils/rte-sanitize.util.d.ts +0 -6
  82. package/lib/src/brand-page-maker/utils/rte-sanitize.util.js +0 -158
  83. package/lib/src/brand-page-maker/utils/rte-serializer.d.ts +0 -5
  84. package/lib/src/brand-page-maker/utils/rte-serializer.js +0 -112
  85. package/src/brand-page/utils/migration/migration-18.test.ts +0 -72
  86. package/src/brand-page/utils/migration/migration-18.ts +0 -83
  87. package/src/brand-page/utils/text.util.test.ts +0 -47
  88. package/src/brand-page/utils/view-model.pageid-rte.test.ts +0 -104
  89. package/src/brand-page-maker/__fixtures__/complex-rte.fixture.ts +0 -23
  90. package/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.test.ts +0 -52
  91. package/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.ts +0 -42
  92. package/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.ts +0 -113
  93. package/src/brand-page-maker/utils/__snapshots__/rte-render-parity.test.ts.snap +0 -200
  94. package/src/brand-page-maker/utils/rte-render-parity.test.ts +0 -156
  95. package/src/brand-page-maker/utils/rte-render.util.ts +0 -77
  96. package/src/brand-page-maker/utils/rte-sanitize-json.test.ts +0 -158
  97. package/src/brand-page-maker/utils/rte-sanitize.util.ts +0 -195
  98. package/src/brand-page-maker/utils/rte-serializer.ts +0 -172
  99. package/src/brand-page-maker/validators/field.validator.test.ts +0 -55
@@ -8,7 +8,18 @@ import {
8
8
  watch,
9
9
  } from 'vue';
10
10
  import { Editor } from '@tiptap/vue-3';
11
- import { getRteExtensions } from './getRteExtensions';
11
+ import StarterKit from '@tiptap/starter-kit';
12
+ import Underline from '@tiptap/extension-underline';
13
+ import TextStyle from '@tiptap/extension-text-style';
14
+ import TextAlign from '@tiptap/extension-text-align';
15
+ import FontFamily from '@tiptap/extension-font-family';
16
+ import ColorExtension from '@tiptap/extension-color';
17
+ import { FontSize } from './extensions/FontSize';
18
+ import { LetterSpacing } from './extensions/LetterSpacing';
19
+ import { LineHeight } from './extensions/LineHeight';
20
+ import Highlight from '@tiptap/extension-highlight';
21
+ import Placeholder from '@tiptap/extension-placeholder';
22
+ import Link from '@tiptap/extension-link';
12
23
  import type { StyleTextFontFamilyOption } from '@shared/style-settings/models';
13
24
  import { createFormattingActions } from './formatting';
14
25
  import { useTextAlign } from './textAlign';
@@ -20,7 +31,7 @@ import {
20
31
  emToPx,
21
32
  isStyleChanged,
22
33
  pxToEm,
23
- sanitizeRteJson,
34
+ sanitiseClientSideRTE,
24
35
  skipStyleDetection,
25
36
  } from '@brand-page-maker/utils';
26
37
  import type { HyperlinkType, TextStyles } from '@brand-page';
@@ -38,7 +49,9 @@ import {
38
49
  import { useUiContentSettingsStore } from '@shared/content-settings/store';
39
50
  import { useHistoryStore } from '@brand-page-maker/stores/history/history-store';
40
51
  import { AllSelection } from 'prosemirror-state';
41
- import { useHyperlink } from './hyperlink';
52
+ import { MaxLength } from './extensions/MaxLength';
53
+ import { AutoLink } from './extensions/AutoLink';
54
+ import { isHrefPageId, useHyperlink } from './hyperlink';
42
55
  import { useBubbleMenu } from './bubbleMenu';
43
56
  import {
44
57
  hasLogoColorVar,
@@ -116,7 +129,44 @@ export const useExternalRichTextEditor = (): {
116
129
  } => {
117
130
  const externalEditor = new Editor({
118
131
  editable: false,
119
- extensions: getRteExtensions({ includeLinkRel: true }),
132
+ extensions: [
133
+ StarterKit,
134
+ Underline,
135
+ TextStyle.configure({ mergeNestedSpanStyles: true }),
136
+ TextAlign.configure({
137
+ types: ['heading', 'paragraph'],
138
+ }),
139
+ FontFamily.configure({
140
+ types: ['textStyle'],
141
+ }),
142
+ FontSize,
143
+ LetterSpacing,
144
+ LineHeight,
145
+ ColorExtension.configure({
146
+ types: ['textStyle'],
147
+ }),
148
+ Highlight.configure({
149
+ multicolor: true,
150
+ }),
151
+ Link.configure({
152
+ openOnClick: false,
153
+ defaultProtocol: 'https',
154
+ autolink: true,
155
+ HTMLAttributes: (attrs) => {
156
+ const href = attrs.href;
157
+ const isExternal =
158
+ href && (href.startsWith('http://') || href.startsWith('https://'));
159
+
160
+ return {
161
+ rel: 'noopener noreferrer',
162
+ target: isExternal ? '_blank' : null,
163
+ };
164
+ },
165
+ isAllowedUri: (url, ctx) =>
166
+ ctx.defaultValidate(url) || isHrefPageId(url),
167
+ }),
168
+ AutoLink,
169
+ ],
120
170
  });
121
171
 
122
172
  return {
@@ -168,13 +218,54 @@ export const useRichTextEditor = (
168
218
  const editor = new Editor({
169
219
  content: model.value ?? '',
170
220
  editable: !props.readOnly,
171
- extensions: getRteExtensions({
172
- restrictStarterKit: true,
173
- editing: {
221
+ extensions: [
222
+ StarterKit.configure({
223
+ bulletList: false,
224
+ orderedList: false,
225
+ listItem: false,
226
+ horizontalRule: false,
227
+ codeBlock: false,
228
+ code: false,
229
+ }),
230
+ Underline,
231
+ TextStyle.configure({ mergeNestedSpanStyles: true }),
232
+ TextAlign.configure({
233
+ types: ['heading', 'paragraph'],
234
+ }),
235
+ FontFamily.configure({
236
+ types: ['textStyle'],
237
+ }),
238
+ FontSize,
239
+ LetterSpacing,
240
+ LineHeight,
241
+ ColorExtension.configure({
242
+ types: ['textStyle'],
243
+ }),
244
+ Highlight.configure({
245
+ multicolor: true,
246
+ }),
247
+ Placeholder.configure({
174
248
  placeholder: props.placeholder,
175
- maxLength: props.maxLength,
176
- },
177
- }),
249
+ }),
250
+ MaxLength.configure({ maxLength: props.maxLength }),
251
+ Link.configure({
252
+ openOnClick: false,
253
+ defaultProtocol: 'https',
254
+ autolink: true,
255
+ HTMLAttributes: (attrs) => {
256
+ const href = attrs.href;
257
+ const isExternal =
258
+ href && (href.startsWith('http://') || href.startsWith('https://'));
259
+
260
+ return {
261
+ target: isExternal ? '_blank' : null,
262
+ };
263
+ },
264
+ isAllowedUri: (url, ctx) =>
265
+ ctx.defaultValidate(url) || isHrefPageId(url),
266
+ }),
267
+ AutoLink,
268
+ ],
178
269
  editorProps: {
179
270
  handleDOMEvents: {
180
271
  drop(view, event: DragEvent) {
@@ -385,13 +476,7 @@ export const useRichTextEditor = (
385
476
 
386
477
  const isExternalMutation = updateRichTextEditor.value || appliedSinceLast;
387
478
 
388
- // Compare in the stored (JSON) format so a JSON model value isn't always
389
- // seen as "changed" against the editor's HTML (which would re-run
390
- // setContent and reset the user's just-applied styles).
391
- const hasChanged =
392
- JSON.stringify(editor.getJSON()) !== JSON.stringify(newValue);
393
-
394
- if (isExternalMutation && hasChanged) {
479
+ if (isExternalMutation && editor.getHTML() !== newValue) {
395
480
  editor.commands.setContent(newValue, false);
396
481
  updateRichTextEditor.value = false;
397
482
  //text style type is not switching when reordering
@@ -414,12 +499,11 @@ export const useRichTextEditor = (
414
499
  }
415
500
  });
416
501
 
417
- // Update model when editor content changes. RTE content is stored as Tiptap
418
- // JSON (BP-5787). getJSON() is already in hand (the editor holds the doc) and
419
- // is sanitised natively — no getHTML(), no DOMPurify, no extra editor — so
420
- // the per-keystroke path allocates nothing heavy.
502
+ // Update model when editor content changes
421
503
  editor.on('update', () => {
422
- emit('update:modelValue', sanitizeRteJson(editor.getJSON()));
504
+ const sanitizedData = sanitiseClientSideRTE(editor.getHTML());
505
+
506
+ emit('update:modelValue', sanitizedData.sanitizedHtml);
423
507
  });
424
508
 
425
509
  editor.on('selectionUpdate', () => {
@@ -7,8 +7,6 @@ export * from './url.util';
7
7
  export * from './slug.util';
8
8
  export * from './text.util';
9
9
  export * from './rich-text-editor.util';
10
- export * from './rte-render.util';
11
- export * from './rte-sanitize.util';
12
10
 
13
11
  export const isColorMatched = (color: string | null | undefined): boolean => {
14
12
  if (!color) return false;
@@ -76,7 +76,7 @@ describe('hasHTML', () => {
76
76
  });
77
77
 
78
78
  describe('getUpdatedHTMLFromEditor', () => {
79
- it('applies style commands and returns updated JSON', () => {
79
+ it('applies style commands and returns updated HTML', () => {
80
80
  // Mock editor
81
81
  const runMock = vi.fn();
82
82
  const chain = {
@@ -119,7 +119,7 @@ describe('getUpdatedHTMLFromEditor', () => {
119
119
  },
120
120
  },
121
121
  chain: () => chain as any,
122
- getJSON: () => ({ type: 'doc', content: [{ type: 'paragraph' }] }),
122
+ getHTML: () => '<div>styled</div>',
123
123
  } as unknown as Editor;
124
124
 
125
125
  const newStyle = {
@@ -137,8 +137,7 @@ describe('getUpdatedHTMLFromEditor', () => {
137
137
 
138
138
  expect(externalEditor.commands.setContent).toHaveBeenCalledWith('foo');
139
139
  expect(runMock).toHaveBeenCalled();
140
- // BP-5787: returns the JSON form (consistent with JSON storage).
141
- expect(result).toEqual({ type: 'doc', content: [{ type: 'paragraph' }] });
140
+ expect(result).toBe('<div>styled</div>');
142
141
  });
143
142
  });
144
143
 
@@ -1,4 +1,4 @@
1
- import type { ChainedCommands, Editor, JSONContent } from '@tiptap/vue-3';
1
+ import type { ChainedCommands, Editor } from '@tiptap/vue-3';
2
2
  import type { EditorState, Transaction, Selection } from 'prosemirror-state';
3
3
  import {
4
4
  STYLE_THEME_KEYS,
@@ -19,7 +19,6 @@ import {
19
19
  type RequestPaymentContent,
20
20
  type SupportMeContent,
21
21
  type ShopContent,
22
- type RichTextValue,
23
22
  } from '@brand-page';
24
23
  import type { PageSettingsState } from '@shared/page-settings/store';
25
24
  import type { StyleSettingsState } from '@shared/style-settings/store';
@@ -244,38 +243,8 @@ export const skipStyleDetection = (chain: ChainedCommands, marks?: Mark[]) =>
244
243
  return true;
245
244
  });
246
245
 
247
- /**
248
- * BP-5787 — canonical discriminator for the JSON RTE model: a ProseMirror
249
- * document is an object with `type: 'doc'`.
250
- */
251
- export const isTiptapDocument = (value: unknown): value is JSONContent =>
252
- typeof value === 'object' &&
253
- value !== null &&
254
- (value as JSONContent).type === 'doc';
255
-
256
- /**
257
- * Extract visible plain text from a Tiptap JSON document (concatenated text
258
- * nodes). Dependency-free — no editor instantiation.
259
- */
260
- export const getRteJsonPlainText = (doc: JSONContent): string => {
261
- const parts: string[] = [];
262
-
263
- const walk = (node: JSONContent): void => {
264
- if (typeof node.text === 'string') parts.push(node.text);
265
-
266
- node.content?.forEach(walk);
267
- };
268
-
269
- walk(doc);
270
-
271
- return parts.join(' ').replace(/\s+/g, ' ').trim();
272
- };
273
-
274
- // Accepts a legacy inline-HTML string or a Tiptap JSON document (BP-5787).
275
- export const hasRTEContent = (content: unknown): boolean => {
276
- if (isTiptapDocument(content)) return true;
277
-
278
- if (typeof content !== 'string' || content.length === 0) return false;
246
+ export const hasRTEContent = (content: string): boolean => {
247
+ if (!content || content.length === 0) return false;
279
248
 
280
249
  const trimmed = content.trim();
281
250
  const tagWrapRegex = /^<([A-Za-z][A-Za-z0-9-]*)\b[^>]*>[\s\S]*<\/\1>$/;
@@ -283,11 +252,7 @@ export const hasRTEContent = (content: unknown): boolean => {
283
252
  return tagWrapRegex.test(trimmed);
284
253
  };
285
254
 
286
- export const getPlainTextFromRTE = (content: unknown): string => {
287
- if (isTiptapDocument(content)) {
288
- return getRteJsonPlainText(content);
289
- }
290
-
255
+ export const getPlainTextFromRTE = (content: string): string => {
291
256
  if (typeof content !== 'string') {
292
257
  return '';
293
258
  }
@@ -756,20 +721,16 @@ export const switchAllContentToRTE = (
756
721
 
757
722
  export const getUpdatedHTMLFromEditor = (
758
723
  externalEditor: Editor,
759
- contentText: RichTextValue,
724
+ contentText: string,
760
725
  newContentStyle: TextStyleDeclaration,
761
726
  ) => {
762
727
  if (!contentText) {
763
728
  return contentText;
764
729
  }
765
730
 
766
- // BP-5787: accept a string (legacy HTML) or a Tiptap JSON doc. setContent
767
- // handles both; only the newline-escaping shim applies to strings.
768
- const content =
769
- typeof contentText === 'string' &&
770
- contentText.includes(ESCAPED_NEWLINE_CHARACTER)
771
- ? getRenderText(contentText)
772
- : contentText;
731
+ const content = contentText.includes(ESCAPED_NEWLINE_CHARACTER)
732
+ ? getRenderText(contentText)
733
+ : contentText;
773
734
 
774
735
  externalEditor.commands.setContent(content);
775
736
 
@@ -827,9 +788,9 @@ export const getUpdatedHTMLFromEditor = (
827
788
  chain = newContentStyle?.fontStyle ? chain.setItalic() : chain.unsetItalic();
828
789
 
829
790
  chain.run();
791
+ const html = externalEditor.getHTML();
830
792
 
831
- // BP-5787: return the JSON form, consistent with how RTE is now stored.
832
- return externalEditor.getJSON();
793
+ return html;
833
794
  };
834
795
 
835
796
  // Helper to build stored marks from a style declaration
@@ -953,7 +914,7 @@ export const sanitiseClientSideRTE = (html: string): SanitizationResult => {
953
914
  /**
954
915
  * Validates if an href value is safe and returns a sanitized version
955
916
  */
956
- export const validateAndSanitizeHref = (href: string): string | null => {
917
+ const validateAndSanitizeHref = (href: string): string | null => {
957
918
  if (!href || typeof href !== 'string') return null;
958
919
 
959
920
  // Trim whitespace and decode entities
@@ -1,6 +1,6 @@
1
1
  import { isURL, isEmail, isTel } from '@utils/validator.util';
2
2
  import type { ItemFieldType, Validator } from '@brand-page-maker/models';
3
- import { YOUTUBE_URL_RE, isTiptapDocument } from '@brand-page-maker/utils';
3
+ import { YOUTUBE_URL_RE } from '@brand-page-maker/utils';
4
4
  import { validatorTr } from '@src/composables/useTranslate';
5
5
 
6
6
  const addressValueValidator: Validator = {
@@ -76,15 +76,7 @@ export const dummyValidator: Validator = {
76
76
 
77
77
  const nonEmptyStringValidator: Validator = {
78
78
  validate: (name, value: any) => {
79
- // BP-5787/BP-5857: match legacy (pre-JSON) validation semantics.
80
- // An RTE field's stored value used to be inline HTML — an empty editor was
81
- // "<p></p>", a NON-empty string, so the required check always passed for RTE
82
- // fields (it only ever failed for genuinely empty PLAIN inputs). Now RTE
83
- // fields are Tiptap JSON docs; treat any doc as "present" so behaviour
84
- // stays identical. A plain string is still required to be non-empty.
85
- const isValid = isTiptapDocument(value)
86
- ? true
87
- : typeof value === 'string' && value.length > 0;
79
+ const isValid = typeof value === 'string' && value.length > 0;
88
80
 
89
81
  return {
90
82
  isValid,
package/src/lib.ts CHANGED
@@ -85,15 +85,6 @@ export {
85
85
  recalculateHeaderLogoSizes,
86
86
  } from './brand-page/utils/logo.util';
87
87
  export { sanitizeRTEHtml } from './brand-page-maker/utils';
88
- // BP-5787: RTE JSON storage boundary helpers (render/normalise/sanitise/extract).
89
- export {
90
- renderRteToHtml,
91
- toRteJson,
92
- isTiptapDocument,
93
- sanitizeRteJson,
94
- getRteJsonPlainText,
95
- type RichTextValue,
96
- } from './brand-page-maker/utils';
97
88
  export { getPageSlugFromPageTitle } from './brand-page-maker/utils/slug.util';
98
89
 
99
90
  // AI-pipeline preview (longclaw per-turn snapshots): web-renderer's
@@ -6,36 +6,7 @@ import {
6
6
  type BuilderResult,
7
7
  type MetadataBuilderRequest,
8
8
  } from '../models';
9
- import {
10
- hasRTEContent,
11
- replaceTextInRTE,
12
- isTiptapDocument,
13
- renderRteToHtml,
14
- toRteJson,
15
- } from '../../brand-page-maker/utils';
16
-
17
- /**
18
- * BP-5787 — replace the text of an RTE field while preserving formatting, for
19
- * either storage form. JSON is rendered to HTML, edited via the existing
20
- * HTML-preserving replace, then converted back to JSON; plain/legacy strings
21
- * keep the original behaviour.
22
- */
23
- const updateRteFieldValue = (existing: unknown, newText: string): unknown => {
24
- const wasJson = isTiptapDocument(existing);
25
- const existingHtml = wasJson ? renderRteToHtml(existing) : existing;
26
-
27
- if (
28
- existingHtml &&
29
- typeof existingHtml === 'string' &&
30
- hasRTEContent(existingHtml)
31
- ) {
32
- const updatedHtml = replaceTextInRTE(existingHtml, newText);
33
-
34
- return wasJson ? toRteJson(updatedHtml) : updatedHtml;
35
- }
36
-
37
- return newText;
38
- };
9
+ import { hasRTEContent, replaceTextInRTE } from '../../brand-page-maker/utils';
39
10
 
40
11
  export interface UpdateTextPayload {
41
12
  contentId: string;
@@ -65,7 +36,12 @@ const updateTextField = (target: any, fieldName: string, newText: string) => {
65
36
 
66
37
  if (Array.isArray(array) && array[+index]) {
67
38
  const existingText = array[+index][property];
68
- const updatedValue = updateRteFieldValue(existingText, newText);
39
+ const updatedValue =
40
+ existingText &&
41
+ typeof existingText === 'string' &&
42
+ hasRTEContent(existingText)
43
+ ? replaceTextInRTE(existingText, newText)
44
+ : newText;
69
45
 
70
46
  // Create a new array with the updated item to trigger reactivity
71
47
  const newArray = [...array];
@@ -78,7 +54,17 @@ const updateTextField = (target: any, fieldName: string, newText: string) => {
78
54
  }
79
55
  } else {
80
56
  // Simple field
81
- target[fieldName] = updateRteFieldValue(target[fieldName], newText);
57
+ const existingText = target[fieldName];
58
+
59
+ if (
60
+ existingText &&
61
+ typeof existingText === 'string' &&
62
+ hasRTEContent(existingText)
63
+ ) {
64
+ target[fieldName] = replaceTextInRTE(existingText, newText);
65
+ } else {
66
+ target[fieldName] = newText;
67
+ }
82
68
  }
83
69
  };
84
70
 
@@ -68,19 +68,17 @@ const mergeStyles = (
68
68
  return merged;
69
69
  };
70
70
 
71
- // BP-5787 — apply a style to RTE text natively. applyStyleToRTEText runs Tiptap
72
- // commands on the doc (string or JSON in) and returns JSON — no HTML round-trip.
73
- const updateRTEText = (text: unknown, style: TextStyleDeclaration): unknown => {
74
- if (!hasRTEContent(text)) {
71
+ const updateRTEText = (text: string, style: TextStyleDeclaration): string => {
72
+ if (!text || typeof text !== 'string' || !hasRTEContent(text)) {
75
73
  return text;
76
74
  }
77
75
 
78
76
  const { externalEditor } = useExternalRichTextEditor();
79
- const updated = applyStyleToRTEText(externalEditor, text, style);
77
+ const updatedText = applyStyleToRTEText(externalEditor, text, style);
80
78
 
81
79
  externalEditor.destroy();
82
80
 
83
- return updated;
81
+ return updatedText;
84
82
  };
85
83
 
86
84
  const findStyleKey = (target: Content, property: string): string | null => {
@@ -11,23 +11,18 @@ import {
11
11
 
12
12
  export const applyStyleToRTEText = (
13
13
  externalEditor: Editor,
14
- contentText: unknown,
14
+ contentText: string,
15
15
  newContentStyle: TextStyleDeclaration,
16
16
  ) => {
17
17
  if (!contentText) {
18
18
  return contentText;
19
19
  }
20
20
 
21
- // BP-5787: operate natively on the Tiptap doc. setContent accepts a JSON
22
- // document or an HTML string; the style is applied via editor commands and
23
- // the result is returned as JSON — no HTML round-trip.
24
- const content =
25
- typeof contentText === 'string' &&
26
- contentText.includes(ESCAPED_NEWLINE_CHARACTER)
27
- ? getRenderText(contentText)
28
- : contentText;
21
+ const content = contentText.includes(ESCAPED_NEWLINE_CHARACTER)
22
+ ? getRenderText(contentText)
23
+ : contentText;
29
24
 
30
- externalEditor.commands.setContent(content as string);
25
+ externalEditor.commands.setContent(content);
31
26
 
32
27
  let chain = externalEditor.chain().selectAll();
33
28
 
@@ -82,8 +77,9 @@ export const applyStyleToRTEText = (
82
77
  : chain.unsetItalic();
83
78
 
84
79
  chain.run();
80
+ const html = externalEditor.getHTML();
85
81
 
86
- return externalEditor.getJSON();
82
+ return html;
87
83
  };
88
84
 
89
85
  /**
@@ -159,23 +159,7 @@ export default defineComponent({
159
159
  },
160
160
  },
161
161
  watch: {
162
- value(newVal: string | undefined, oldVal: string | undefined) {
163
- // BP-5787: RTE values are Tiptap JSON objects. The parent (TextItemsField)
164
- // re-clones its items array (cloneDeep) on ANY change — including a sibling
165
- // field's style sync — yielding a NEW object reference with identical text
166
- // content. For objects Vue fires this watcher on reference change, so that
167
- // spurious re-clone would overwrite `_value` and clobber an in-flight edit
168
- // (a mark applied but not yet debounced to the store), silently dropping
169
- // the user's formatting. Strings never hit this (same content = same
170
- // reference = no fire), so mirror that: ignore re-clones whose content is
171
- // unchanged from the previous prop value.
172
- if (
173
- this.showRichTextEditor &&
174
- JSON.stringify(newVal) === JSON.stringify(oldVal)
175
- ) {
176
- return;
177
- }
178
-
162
+ value(newVal: string | undefined) {
179
163
  this._value = newVal;
180
164
 
181
165
  if (this.showRichTextEditor) {
@@ -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';
@@ -324,20 +321,10 @@ export default defineComponent({
324
321
  this.isToggled = isToggled;
325
322
  },
326
323
  onUseGeneratedText(generatedText: string) {
327
- let formattedText: unknown = generatedText;
324
+ let formattedText = generatedText;
328
325
 
329
- // BP-5787: preserve the field's formatting when dropping in AI text.
330
- // replaceTextInRTE is HTML-based, so JSON is rendered/parsed around it
331
- // (cold AI path, shared editor); string fields use it directly.
332
- if (this.showRichTextEditor) {
333
- if (isTiptapDocument(this.value)) {
334
- formattedText =
335
- toRteJson(
336
- replaceTextInRTE(renderRteToHtml(this.value), generatedText),
337
- ) ?? generatedText;
338
- } else if (typeof this.value === 'string') {
339
- formattedText = replaceTextInRTE(this.value, generatedText);
340
- }
326
+ if (this.showRichTextEditor && typeof this.value === 'string') {
327
+ formattedText = replaceTextInRTE(this.value, generatedText);
341
328
  }
342
329
 
343
330
  this.contentSettingsStore.updateItemValue({
package/src/tracker.ts ADDED
@@ -0,0 +1,28 @@
1
+ // Lightweight subpath entry: `@designcrowd/library.brand-page/tracker`.
2
+ // Re-exports ONLY the analytics surface (useEventTracker + event constants),
3
+ // which depends solely on `vue`. This lets consumers import the tracker without
4
+ // pulling the full library graph (maker, TipTap, renderer). See
5
+ // docs/plans/multi-entrypoint-tree-shaking.md and the /tracker proposal.
6
+ //
7
+ // IMPORTANT: useEventTracker holds module-scoped pub/sub state (LISTENERS,
8
+ // GLOBAL_EVENT_PROPS). It MUST resolve to a single shared instance across this
9
+ // entry and the root `.` entry, or subscribers registered here won't receive
10
+ // events published via the root. The multi-entry build hoists it into a shared
11
+ // chunk (both `index` and `tracker` import it) — verified at build time.
12
+ export {
13
+ useEventTracker,
14
+ EVENTS,
15
+ SELECT_LOCATIONS,
16
+ UPGRADE_CTA_LOCATIONS,
17
+ AI_CTA_LOCATIONS,
18
+ TRIGGER_TYPE,
19
+ PUBLISH_MODAL_CONTINUATION_EVENTS,
20
+ GENERATE_TEXT_CONTINUATION_EVENTS,
21
+ PUBLISH_OPTION_MODAL_CONTINUATION_EVENTS,
22
+ } from './brand-page-maker/composables/useEventTracker';
23
+ export type {
24
+ EventTracker,
25
+ BpEventTrackerEvent,
26
+ BpGlobalEventProps,
27
+ BpUpsellEventProps,
28
+ } from './brand-page-maker/composables/useEventTracker';
@@ -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 {
@@ -101,9 +101,6 @@ const convertText = (
101
101
  value: unknown,
102
102
  editor: Editor,
103
103
  ): RichTextValue | undefined => {
104
- // Already Tiptap JSON (BP-5787 storage) — keep as-is.
105
- if (isTiptapDocument(value)) return value;
106
-
107
104
  if (typeof value !== 'string' || !value) return undefined;
108
105
 
109
106
  if (hasRTEContent(value)) {
@@ -1,2 +0,0 @@
1
- import type { MigrationData } from '../migration.util';
2
- export declare const migration18: (migrationResult: MigrationData) => Promise<MigrationData>;