@designcrowd/library.brand-page 6.0.26-json-prereq-2 → 6.0.26

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 (155) hide show
  1. package/lib/src/{CreatedOnBrandCrowd-de-de-CKBqjDH3.js → CreatedOnBrandCrowd-de-de-DU6YEVcW.js} +1 -1
  2. package/lib/src/{CreatedOnBrandCrowd-en-us-CRXiimvT.js → CreatedOnBrandCrowd-en-us-DNWWK6Tj.js} +1 -1
  3. package/lib/src/{CreatedOnBrandCrowd-es-es-DCYdY6sj.js → CreatedOnBrandCrowd-es-es-DVFvV9Qp.js} +1 -1
  4. package/lib/src/{CreatedOnBrandCrowd-fr-fr-Dk7LOhxE.js → CreatedOnBrandCrowd-fr-ca-B4ZkjNdw.js} +1 -1
  5. package/lib/src/{CreatedOnBrandCrowd-fr-ca-Dk7LOhxE.js → CreatedOnBrandCrowd-fr-fr-B4ZkjNdw.js} +1 -1
  6. package/lib/src/{CreatedOnBrandCrowd-pt-pt-Cp4ufHJP.js → CreatedOnBrandCrowd-pt-br-DP7AQGgW.js} +1 -1
  7. package/lib/src/{CreatedOnBrandCrowd-pt-br-Cp4ufHJP.js → CreatedOnBrandCrowd-pt-pt-DP7AQGgW.js} +1 -1
  8. package/lib/src/{CreatedOnDcom-de-de-ZSLkWMwM.js → CreatedOnDcom-de-de-CaC3GtG9.js} +1 -1
  9. package/lib/src/{CreatedOnDcom-en-us-BgdIGfT3.js → CreatedOnDcom-en-us-D16yT0-P.js} +1 -1
  10. package/lib/src/{CreatedOnDcom-es-es-D0KemkCZ.js → CreatedOnDcom-es-es-DA5sHgD_.js} +1 -1
  11. package/lib/src/{CreatedOnDcom-fr-ca-BuwNjNbC.js → CreatedOnDcom-fr-ca-CDv2SG1-.js} +1 -1
  12. package/lib/src/{CreatedOnDcom-fr-fr-DCQcsFPf.js → CreatedOnDcom-fr-fr-Cygc7c5d.js} +1 -1
  13. package/lib/src/{CreatedOnDcom-pt-br-DD-oLAS1.js → CreatedOnDcom-pt-br-B_3RIzgF.js} +1 -1
  14. package/lib/src/{CreatedOnDcom-pt-pt-D8qLq2wW.js → CreatedOnDcom-pt-pt-Bz5w-rrD.js} +1 -1
  15. package/lib/src/brand-page/components/contact-form/ContactForm.mixin.js +2 -5
  16. package/lib/src/brand-page/models/index.d.ts +26 -34
  17. package/lib/src/brand-page/utils/migration.util.js +0 -11
  18. package/lib/src/brand-page/utils/text.util.d.ts +1 -1
  19. package/lib/src/brand-page/utils/text.util.js +0 -32
  20. package/lib/src/brand-page/utils/vcard.util.js +4 -4
  21. package/lib/src/brand-page/utils/view-model.util.d.ts +0 -1
  22. package/lib/src/brand-page/utils/view-model.util.js +20 -68
  23. package/lib/src/brand-page/view-models.d.ts +3 -3
  24. package/lib/src/brand-page-maker/composables/ai-chat/useAiMetadataBuilder.d.ts +1 -0
  25. package/lib/src/brand-page-maker/composables/rich-text-editor/bubbleMenu.d.ts +9 -2
  26. package/lib/src/brand-page-maker/composables/rich-text-editor/bubbleMenu.js +10 -6
  27. package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontSize.d.ts +20 -0
  28. package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontSize.js +44 -0
  29. package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/LineHeight.d.ts +27 -0
  30. package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/LineHeight.js +44 -0
  31. package/lib/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.d.ts +1 -1
  32. package/lib/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.js +103 -24
  33. package/lib/src/brand-page-maker/utils/index.d.ts +0 -2
  34. package/lib/src/brand-page-maker/utils/index.js +0 -2
  35. package/lib/src/brand-page-maker/utils/rich-text-editor.util.d.ts +5 -28
  36. package/lib/src/brand-page-maker/utils/rich-text-editor.util.js +5 -48
  37. package/lib/src/brand-page-maker/validators/field.validator.js +2 -10
  38. package/lib/src/css/library.brand-page-brandCrowd.min.css +39 -39
  39. package/lib/src/css/library.brand-page-brandPage.min.css +39 -39
  40. package/lib/src/css/library.brand-page-designCom.min.css +39 -39
  41. package/lib/src/css/library.brand-page-preview.min.css +39 -39
  42. package/lib/src/index.d.ts +0 -1
  43. package/lib/src/index.mjs +138 -143
  44. package/lib/src/{lib-Db6j9fQZ.js → lib-6_P6kXPu.js} +51709 -55194
  45. package/lib/src/lib.js +0 -2
  46. package/lib/src/metadata-builder/ai-metadata-executor.d.ts +1 -0
  47. package/lib/src/metadata-builder/ai-metadata-executor.js +19 -0
  48. package/lib/src/metadata-builder/builders/section-builders/about-section-builder.js +3 -2
  49. package/lib/src/metadata-builder/builders/section-builders/banner-section-builder.js +3 -2
  50. package/lib/src/metadata-builder/builders/section-builders/booking-section-builder.js +3 -2
  51. package/lib/src/metadata-builder/builders/section-builders/contact-form-builder.js +4 -3
  52. package/lib/src/metadata-builder/builders/section-builders/images-section-builder.js +3 -2
  53. package/lib/src/metadata-builder/builders/section-builders/mailing-list-builder.js +2 -1
  54. package/lib/src/metadata-builder/builders/section-builders/mailing-list-section-builder.js +2 -1
  55. package/lib/src/metadata-builder/builders/section-builders/profile-section-builder.js +4 -3
  56. package/lib/src/metadata-builder/builders/section-builders/shapes-section-builder.js +3 -2
  57. package/lib/src/metadata-builder/builders/section-builders/testimonial-section-builder.js +2 -1
  58. package/lib/src/metadata-builder/builders/section-builders/text-section-builder.js +11 -15
  59. package/lib/src/metadata-builder/builders/update-content-text-builder.d.ts +17 -0
  60. package/lib/src/metadata-builder/builders/update-content-text-builder.js +103 -0
  61. package/lib/src/metadata-builder/builders/update-content-text-style-builder.d.ts +16 -0
  62. package/lib/src/metadata-builder/builders/update-content-text-style-builder.js +216 -0
  63. package/lib/src/metadata-builder/helpers/rte.helpers.d.ts +13 -11
  64. package/lib/src/metadata-builder/helpers/rte.helpers.js +73 -14
  65. package/lib/src/metadata-builder/metadata-executor.js +4 -0
  66. package/lib/src/metadata-builder/models.d.ts +2 -0
  67. package/lib/src/metadata-builder/models.js +2 -0
  68. package/lib/src/metadata-builder/parsers/ai-toolcall-parser.d.ts +19 -0
  69. package/lib/src/metadata-builder/parsers/ai-toolcall-parser.js +4 -0
  70. package/lib/src/metadata-builder/sanitizers/rte-schema-check.d.ts +8 -7
  71. package/lib/src/metadata-builder/sanitizers/rte-schema-check.js +52 -21
  72. package/lib/src/shared/content-settings/components/GenericTextInput.mixin.js +1 -14
  73. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.js +3 -12
  74. package/lib/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.mixin.d.ts +2 -3
  75. package/lib/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.mixin.js +3 -10
  76. package/lib/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.d.ts +1 -2
  77. package/lib/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.js +1 -4
  78. package/lib/src/utils/minify-brand-page-metadata.js +1 -4
  79. package/package.json +15 -17
  80. package/src/brand-page/components/contact-form/ContactForm.mixin.ts +2 -5
  81. package/src/brand-page/components/text-wrapper/TextWrapper.vue +6 -17
  82. package/src/brand-page/models/index.ts +26 -35
  83. package/src/brand-page/utils/__snapshots__/migration.util.test.ts.snap +3 -3
  84. package/src/brand-page/utils/migration.util.ts +0 -12
  85. package/src/brand-page/utils/style.util.test.ts +0 -51
  86. package/src/brand-page/utils/text.util.ts +4 -43
  87. package/src/brand-page/utils/vcard.util.ts +4 -6
  88. package/src/brand-page/utils/view-model.util.ts +26 -94
  89. package/src/brand-page/view-models.ts +2 -3
  90. package/src/brand-page-maker/components/rich-text-editor/RichTextEditor.vue +3 -4
  91. package/src/brand-page-maker/composables/ai-chat/useAiMetadataBuilder.ts +1 -0
  92. package/src/brand-page-maker/composables/rich-text-editor/bubbleMenu.ts +10 -6
  93. package/src/brand-page-maker/composables/rich-text-editor/extensions/FontSize.ts +73 -0
  94. package/src/brand-page-maker/composables/rich-text-editor/extensions/LineHeight.ts +85 -0
  95. package/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.ts +111 -27
  96. package/src/brand-page-maker/utils/index.ts +0 -2
  97. package/src/brand-page-maker/utils/rich-text-editor.util.test.ts +3 -37
  98. package/src/brand-page-maker/utils/rich-text-editor.util.ts +11 -66
  99. package/src/brand-page-maker/validators/field.validator.ts +2 -10
  100. package/src/lib.ts +0 -9
  101. package/src/metadata-builder/ai-metadata-executor.ts +26 -0
  102. package/src/metadata-builder/builders/section-builders/about-section-builder.ts +3 -2
  103. package/src/metadata-builder/builders/section-builders/banner-section-builder.ts +3 -2
  104. package/src/metadata-builder/builders/section-builders/booking-section-builder.ts +5 -2
  105. package/src/metadata-builder/builders/section-builders/contact-form-builder.ts +4 -3
  106. package/src/metadata-builder/builders/section-builders/images-section-builder.ts +3 -2
  107. package/src/metadata-builder/builders/section-builders/mailing-list-builder.ts +2 -1
  108. package/src/metadata-builder/builders/section-builders/mailing-list-section-builder.ts +2 -1
  109. package/src/metadata-builder/builders/section-builders/profile-section-builder.ts +6 -3
  110. package/src/metadata-builder/builders/section-builders/shapes-section-builder.ts +3 -2
  111. package/src/metadata-builder/builders/section-builders/testimonial-section-builder.ts +2 -1
  112. package/src/metadata-builder/builders/section-builders/text-section-builder.ts +13 -19
  113. package/src/metadata-builder/builders/update-content-text-builder.ts +148 -0
  114. package/src/metadata-builder/builders/update-content-text-style-builder.ts +352 -0
  115. package/src/metadata-builder/helpers/rte.helpers.ts +101 -15
  116. package/src/metadata-builder/metadata-executor.ts +5 -0
  117. package/src/metadata-builder/models.ts +3 -0
  118. package/src/metadata-builder/parsers/ai-toolcall-parser.ts +27 -0
  119. package/src/metadata-builder/sanitizers/rte-schema-check.ts +53 -24
  120. package/src/shared/content-settings/components/GenericTextInput.mixin.ts +1 -17
  121. package/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.ts +3 -16
  122. package/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.mixin.ts +4 -13
  123. package/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.vue +4 -6
  124. package/src/shared/content-settings/components/text-image-field/TextImageField.vue +3 -6
  125. package/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.ts +1 -6
  126. package/src/shared/content-settings/components/text-items-field/TextItemsField.vue +1 -1
  127. package/src/utils/minify-brand-page-metadata.ts +1 -4
  128. package/lib/src/brand-page/utils/migration/migration-20.d.ts +0 -2
  129. package/lib/src/brand-page/utils/migration/migration-20.js +0 -66
  130. package/lib/src/brand-page-maker/__fixtures__/complex-rte.fixture.d.ts +0 -22
  131. package/lib/src/brand-page-maker/__fixtures__/complex-rte.fixture.js +0 -22
  132. package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.d.ts +0 -14
  133. package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.js +0 -39
  134. package/lib/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.d.ts +0 -28
  135. package/lib/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.js +0 -90
  136. package/lib/src/brand-page-maker/utils/rte-render.util.d.ts +0 -63
  137. package/lib/src/brand-page-maker/utils/rte-render.util.js +0 -95
  138. package/lib/src/brand-page-maker/utils/rte-sanitize.util.d.ts +0 -6
  139. package/lib/src/brand-page-maker/utils/rte-sanitize.util.js +0 -158
  140. package/src/brand-page/utils/migration/migration-20.server.test.ts +0 -125
  141. package/src/brand-page/utils/migration/migration-20.test.ts +0 -72
  142. package/src/brand-page/utils/migration/migration-20.ts +0 -87
  143. package/src/brand-page/utils/text.util.test.ts +0 -47
  144. package/src/brand-page/utils/view-model.pageid-rte.test.ts +0 -104
  145. package/src/brand-page-maker/__fixtures__/complex-rte.fixture.ts +0 -23
  146. package/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.test.ts +0 -52
  147. package/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.ts +0 -42
  148. package/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.ts +0 -137
  149. package/src/brand-page-maker/utils/__snapshots__/rte-render-parity.test.ts.snap +0 -202
  150. package/src/brand-page-maker/utils/rte-features.test.ts +0 -351
  151. package/src/brand-page-maker/utils/rte-render-parity.test.ts +0 -157
  152. package/src/brand-page-maker/utils/rte-render.util.ts +0 -165
  153. package/src/brand-page-maker/utils/rte-sanitize-json.test.ts +0 -158
  154. package/src/brand-page-maker/utils/rte-sanitize.util.ts +0 -195
  155. package/src/brand-page-maker/validators/field.validator.test.ts +0 -55
@@ -0,0 +1,44 @@
1
+ import { Extension } from '@tiptap/core';
2
+ import '@tiptap/extension-text-style';
3
+ export const FontSize = Extension.create({
4
+ name: 'fontSize',
5
+ addOptions() {
6
+ return {
7
+ types: ['textStyle'],
8
+ };
9
+ },
10
+ addGlobalAttributes() {
11
+ return [
12
+ {
13
+ types: this.options.types,
14
+ attributes: {
15
+ fontSize: {
16
+ default: null,
17
+ parseHTML: (element) => element.style.fontSize.replace(/['"]+/g, ''),
18
+ renderHTML: (attributes) => {
19
+ if (!attributes.fontSize) {
20
+ return {};
21
+ }
22
+ return {
23
+ style: `font-size: ${attributes.fontSize}`,
24
+ };
25
+ },
26
+ },
27
+ },
28
+ },
29
+ ];
30
+ },
31
+ addCommands() {
32
+ return {
33
+ setFontSize: (fontSize) => ({ chain }) => {
34
+ return chain().setMark('textStyle', { fontSize }).run();
35
+ },
36
+ unsetFontSize: () => ({ chain }) => {
37
+ return chain()
38
+ .setMark('textStyle', { fontSize: null })
39
+ .removeEmptyTextStyle()
40
+ .run();
41
+ },
42
+ };
43
+ },
44
+ });
@@ -0,0 +1,27 @@
1
+ import { Extension } from '@tiptap/core';
2
+ export type LineHeightOptions = {
3
+ /**
4
+ * Node / mark types that should carry the `lineHeight` attribute.
5
+ * Defaults to `['textStyle']` to piggy‑back on TipTap’s built‑in mark.
6
+ */
7
+ types: string[];
8
+ };
9
+ declare module '@tiptap/core' {
10
+ interface Commands<ReturnType> {
11
+ lineHeight: {
12
+ /**
13
+ * Apply a line‑height to the current selection (or stored marks).
14
+ */
15
+ setLineHeight: (heightValue: string) => ReturnType;
16
+ /**
17
+ * Remove any line‑height attribute.
18
+ */
19
+ unsetLineHeight: () => ReturnType;
20
+ /**
21
+ * Read the current line‑height from the selection / stored marks.
22
+ */
23
+ getLineHeight: () => ReturnType;
24
+ };
25
+ }
26
+ }
27
+ export declare const LineHeight: Extension<LineHeightOptions, any>;
@@ -0,0 +1,44 @@
1
+ import { Extension } from '@tiptap/core';
2
+ export const LineHeight = Extension.create({
3
+ name: 'lineHeight',
4
+ addOptions() {
5
+ return {
6
+ types: ['textStyle'],
7
+ };
8
+ },
9
+ addGlobalAttributes() {
10
+ return [
11
+ {
12
+ types: this.options.types,
13
+ attributes: {
14
+ lineHeight: {
15
+ default: null,
16
+ parseHTML: (element) => (element.style.lineHeight || '').replace(/['"]+/g, ''),
17
+ renderHTML: (attributes) => {
18
+ if (!attributes.lineHeight) {
19
+ return {};
20
+ }
21
+ return { style: `line-height: ${attributes.lineHeight}` };
22
+ },
23
+ },
24
+ },
25
+ },
26
+ ];
27
+ },
28
+ addCommands() {
29
+ return {
30
+ setLineHeight: (lineHeight) => ({ chain }) => {
31
+ return chain().setMark('textStyle', { lineHeight }).run();
32
+ },
33
+ unsetLineHeight: () => ({ chain }) => {
34
+ return chain()
35
+ .setMark('textStyle', { lineHeight: null })
36
+ .removeEmptyTextStyle()
37
+ .run();
38
+ },
39
+ getLineHeight: () => ({ editor }) => {
40
+ return editor.getAttributes('textStyle').lineHeight;
41
+ },
42
+ };
43
+ },
44
+ });
@@ -43,7 +43,7 @@ interface UseRichTextEditorReturn {
43
43
  editor: any;
44
44
  state: any;
45
45
  }) => boolean;
46
- bubbleMenuOptions: Record<string, any>;
46
+ tippyOptions: Record<string, any>;
47
47
  handleAdminColorChange: (color: {
48
48
  id: string;
49
49
  name: string;
@@ -1,25 +1,72 @@
1
1
  import { onBeforeUnmount, computed, watch, } from 'vue';
2
2
  import { Editor } from '@tiptap/vue-3';
3
- import { getRteExtensions } from './getRteExtensions';
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';
4
15
  import { createFormattingActions } from './formatting';
5
16
  import { useTextAlign } from './textAlign';
6
17
  import { useFont } from './font';
7
18
  import { useSpacing } from './spacing';
8
19
  import Color from 'color';
9
- import { buildInitialMarks, emToPx, isStyleChanged, pxToEm, sanitizeRteJson, skipStyleDetection, } from '@brand-page-maker/utils';
20
+ import { buildInitialMarks, emToPx, isStyleChanged, pxToEm, sanitiseClientSideRTE, skipStyleDetection, } from '@brand-page-maker/utils';
10
21
  import { HYPERLINK_TYPE, } from '@brand-page';
11
22
  import { letterSpacingValueConvertor, colorConvertor, backgroundColorConvertor, } from '@brand-page-maker/convertors';
12
23
  import { useUiContentSettingsStore } from '@shared/content-settings/store';
13
24
  import { useHistoryStore } from '@brand-page-maker/stores/history/history-store';
14
25
  import { AllSelection } from 'prosemirror-state';
15
- import { useHyperlink } from './hyperlink';
26
+ import { MaxLength } from './extensions/MaxLength';
27
+ import { AutoLink } from './extensions/AutoLink';
28
+ import { isHrefPageId, useHyperlink } from './hyperlink';
16
29
  import { useBubbleMenu } from './bubbleMenu';
17
30
  import { hasLogoColorVar, isLogoColor, getRTEColor, } from '@brand-page-maker/utils/rich-text-editor.util';
18
31
  import { useEventTracker, EVENTS } from '../useEventTracker';
19
32
  export const useExternalRichTextEditor = () => {
20
33
  const externalEditor = new Editor({
21
34
  editable: false,
22
- extensions: getRteExtensions({ includeLinkRel: true }),
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
+ ],
23
70
  });
24
71
  return {
25
72
  externalEditor,
@@ -30,13 +77,51 @@ export const useRichTextEditor = (model, props, emit, fontSettings, fontStyleDro
30
77
  const editor = new Editor({
31
78
  content: model.value ?? '',
32
79
  editable: !props.readOnly,
33
- extensions: getRteExtensions({
34
- restrictStarterKit: true,
35
- editing: {
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({
36
107
  placeholder: props.placeholder,
37
- maxLength: props.maxLength,
38
- },
39
- }),
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
+ ],
40
125
  editorProps: {
41
126
  handleDOMEvents: {
42
127
  drop(view, event) {
@@ -139,7 +224,7 @@ export const useRichTextEditor = (model, props, emit, fontSettings, fontStyleDro
139
224
  const { selectedFont, toggleFont, toggleFontSize, toggleFontAll, toggleFontSizeAll, } = useFont(editor, fontSettings, fontStyleDropdownRef);
140
225
  const { letterSpacingValue, lineHeightValue, letterSpacing, lineHeight, setLetterSpaceAll, setLineHeightAll, } = useSpacing(editor);
141
226
  const { hyperlinkValue, setHyperlinkUrl, setHyperlinkUrlValue, removeHyperlink: removeHyperlinkFromComposable, applyHyperlinkToEditor, } = useHyperlink(editor);
142
- const { shouldShow, bubbleMenuOptions } = useBubbleMenu(editor, isHyperlinkPopupOpen);
227
+ const { shouldShow, tippyOptions } = useBubbleMenu(editor, isHyperlinkPopupOpen);
143
228
  watch(() => props.styles.textAlign, (newAlignment) => {
144
229
  if (newAlignment && newAlignment !== currentAlignment.value.alignment) {
145
230
  skipStyleDetection(editor.chain().selectAll().setTextAlign(newAlignment)).run();
@@ -154,12 +239,8 @@ export const useRichTextEditor = (model, props, emit, fontSettings, fontStyleDro
154
239
  const appliedSinceLast = historyStore.snapshotAppliedToken !== lastAppliedToken;
155
240
  lastAppliedToken = historyStore.snapshotAppliedToken;
156
241
  const isExternalMutation = updateRichTextEditor.value || appliedSinceLast;
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 });
242
+ if (isExternalMutation && editor.getHTML() !== newValue) {
243
+ editor.commands.setContent(newValue, false);
163
244
  updateRichTextEditor.value = false;
164
245
  //text style type is not switching when reordering
165
246
  textStyleTypeOptions.forEach((item) => {
@@ -186,13 +267,11 @@ export const useRichTextEditor = (model, props, emit, fontSettings, fontStyleDro
186
267
  });
187
268
  }
188
269
  });
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.
270
+ // Update model when editor content changes
193
271
  editor.on('update', () => {
272
+ const sanitizedData = sanitiseClientSideRTE(editor.getHTML());
194
273
  // An update fired while the editor is unfocused is TipTap rewriting the
195
- // model back into its canonical form during mount rehydration or a
274
+ // model back into its canonical HTML during mount rehydration or a
196
275
  // programmatic setContent — not a user edit. Tag the emit so the write
197
276
  // folds into the current undo head instead of pushing a phantom entry
198
277
  // (BP-5810). The flag travels WITH the value through the debounced
@@ -206,7 +285,7 @@ export const useRichTextEditor = (model, props, emit, fontSettings, fontStyleDro
206
285
  // the RTE only mounts inside an already-open settings drawer and the user
207
286
  // focuses it before formatting, so this doesn't occur — revisit if a
208
287
  // consumer ever drives toolbar actions on an unfocused editor.
209
- emit('update:modelValue', sanitizeRteJson(editor.getJSON()), {
288
+ emit('update:modelValue', sanitizedData.sanitizedHtml, {
210
289
  isHydrationWrite: !editor.isFocused,
211
290
  });
212
291
  });
@@ -646,7 +725,7 @@ export const useRichTextEditor = (model, props, emit, fontSettings, fontStyleDro
646
725
  hyperlinkValue,
647
726
  setHyperlinkUrlValue,
648
727
  shouldShow,
649
- bubbleMenuOptions,
728
+ tippyOptions,
650
729
  handleAdminColorChange,
651
730
  };
652
731
  };
@@ -5,6 +5,4 @@ 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';
10
8
  export declare const isColorMatched: (color: string | null | undefined) => boolean;
@@ -6,8 +6,6 @@ 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';
11
9
  export const isColorMatched = (color) => {
12
10
  if (!color)
13
11
  return false;
@@ -1,6 +1,6 @@
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
- import { type Content, type TextStyleDeclaration, type TextStyleType, type RichTextValue } from '@brand-page';
3
+ import { type Content, type TextStyleDeclaration, type TextStyleType } from '@brand-page';
4
4
  import type { PageSettingsState } from '@shared/page-settings/store';
5
5
  import type { StyleSettingsState } from '@shared/style-settings/store';
6
6
  import type { Mark } from 'prosemirror-model';
@@ -9,31 +9,12 @@ export declare const RTE_ALLOWED_SECTION_TYPES: string[];
9
9
  export declare const isRTEManagedStyleField: (sectionType: string, fieldName: string) => boolean;
10
10
  export declare const isStyleChanged: (tr: Transaction) => boolean;
11
11
  export declare const skipStyleDetection: (chain: ChainedCommands, marks?: Mark[]) => ChainedCommands;
12
- /**
13
- * BP-5787 canonical discriminator for the JSON RTE model: a ProseMirror
14
- * document is an object with `type: 'doc'`.
15
- */
16
- export declare const isTiptapDocument: (value: unknown) => value is JSONContent;
17
- /**
18
- * Extract visible plain text from a Tiptap JSON document (concatenated text
19
- * nodes). Dependency-free — no editor instantiation.
20
- */
21
- export declare const getRteJsonPlainText: (doc: JSONContent) => string;
22
- export declare const hasRTEContent: (content: unknown) => boolean;
23
- export declare const getPlainTextFromRTE: (content: unknown) => string;
24
- /**
25
- * BP-5787 — visible-text length of an RTE value, for the maker item-field
26
- * max-length / emptiness checks. A plain string keeps its raw `.length`, so the
27
- * pre-JSON behaviour is byte-for-byte identical; a Tiptap JSON doc measures its
28
- * concatenated visible text. Without this, `doc.length` is `undefined` (silently
29
- * disabling the check) and `doc.trim()` throws — the class of bug that survived
30
- * the `RichTextValue` widening because Vue Options-API `this`/item access is `any`.
31
- */
32
- export declare const getRteTextLength: (value: RichTextValue | null | undefined) => number;
12
+ export declare const hasRTEContent: (content: string) => boolean;
13
+ export declare const getPlainTextFromRTE: (content: string) => string;
33
14
  export declare const replaceTextInRTE: (existingHtml: string, newText: string) => string;
34
15
  export declare const transformToRTE: (uiPageSettingStore: PageSettingsState, externalEditor: Editor, uiStyleSettingsStore?: StyleSettingsState, newContent?: Content, type?: TextStyleType, newStyle?: TextStyleDeclaration) => void;
35
16
  export declare const switchAllContentToRTE: (uiPageSettingStore: PageSettingsState, externalEditor: Editor, uiStyleSettingsStore?: StyleSettingsState) => void;
36
- export declare const getUpdatedHTMLFromEditor: (externalEditor: Editor, contentText: RichTextValue, newContentStyle: TextStyleDeclaration) => string | import("@tiptap/core").DocumentType<Record<string, any> | undefined, import("@tiptap/core").NodeType<string, Record<string, any> | undefined, any, (import("@tiptap/core").NodeType<any, any, any, any> | import("@tiptap/core").TextType<import("@tiptap/core").MarkType<any, any>>)[]>[]>;
17
+ export declare const getUpdatedHTMLFromEditor: (externalEditor: Editor, contentText: string, newContentStyle: TextStyleDeclaration) => string;
37
18
  export declare const buildInitialMarks: (state: EditorState, style: TextStyleDeclaration) => any[];
38
19
  export declare const retainStyleOnEmptyLine: (editor: Editor, selection: Selection) => void;
39
20
  export interface SanitizationResult {
@@ -41,10 +22,6 @@ export interface SanitizationResult {
41
22
  removedElements: string[];
42
23
  }
43
24
  export declare const sanitiseClientSideRTE: (html: string) => SanitizationResult;
44
- /**
45
- * Validates if an href value is safe and returns a sanitized version
46
- */
47
- export declare const validateAndSanitizeHref: (href: string) => string | null;
48
25
  /**
49
26
  * Sanitize HTML string using DOMPurify.
50
27
  * Only allow tags and attributes that correspond to the enabled Tiptap extensions.
@@ -175,41 +175,14 @@ export const skipStyleDetection = (chain, marks) => chain.command(({ tr }) => {
175
175
  }
176
176
  return true;
177
177
  });
178
- /**
179
- * BP-5787 — canonical discriminator for the JSON RTE model: a ProseMirror
180
- * document is an object with `type: 'doc'`.
181
- */
182
- export const isTiptapDocument = (value) => typeof value === 'object' &&
183
- value !== null &&
184
- value.type === 'doc';
185
- /**
186
- * Extract visible plain text from a Tiptap JSON document (concatenated text
187
- * nodes). Dependency-free — no editor instantiation.
188
- */
189
- export const getRteJsonPlainText = (doc) => {
190
- const parts = [];
191
- const walk = (node) => {
192
- if (typeof node.text === 'string')
193
- parts.push(node.text);
194
- node.content?.forEach(walk);
195
- };
196
- walk(doc);
197
- return parts.join(' ').replace(/\s+/g, ' ').trim();
198
- };
199
- // Accepts a legacy inline-HTML string or a Tiptap JSON document (BP-5787).
200
178
  export const hasRTEContent = (content) => {
201
- if (isTiptapDocument(content))
202
- return true;
203
- if (typeof content !== 'string' || content.length === 0)
179
+ if (!content || content.length === 0)
204
180
  return false;
205
181
  const trimmed = content.trim();
206
182
  const tagWrapRegex = /^<([A-Za-z][A-Za-z0-9-]*)\b[^>]*>[\s\S]*<\/\1>$/;
207
183
  return tagWrapRegex.test(trimmed);
208
184
  };
209
185
  export const getPlainTextFromRTE = (content) => {
210
- if (isTiptapDocument(content)) {
211
- return getRteJsonPlainText(content);
212
- }
213
186
  if (typeof content !== 'string') {
214
187
  return '';
215
188
  }
@@ -227,19 +200,6 @@ export const getPlainTextFromRTE = (content) => {
227
200
  }
228
201
  return plainText.trim();
229
202
  };
230
- /**
231
- * BP-5787 — visible-text length of an RTE value, for the maker item-field
232
- * max-length / emptiness checks. A plain string keeps its raw `.length`, so the
233
- * pre-JSON behaviour is byte-for-byte identical; a Tiptap JSON doc measures its
234
- * concatenated visible text. Without this, `doc.length` is `undefined` (silently
235
- * disabling the check) and `doc.trim()` throws — the class of bug that survived
236
- * the `RichTextValue` widening because Vue Options-API `this`/item access is `any`.
237
- */
238
- export const getRteTextLength = (value) => {
239
- if (typeof value === 'string')
240
- return value.length;
241
- return value ? getRteJsonPlainText(value).length : 0;
242
- };
243
203
  export const replaceTextInRTE = (existingHtml, newText) => {
244
204
  if (!hasRTEContent(existingHtml)) {
245
205
  return `<p>${newText}</p>`;
@@ -514,10 +474,7 @@ export const getUpdatedHTMLFromEditor = (externalEditor, contentText, newContent
514
474
  if (!contentText) {
515
475
  return contentText;
516
476
  }
517
- // BP-5787: accept a string (legacy HTML) or a Tiptap JSON doc. setContent
518
- // handles both; only the newline-escaping shim applies to strings.
519
- const content = typeof contentText === 'string' &&
520
- contentText.includes(ESCAPED_NEWLINE_CHARACTER)
477
+ const content = contentText.includes(ESCAPED_NEWLINE_CHARACTER)
521
478
  ? getRenderText(contentText)
522
479
  : contentText;
523
480
  externalEditor.commands.setContent(content);
@@ -561,8 +518,8 @@ export const getUpdatedHTMLFromEditor = (externalEditor, contentText, newContent
561
518
  : chain.unsetBold();
562
519
  chain = newContentStyle?.fontStyle ? chain.setItalic() : chain.unsetItalic();
563
520
  chain.run();
564
- // BP-5787: return the JSON form, consistent with how RTE is now stored.
565
- return externalEditor.getJSON();
521
+ const html = externalEditor.getHTML();
522
+ return html;
566
523
  };
567
524
  // Helper to build stored marks from a style declaration
568
525
  export const buildInitialMarks = (state, style) => {
@@ -641,7 +598,7 @@ export const sanitiseClientSideRTE = (html) => {
641
598
  /**
642
599
  * Validates if an href value is safe and returns a sanitized version
643
600
  */
644
- export const validateAndSanitizeHref = (href) => {
601
+ const validateAndSanitizeHref = (href) => {
645
602
  if (!href || typeof href !== 'string')
646
603
  return null;
647
604
  // Trim whitespace and decode entities
@@ -1,5 +1,5 @@
1
1
  import { isURL, isEmail, isTel } from '@utils/validator.util';
2
- import { YOUTUBE_URL_RE, isTiptapDocument } from '@brand-page-maker/utils';
2
+ import { YOUTUBE_URL_RE } from '@brand-page-maker/utils';
3
3
  import { validatorTr } from '@src/composables/useTranslate';
4
4
  const addressValueValidator = {
5
5
  validate: (name, value) => {
@@ -62,15 +62,7 @@ export const dummyValidator = {
62
62
  };
63
63
  const nonEmptyStringValidator = {
64
64
  validate: (name, value) => {
65
- // BP-5787/BP-5857: match legacy (pre-JSON) validation semantics.
66
- // An RTE field's stored value used to be inline HTML — an empty editor was
67
- // "<p></p>", a NON-empty string, so the required check always passed for RTE
68
- // fields (it only ever failed for genuinely empty PLAIN inputs). Now RTE
69
- // fields are Tiptap JSON docs; treat any doc as "present" so behaviour
70
- // stays identical. A plain string is still required to be non-empty.
71
- const isValid = isTiptapDocument(value)
72
- ? true
73
- : typeof value === 'string' && value.length > 0;
65
+ const isValid = typeof value === 'string' && value.length > 0;
74
66
  return {
75
67
  isValid,
76
68
  message: isValid ? '' : validatorTr(`emptyStringMessage`),