@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.
- package/lib/src/{CreatedOnBrandCrowd-de-de-CS3ct0wa.js → CreatedOnBrandCrowd-de-de-CKBqjDH3.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-en-us-CmBcnDu6.js → CreatedOnBrandCrowd-en-us-CRXiimvT.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-es-es-BdTuJtqV.js → CreatedOnBrandCrowd-es-es-DCYdY6sj.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-fr-Cx_H9X6l.js → CreatedOnBrandCrowd-fr-ca-Dk7LOhxE.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-ca-Cx_H9X6l.js → CreatedOnBrandCrowd-fr-fr-Dk7LOhxE.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-pt-BZZ9hjUi.js → CreatedOnBrandCrowd-pt-br-Cp4ufHJP.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-br-BZZ9hjUi.js → CreatedOnBrandCrowd-pt-pt-Cp4ufHJP.js} +1 -1
- package/lib/src/{CreatedOnDcom-de-de-SbONDQN1.js → CreatedOnDcom-de-de-ZSLkWMwM.js} +1 -1
- package/lib/src/{CreatedOnDcom-en-us-Beno1_gw.js → CreatedOnDcom-en-us-BgdIGfT3.js} +1 -1
- package/lib/src/{CreatedOnDcom-es-es-BBE_zdRu.js → CreatedOnDcom-es-es-D0KemkCZ.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-ca-C6pgDB5x.js → CreatedOnDcom-fr-ca-BuwNjNbC.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-fr-FD1eqtg3.js → CreatedOnDcom-fr-fr-DCQcsFPf.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-br-BiSx3OwM.js → CreatedOnDcom-pt-br-DD-oLAS1.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-pt-Dn4DmKGA.js → CreatedOnDcom-pt-pt-D8qLq2wW.js} +1 -1
- package/lib/src/brand-page/components/contact-form/ContactForm.mixin.js +5 -2
- package/lib/src/brand-page/models/index.d.ts +34 -26
- package/lib/src/brand-page/utils/migration/migration-20.d.ts +2 -0
- package/lib/src/brand-page/utils/migration/migration-20.js +66 -0
- package/lib/src/brand-page/utils/migration.util.js +11 -0
- package/lib/src/brand-page/utils/text.util.d.ts +1 -1
- package/lib/src/brand-page/utils/text.util.js +32 -0
- package/lib/src/brand-page/utils/vcard.util.js +4 -4
- package/lib/src/brand-page/utils/view-model.util.d.ts +1 -0
- package/lib/src/brand-page/utils/view-model.util.js +68 -20
- package/lib/src/brand-page/view-models.d.ts +3 -3
- package/lib/src/brand-page-maker/__fixtures__/complex-rte.fixture.d.ts +22 -0
- package/lib/src/brand-page-maker/__fixtures__/complex-rte.fixture.js +22 -0
- package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +1 -1
- package/lib/src/brand-page-maker/components/seo-settings/SeoPageSettings.mixin.d.ts +1 -1
- package/lib/src/brand-page-maker/composables/ai-chat/useAiMetadataBuilder.d.ts +0 -1
- package/lib/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.js +21 -9
- package/lib/src/brand-page-maker/composables/rich-text-editor/bubbleMenu.d.ts +2 -9
- package/lib/src/brand-page-maker/composables/rich-text-editor/bubbleMenu.js +6 -10
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.d.ts +14 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.js +39 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.d.ts +28 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.js +90 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.d.ts +1 -1
- package/lib/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.js +24 -103
- package/lib/src/brand-page-maker/stores/ai-thread/ai-website-builder-thread-store.d.ts +8 -2
- package/lib/src/brand-page-maker/stores/ai-thread/ai-website-builder-thread-store.js +20 -0
- package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.js +7 -0
- package/lib/src/brand-page-maker/utils/index.d.ts +2 -0
- package/lib/src/brand-page-maker/utils/index.js +2 -0
- package/lib/src/brand-page-maker/utils/rich-text-editor.util.d.ts +28 -5
- package/lib/src/brand-page-maker/utils/rich-text-editor.util.js +48 -5
- package/lib/src/brand-page-maker/utils/rte-render.util.d.ts +63 -0
- package/lib/src/brand-page-maker/utils/rte-render.util.js +95 -0
- package/lib/src/brand-page-maker/utils/rte-sanitize.util.d.ts +6 -0
- package/lib/src/brand-page-maker/utils/rte-sanitize.util.js +158 -0
- package/lib/src/brand-page-maker/validators/field.validator.js +10 -2
- package/lib/src/css/library.brand-page-brandCrowd.min.css +39 -39
- package/lib/src/css/library.brand-page-brandPage.min.css +39 -39
- package/lib/src/css/library.brand-page-designCom.min.css +39 -39
- package/lib/src/css/library.brand-page-preview.min.css +39 -39
- package/lib/src/index.d.ts +1 -0
- package/lib/src/index.mjs +143 -138
- package/lib/src/{lib-BVHFYSLL.js → lib-Db6j9fQZ.js} +56262 -52768
- package/lib/src/lib.js +2 -0
- package/lib/src/metadata-builder/ai-metadata-executor.d.ts +0 -1
- package/lib/src/metadata-builder/ai-metadata-executor.js +0 -19
- package/lib/src/metadata-builder/builders/section-builders/about-section-builder.js +2 -3
- package/lib/src/metadata-builder/builders/section-builders/banner-section-builder.js +2 -3
- package/lib/src/metadata-builder/builders/section-builders/booking-section-builder.js +2 -3
- package/lib/src/metadata-builder/builders/section-builders/contact-form-builder.js +3 -4
- package/lib/src/metadata-builder/builders/section-builders/images-section-builder.js +2 -3
- package/lib/src/metadata-builder/builders/section-builders/mailing-list-builder.js +1 -2
- package/lib/src/metadata-builder/builders/section-builders/mailing-list-section-builder.js +1 -2
- package/lib/src/metadata-builder/builders/section-builders/profile-section-builder.js +3 -4
- package/lib/src/metadata-builder/builders/section-builders/shapes-section-builder.js +2 -3
- package/lib/src/metadata-builder/builders/section-builders/testimonial-section-builder.js +1 -2
- package/lib/src/metadata-builder/builders/section-builders/text-section-builder.js +15 -11
- package/lib/src/metadata-builder/helpers/rte.helpers.d.ts +11 -13
- package/lib/src/metadata-builder/helpers/rte.helpers.js +14 -73
- package/lib/src/metadata-builder/metadata-executor.js +0 -4
- package/lib/src/metadata-builder/models.d.ts +0 -2
- package/lib/src/metadata-builder/models.js +0 -2
- package/lib/src/metadata-builder/parsers/ai-toolcall-parser.d.ts +0 -19
- package/lib/src/metadata-builder/parsers/ai-toolcall-parser.js +0 -4
- package/lib/src/metadata-builder/sanitizers/rte-schema-check.d.ts +7 -8
- package/lib/src/metadata-builder/sanitizers/rte-schema-check.js +21 -52
- package/lib/src/shared/content-settings/components/GenericTextInput.mixin.d.ts +2 -2
- package/lib/src/shared/content-settings/components/GenericTextInput.mixin.js +14 -1
- package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.d.ts +1 -1
- package/lib/src/shared/content-settings/components/content-settings/ReplaceImageSettingsForContent.mixin.d.ts +1 -1
- package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.js +12 -3
- package/lib/src/shared/content-settings/components/email-field/EmailField.mixin.d.ts +2 -2
- package/lib/src/shared/content-settings/components/radio-field/RadioField.mixin.d.ts +1 -1
- package/lib/src/shared/content-settings/components/slider-field/SliderField.mixin.d.ts +1 -1
- package/lib/src/shared/content-settings/components/tel-field/TelField.mixin.d.ts +2 -2
- package/lib/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.mixin.d.ts +3 -2
- package/lib/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.mixin.js +10 -3
- package/lib/src/shared/content-settings/components/text-field/TextField.mixin.d.ts +2 -2
- package/lib/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.d.ts +2 -1
- package/lib/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.js +4 -1
- package/lib/src/shared/content-settings/components/textarea-field/TextareaField.mixin.d.ts +2 -2
- package/lib/src/shared/content-settings/components/url-field/UrlField.mixin.d.ts +2 -2
- package/lib/src/shared/content-settings/components/youtube-video-field/YoutubeVideoField.mixin.d.ts +2 -2
- package/lib/src/shared/side-bar/components/SideBar.mixin.d.ts +1 -1
- package/lib/src/utils/minify-brand-page-metadata.js +4 -1
- package/package.json +17 -15
- package/src/brand-page/components/contact-form/ContactForm.mixin.ts +5 -2
- package/src/brand-page/components/text-wrapper/TextWrapper.vue +17 -6
- package/src/brand-page/models/index.ts +35 -26
- package/src/brand-page/utils/__snapshots__/migration.util.test.ts.snap +3 -3
- package/src/brand-page/utils/migration/migration-20.server.test.ts +125 -0
- package/src/brand-page/utils/migration/migration-20.test.ts +72 -0
- package/src/brand-page/utils/migration/migration-20.ts +87 -0
- package/src/brand-page/utils/migration.util.ts +12 -0
- package/src/brand-page/utils/style.util.test.ts +51 -0
- package/src/brand-page/utils/text.util.test.ts +47 -0
- package/src/brand-page/utils/text.util.ts +43 -4
- package/src/brand-page/utils/vcard.util.ts +6 -4
- package/src/brand-page/utils/view-model.pageid-rte.test.ts +104 -0
- package/src/brand-page/utils/view-model.util.ts +94 -26
- package/src/brand-page/view-models.ts +3 -2
- package/src/brand-page-maker/__fixtures__/complex-rte.fixture.ts +23 -0
- package/src/brand-page-maker/components/rich-text-editor/RichTextEditor.vue +4 -3
- package/src/brand-page-maker/composables/ai-chat/useAiMetadataBuilder.ts +0 -1
- package/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.ts +28 -9
- package/src/brand-page-maker/composables/rich-text-editor/bubbleMenu.ts +6 -10
- package/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.test.ts +52 -0
- package/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.ts +42 -0
- package/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.ts +137 -0
- package/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.ts +27 -111
- package/src/brand-page-maker/stores/ai-thread/ai-website-builder-thread-store.ts +23 -0
- package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.ts +8 -0
- package/src/brand-page-maker/utils/__snapshots__/rte-render-parity.test.ts.snap +202 -0
- package/src/brand-page-maker/utils/index.ts +2 -0
- package/src/brand-page-maker/utils/rich-text-editor.util.test.ts +37 -3
- package/src/brand-page-maker/utils/rich-text-editor.util.ts +66 -11
- package/src/brand-page-maker/utils/rte-features.test.ts +351 -0
- package/src/brand-page-maker/utils/rte-render-parity.test.ts +157 -0
- package/src/brand-page-maker/utils/rte-render.util.ts +165 -0
- package/src/brand-page-maker/utils/rte-sanitize-json.test.ts +158 -0
- package/src/brand-page-maker/utils/rte-sanitize.util.ts +195 -0
- package/src/brand-page-maker/validators/field.validator.test.ts +55 -0
- package/src/brand-page-maker/validators/field.validator.ts +10 -2
- package/src/lib.ts +9 -0
- package/src/metadata-builder/ai-metadata-executor.ts +0 -26
- package/src/metadata-builder/builders/section-builders/about-section-builder.ts +2 -3
- package/src/metadata-builder/builders/section-builders/banner-section-builder.ts +2 -3
- package/src/metadata-builder/builders/section-builders/booking-section-builder.ts +2 -5
- package/src/metadata-builder/builders/section-builders/contact-form-builder.ts +3 -4
- package/src/metadata-builder/builders/section-builders/images-section-builder.ts +2 -3
- package/src/metadata-builder/builders/section-builders/mailing-list-builder.ts +1 -2
- package/src/metadata-builder/builders/section-builders/mailing-list-section-builder.ts +1 -2
- package/src/metadata-builder/builders/section-builders/profile-section-builder.ts +3 -6
- package/src/metadata-builder/builders/section-builders/shapes-section-builder.ts +2 -3
- package/src/metadata-builder/builders/section-builders/testimonial-section-builder.ts +1 -2
- package/src/metadata-builder/builders/section-builders/text-section-builder.ts +19 -13
- package/src/metadata-builder/helpers/rte.helpers.ts +15 -101
- package/src/metadata-builder/metadata-executor.ts +0 -5
- package/src/metadata-builder/models.ts +0 -3
- package/src/metadata-builder/parsers/ai-toolcall-parser.ts +0 -27
- package/src/metadata-builder/sanitizers/rte-schema-check.ts +24 -53
- package/src/shared/content-settings/components/GenericTextInput.mixin.ts +17 -1
- package/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.ts +16 -3
- package/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.mixin.ts +13 -4
- package/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.vue +6 -4
- package/src/shared/content-settings/components/text-image-field/TextImageField.vue +6 -3
- package/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.ts +6 -1
- package/src/shared/content-settings/components/text-items-field/TextItemsField.vue +1 -1
- package/src/utils/minify-brand-page-metadata.ts +4 -1
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontSize.d.ts +0 -20
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontSize.js +0 -44
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/LineHeight.d.ts +0 -27
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/LineHeight.js +0 -44
- package/lib/src/metadata-builder/builders/update-content-text-builder.d.ts +0 -17
- package/lib/src/metadata-builder/builders/update-content-text-builder.js +0 -103
- package/lib/src/metadata-builder/builders/update-content-text-style-builder.d.ts +0 -16
- package/lib/src/metadata-builder/builders/update-content-text-style-builder.js +0 -216
- package/src/brand-page-maker/composables/rich-text-editor/extensions/FontSize.ts +0 -73
- package/src/brand-page-maker/composables/rich-text-editor/extensions/LineHeight.ts +0 -85
- package/src/metadata-builder/builders/update-content-text-builder.ts +0 -148
- package/src/metadata-builder/builders/update-content-text-style-builder.ts +0 -352
|
@@ -8,18 +8,7 @@ import {
|
|
|
8
8
|
watch,
|
|
9
9
|
} from 'vue';
|
|
10
10
|
import { Editor } from '@tiptap/vue-3';
|
|
11
|
-
import
|
|
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';
|
|
11
|
+
import { getRteExtensions } from './getRteExtensions';
|
|
23
12
|
import type { StyleTextFontFamilyOption } from '@shared/style-settings/models';
|
|
24
13
|
import { createFormattingActions } from './formatting';
|
|
25
14
|
import { useTextAlign } from './textAlign';
|
|
@@ -31,7 +20,7 @@ import {
|
|
|
31
20
|
emToPx,
|
|
32
21
|
isStyleChanged,
|
|
33
22
|
pxToEm,
|
|
34
|
-
|
|
23
|
+
sanitizeRteJson,
|
|
35
24
|
skipStyleDetection,
|
|
36
25
|
} from '@brand-page-maker/utils';
|
|
37
26
|
import type { HyperlinkType, TextStyles } from '@brand-page';
|
|
@@ -49,9 +38,7 @@ import {
|
|
|
49
38
|
import { useUiContentSettingsStore } from '@shared/content-settings/store';
|
|
50
39
|
import { useHistoryStore } from '@brand-page-maker/stores/history/history-store';
|
|
51
40
|
import { AllSelection } from 'prosemirror-state';
|
|
52
|
-
import {
|
|
53
|
-
import { AutoLink } from './extensions/AutoLink';
|
|
54
|
-
import { isHrefPageId, useHyperlink } from './hyperlink';
|
|
41
|
+
import { useHyperlink } from './hyperlink';
|
|
55
42
|
import { useBubbleMenu } from './bubbleMenu';
|
|
56
43
|
import {
|
|
57
44
|
hasLogoColorVar,
|
|
@@ -92,7 +79,7 @@ interface UseRichTextEditorReturn {
|
|
|
92
79
|
hyperlinkValue: ComputedRef<{ id: string; name: string; value: string }>;
|
|
93
80
|
setHyperlinkUrlValue: (url: string) => void;
|
|
94
81
|
shouldShow: ({ editor, state }: { editor: any; state: any }) => boolean;
|
|
95
|
-
|
|
82
|
+
bubbleMenuOptions: Record<string, any>;
|
|
96
83
|
handleAdminColorChange: (
|
|
97
84
|
color: {
|
|
98
85
|
id: string;
|
|
@@ -129,44 +116,7 @@ export const useExternalRichTextEditor = (): {
|
|
|
129
116
|
} => {
|
|
130
117
|
const externalEditor = new Editor({
|
|
131
118
|
editable: false,
|
|
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
|
-
],
|
|
119
|
+
extensions: getRteExtensions({ includeLinkRel: true }),
|
|
170
120
|
});
|
|
171
121
|
|
|
172
122
|
return {
|
|
@@ -214,54 +164,13 @@ export const useRichTextEditor = (
|
|
|
214
164
|
const editor = new Editor({
|
|
215
165
|
content: model.value ?? '',
|
|
216
166
|
editable: !props.readOnly,
|
|
217
|
-
extensions:
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
orderedList: false,
|
|
221
|
-
listItem: false,
|
|
222
|
-
horizontalRule: false,
|
|
223
|
-
codeBlock: false,
|
|
224
|
-
code: false,
|
|
225
|
-
}),
|
|
226
|
-
Underline,
|
|
227
|
-
TextStyle.configure({ mergeNestedSpanStyles: true }),
|
|
228
|
-
TextAlign.configure({
|
|
229
|
-
types: ['heading', 'paragraph'],
|
|
230
|
-
}),
|
|
231
|
-
FontFamily.configure({
|
|
232
|
-
types: ['textStyle'],
|
|
233
|
-
}),
|
|
234
|
-
FontSize,
|
|
235
|
-
LetterSpacing,
|
|
236
|
-
LineHeight,
|
|
237
|
-
ColorExtension.configure({
|
|
238
|
-
types: ['textStyle'],
|
|
239
|
-
}),
|
|
240
|
-
Highlight.configure({
|
|
241
|
-
multicolor: true,
|
|
242
|
-
}),
|
|
243
|
-
Placeholder.configure({
|
|
167
|
+
extensions: getRteExtensions({
|
|
168
|
+
restrictStarterKit: true,
|
|
169
|
+
editing: {
|
|
244
170
|
placeholder: props.placeholder,
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
openOnClick: false,
|
|
249
|
-
defaultProtocol: 'https',
|
|
250
|
-
autolink: true,
|
|
251
|
-
HTMLAttributes: (attrs) => {
|
|
252
|
-
const href = attrs.href;
|
|
253
|
-
const isExternal =
|
|
254
|
-
href && (href.startsWith('http://') || href.startsWith('https://'));
|
|
255
|
-
|
|
256
|
-
return {
|
|
257
|
-
target: isExternal ? '_blank' : null,
|
|
258
|
-
};
|
|
259
|
-
},
|
|
260
|
-
isAllowedUri: (url, ctx) =>
|
|
261
|
-
ctx.defaultValidate(url) || isHrefPageId(url),
|
|
262
|
-
}),
|
|
263
|
-
AutoLink,
|
|
264
|
-
],
|
|
171
|
+
maxLength: props.maxLength,
|
|
172
|
+
},
|
|
173
|
+
}),
|
|
265
174
|
editorProps: {
|
|
266
175
|
handleDOMEvents: {
|
|
267
176
|
drop(view, event: DragEvent) {
|
|
@@ -440,7 +349,7 @@ export const useRichTextEditor = (
|
|
|
440
349
|
applyHyperlinkToEditor,
|
|
441
350
|
} = useHyperlink(editor);
|
|
442
351
|
|
|
443
|
-
const { shouldShow,
|
|
352
|
+
const { shouldShow, bubbleMenuOptions } = useBubbleMenu(
|
|
444
353
|
editor,
|
|
445
354
|
isHyperlinkPopupOpen,
|
|
446
355
|
);
|
|
@@ -472,8 +381,14 @@ export const useRichTextEditor = (
|
|
|
472
381
|
|
|
473
382
|
const isExternalMutation = updateRichTextEditor.value || appliedSinceLast;
|
|
474
383
|
|
|
475
|
-
|
|
476
|
-
|
|
384
|
+
// Compare in the stored (JSON) format so a JSON model value isn't always
|
|
385
|
+
// seen as "changed" against the editor's HTML (which would re-run
|
|
386
|
+
// setContent and reset the user's just-applied styles).
|
|
387
|
+
const hasChanged =
|
|
388
|
+
JSON.stringify(editor.getJSON()) !== JSON.stringify(newValue);
|
|
389
|
+
|
|
390
|
+
if (isExternalMutation && hasChanged) {
|
|
391
|
+
editor.commands.setContent(newValue, { emitUpdate: false });
|
|
477
392
|
updateRichTextEditor.value = false;
|
|
478
393
|
//text style type is not switching when reordering
|
|
479
394
|
textStyleTypeOptions.forEach((item) => {
|
|
@@ -503,12 +418,13 @@ export const useRichTextEditor = (
|
|
|
503
418
|
}
|
|
504
419
|
});
|
|
505
420
|
|
|
506
|
-
// Update model when editor content changes
|
|
421
|
+
// Update model when editor content changes. RTE content is stored as Tiptap
|
|
422
|
+
// JSON (BP-5787). getJSON() is already in hand (the editor holds the doc) and
|
|
423
|
+
// is sanitised natively — no getHTML(), no DOMPurify, no extra editor — so
|
|
424
|
+
// the per-keystroke path allocates nothing heavy.
|
|
507
425
|
editor.on('update', () => {
|
|
508
|
-
const sanitizedData = sanitiseClientSideRTE(editor.getHTML());
|
|
509
|
-
|
|
510
426
|
// An update fired while the editor is unfocused is TipTap rewriting the
|
|
511
|
-
// model back into its canonical
|
|
427
|
+
// model back into its canonical form during mount rehydration or a
|
|
512
428
|
// programmatic setContent — not a user edit. Tag the emit so the write
|
|
513
429
|
// folds into the current undo head instead of pushing a phantom entry
|
|
514
430
|
// (BP-5810). The flag travels WITH the value through the debounced
|
|
@@ -522,7 +438,7 @@ export const useRichTextEditor = (
|
|
|
522
438
|
// the RTE only mounts inside an already-open settings drawer and the user
|
|
523
439
|
// focuses it before formatting, so this doesn't occur — revisit if a
|
|
524
440
|
// consumer ever drives toolbar actions on an unfocused editor.
|
|
525
|
-
emit('update:modelValue',
|
|
441
|
+
emit('update:modelValue', sanitizeRteJson(editor.getJSON()), {
|
|
526
442
|
isHydrationWrite: !editor.isFocused,
|
|
527
443
|
});
|
|
528
444
|
});
|
|
@@ -1049,7 +965,7 @@ export const useRichTextEditor = (
|
|
|
1049
965
|
hyperlinkValue,
|
|
1050
966
|
setHyperlinkUrlValue,
|
|
1051
967
|
shouldShow,
|
|
1052
|
-
|
|
968
|
+
bubbleMenuOptions,
|
|
1053
969
|
handleAdminColorChange,
|
|
1054
970
|
};
|
|
1055
971
|
};
|
|
@@ -14,6 +14,27 @@ export const useAiWebsiteBuilderThreadStore = defineStore(
|
|
|
14
14
|
const promptText = ref<string>('');
|
|
15
15
|
const stopGeneration = ref<(() => void) | null>(null);
|
|
16
16
|
|
|
17
|
+
// Bumped on every reset so consumers (useAiWebsiteBuilderChat) can react
|
|
18
|
+
// without watching `messages`, which would loop with its own deep watch.
|
|
19
|
+
const resetToken = ref<number>(0);
|
|
20
|
+
|
|
21
|
+
const reset = () => {
|
|
22
|
+
if (isProcessing.value || isBuildInProgress.value) {
|
|
23
|
+
stopGeneration.value?.();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
threadId.value = null;
|
|
27
|
+
runId.value = null;
|
|
28
|
+
messages.value = [];
|
|
29
|
+
isProcessing.value = false;
|
|
30
|
+
buildId.value = null;
|
|
31
|
+
isBuildInProgress.value = false;
|
|
32
|
+
buildSectionsCompleted.value = 0;
|
|
33
|
+
promptText.value = '';
|
|
34
|
+
stopGeneration.value = null;
|
|
35
|
+
resetToken.value += 1;
|
|
36
|
+
};
|
|
37
|
+
|
|
17
38
|
return {
|
|
18
39
|
threadId,
|
|
19
40
|
runId,
|
|
@@ -24,6 +45,8 @@ export const useAiWebsiteBuilderThreadStore = defineStore(
|
|
|
24
45
|
buildSectionsCompleted,
|
|
25
46
|
promptText,
|
|
26
47
|
stopGeneration,
|
|
48
|
+
resetToken,
|
|
49
|
+
reset,
|
|
27
50
|
};
|
|
28
51
|
},
|
|
29
52
|
);
|
|
@@ -30,6 +30,8 @@ import type {
|
|
|
30
30
|
TemplateCategory,
|
|
31
31
|
} from '@shared/template-settings/models';
|
|
32
32
|
import { useBrandPageMakerUserStore } from '../user';
|
|
33
|
+
import { useAiWebsiteBuilderThreadStore } from '../ai-thread/ai-website-builder-thread-store';
|
|
34
|
+
import { useAiCheckpointStore } from '../ai-checkpoint/ai-checkpoint-store';
|
|
33
35
|
import { defineStore } from 'pinia';
|
|
34
36
|
import { toRaw } from 'vue';
|
|
35
37
|
import { useUiStore } from '../ui';
|
|
@@ -620,6 +622,12 @@ export const useBrandPageMakerStore = defineStore('brandPageMaker', {
|
|
|
620
622
|
|
|
621
623
|
await this.recalculateLogoSizes(true);
|
|
622
624
|
|
|
625
|
+
// A template switch invalidates the AI conversation — the server-side
|
|
626
|
+
// thread's history still describes the previous template (BP-5896).
|
|
627
|
+
// reset() also stops any in-flight generation before clearing.
|
|
628
|
+
useAiWebsiteBuilderThreadStore().reset();
|
|
629
|
+
useAiCheckpointStore().clearAll();
|
|
630
|
+
|
|
623
631
|
this.setIsModifiedAfterTemplateApplied({
|
|
624
632
|
isModified: false,
|
|
625
633
|
});
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`BP-5787 RTE render parity (golden master) > T1 — editor renders the complex fixture to the frozen HTML baseline > editor-normalized-html 1`] = `"<h2 style="text-align: center;"><span style="color: var(--logo-text-color);">Themed heading</span></h2><p><strong>bold</strong> <em>italic</em> <u>underline</u> <s>strike</s> <span style="font-family: Montserrat; font-size: 1.5em; letter-spacing: 0.05em; line-height: 1.4;">styled span</span> <mark data-color="#ffe08a" style="background-color: rgb(255, 224, 138); color: inherit;">highlight</mark> <span style="color: rgb(255, 0, 0);">literal hex</span> <a href="/about" target="_self">internal link</a> <a href="https://example.com" target="_blank" rel="noopener noreferrer nofollow">external link</a></p><p style="text-align: right;">second paragraph, right-aligned</p>"`;
|
|
4
|
+
|
|
5
|
+
exports[`BP-5787 RTE render parity (golden master) > T1 — render-from-JSON (DOM-less renderer) produces equivalent HTML > serializer-html 1`] = `"<h2 style="text-align: center"><span style="color: var(--logo-text-color)">Themed heading</span></h2><p><strong>bold</strong> <em>italic</em> <u>underline</u> <s>strike</s> <span style="font-family: Montserrat; font-size: 1.5em; letter-spacing: 0.05em; line-height: 1.4">styled span</span> <mark data-color="#ffe08a" style="background-color: #ffe08a; color: inherit">highlight</mark> <span style="color: #FF0000">literal hex</span> <a href="/about" target="_self">internal link</a> <a href="https://example.com" target="_blank" rel="noopener noreferrer nofollow">external link</a></p><p style="text-align: right">second paragraph, right-aligned</p>"`;
|
|
6
|
+
|
|
7
|
+
exports[`BP-5787 RTE render parity (golden master) > T6 — freezes the JSON shape that becomes the stored form > editor-json 1`] = `
|
|
8
|
+
{
|
|
9
|
+
"content": [
|
|
10
|
+
{
|
|
11
|
+
"attrs": {
|
|
12
|
+
"level": 2,
|
|
13
|
+
"textAlign": "center",
|
|
14
|
+
},
|
|
15
|
+
"content": [
|
|
16
|
+
{
|
|
17
|
+
"marks": [
|
|
18
|
+
{
|
|
19
|
+
"attrs": {
|
|
20
|
+
"color": "var(--logo-text-color)",
|
|
21
|
+
"fontFamily": "",
|
|
22
|
+
"fontSize": "",
|
|
23
|
+
"letterSpacing": "",
|
|
24
|
+
"lineHeight": "",
|
|
25
|
+
},
|
|
26
|
+
"type": "textStyle",
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
"text": "Themed heading",
|
|
30
|
+
"type": "text",
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
"type": "heading",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"attrs": {
|
|
37
|
+
"textAlign": null,
|
|
38
|
+
},
|
|
39
|
+
"content": [
|
|
40
|
+
{
|
|
41
|
+
"marks": [
|
|
42
|
+
{
|
|
43
|
+
"type": "bold",
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
"text": "bold",
|
|
47
|
+
"type": "text",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"text": " ",
|
|
51
|
+
"type": "text",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"marks": [
|
|
55
|
+
{
|
|
56
|
+
"type": "italic",
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
"text": "italic",
|
|
60
|
+
"type": "text",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"text": " ",
|
|
64
|
+
"type": "text",
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"marks": [
|
|
68
|
+
{
|
|
69
|
+
"type": "underline",
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
"text": "underline",
|
|
73
|
+
"type": "text",
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"text": " ",
|
|
77
|
+
"type": "text",
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"marks": [
|
|
81
|
+
{
|
|
82
|
+
"type": "strike",
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
"text": "strike",
|
|
86
|
+
"type": "text",
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"text": " ",
|
|
90
|
+
"type": "text",
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"marks": [
|
|
94
|
+
{
|
|
95
|
+
"attrs": {
|
|
96
|
+
"color": "",
|
|
97
|
+
"fontFamily": "Montserrat",
|
|
98
|
+
"fontSize": "1.5em",
|
|
99
|
+
"letterSpacing": "0.05em",
|
|
100
|
+
"lineHeight": "1.4",
|
|
101
|
+
},
|
|
102
|
+
"type": "textStyle",
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
"text": "styled span",
|
|
106
|
+
"type": "text",
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"text": " ",
|
|
110
|
+
"type": "text",
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"marks": [
|
|
114
|
+
{
|
|
115
|
+
"attrs": {
|
|
116
|
+
"color": "#ffe08a",
|
|
117
|
+
},
|
|
118
|
+
"type": "highlight",
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
"text": "highlight",
|
|
122
|
+
"type": "text",
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"text": " ",
|
|
126
|
+
"type": "text",
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"marks": [
|
|
130
|
+
{
|
|
131
|
+
"attrs": {
|
|
132
|
+
"color": "#FF0000",
|
|
133
|
+
"fontFamily": "",
|
|
134
|
+
"fontSize": "",
|
|
135
|
+
"letterSpacing": "",
|
|
136
|
+
"lineHeight": "",
|
|
137
|
+
},
|
|
138
|
+
"type": "textStyle",
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
"text": "literal hex",
|
|
142
|
+
"type": "text",
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"text": " ",
|
|
146
|
+
"type": "text",
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"marks": [
|
|
150
|
+
{
|
|
151
|
+
"attrs": {
|
|
152
|
+
"class": null,
|
|
153
|
+
"href": "/about",
|
|
154
|
+
"rel": null,
|
|
155
|
+
"target": "_self",
|
|
156
|
+
"title": null,
|
|
157
|
+
},
|
|
158
|
+
"type": "link",
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
"text": "internal link",
|
|
162
|
+
"type": "text",
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"text": " ",
|
|
166
|
+
"type": "text",
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
"marks": [
|
|
170
|
+
{
|
|
171
|
+
"attrs": {
|
|
172
|
+
"class": null,
|
|
173
|
+
"href": "https://example.com",
|
|
174
|
+
"rel": "noopener noreferrer nofollow",
|
|
175
|
+
"target": "_blank",
|
|
176
|
+
"title": null,
|
|
177
|
+
},
|
|
178
|
+
"type": "link",
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
"text": "external link",
|
|
182
|
+
"type": "text",
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
"type": "paragraph",
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
"attrs": {
|
|
189
|
+
"textAlign": "right",
|
|
190
|
+
},
|
|
191
|
+
"content": [
|
|
192
|
+
{
|
|
193
|
+
"text": "second paragraph, right-aligned",
|
|
194
|
+
"type": "text",
|
|
195
|
+
},
|
|
196
|
+
],
|
|
197
|
+
"type": "paragraph",
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
"type": "doc",
|
|
201
|
+
}
|
|
202
|
+
`;
|
|
@@ -7,6 +7,8 @@ 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';
|
|
10
12
|
|
|
11
13
|
export const isColorMatched = (color: string | null | undefined): boolean => {
|
|
12
14
|
if (!color) return false;
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
getUpdatedHTMLFromEditor,
|
|
9
9
|
sanitiseClientSideRTE,
|
|
10
10
|
getRTEColor,
|
|
11
|
+
getRteTextLength,
|
|
11
12
|
} from './rich-text-editor.util';
|
|
12
13
|
import type { TextStyleDeclaration } from '@brand-page';
|
|
13
14
|
|
|
@@ -75,8 +76,40 @@ describe('hasHTML', () => {
|
|
|
75
76
|
});
|
|
76
77
|
});
|
|
77
78
|
|
|
79
|
+
describe('getRteTextLength', () => {
|
|
80
|
+
it('returns the raw length for a plain string (unchanged legacy behaviour)', () => {
|
|
81
|
+
expect(getRteTextLength('hello')).toBe(5);
|
|
82
|
+
expect(getRteTextLength('<p>hello</p>')).toBe('<p>hello</p>'.length);
|
|
83
|
+
expect(getRteTextLength('')).toBe(0);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('returns visible-text length for a Tiptap JSON doc (not undefined/throw)', () => {
|
|
87
|
+
const doc = {
|
|
88
|
+
type: 'doc',
|
|
89
|
+
content: [
|
|
90
|
+
{
|
|
91
|
+
type: 'paragraph',
|
|
92
|
+
content: [
|
|
93
|
+
{ type: 'text', text: 'hello ' },
|
|
94
|
+
{ type: 'text', marks: [{ type: 'bold' }], text: 'world' },
|
|
95
|
+
],
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// "hello world" — measured from the text nodes, not the JSON wrapper.
|
|
101
|
+
expect(getRteTextLength(doc)).toBe(11);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('returns 0 for null/undefined and an empty doc', () => {
|
|
105
|
+
expect(getRteTextLength(null)).toBe(0);
|
|
106
|
+
expect(getRteTextLength(undefined)).toBe(0);
|
|
107
|
+
expect(getRteTextLength({ type: 'doc', content: [] })).toBe(0);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
78
111
|
describe('getUpdatedHTMLFromEditor', () => {
|
|
79
|
-
it('applies style commands and returns updated
|
|
112
|
+
it('applies style commands and returns updated JSON', () => {
|
|
80
113
|
// Mock editor
|
|
81
114
|
const runMock = vi.fn();
|
|
82
115
|
const chain = {
|
|
@@ -119,7 +152,7 @@ describe('getUpdatedHTMLFromEditor', () => {
|
|
|
119
152
|
},
|
|
120
153
|
},
|
|
121
154
|
chain: () => chain as any,
|
|
122
|
-
|
|
155
|
+
getJSON: () => ({ type: 'doc', content: [{ type: 'paragraph' }] }),
|
|
123
156
|
} as unknown as Editor;
|
|
124
157
|
|
|
125
158
|
const newStyle = {
|
|
@@ -137,7 +170,8 @@ describe('getUpdatedHTMLFromEditor', () => {
|
|
|
137
170
|
|
|
138
171
|
expect(externalEditor.commands.setContent).toHaveBeenCalledWith('foo');
|
|
139
172
|
expect(runMock).toHaveBeenCalled();
|
|
140
|
-
|
|
173
|
+
// BP-5787: returns the JSON form (consistent with JSON storage).
|
|
174
|
+
expect(result).toEqual({ type: 'doc', content: [{ type: 'paragraph' }] });
|
|
141
175
|
});
|
|
142
176
|
});
|
|
143
177
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ChainedCommands, Editor } from '@tiptap/vue-3';
|
|
1
|
+
import type { ChainedCommands, Editor, JSONContent } from '@tiptap/vue-3';
|
|
2
2
|
import type { EditorState, Transaction, Selection } from 'prosemirror-state';
|
|
3
3
|
import {
|
|
4
4
|
STYLE_THEME_KEYS,
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
type RequestPaymentContent,
|
|
20
20
|
type SupportMeContent,
|
|
21
21
|
type ShopContent,
|
|
22
|
+
type RichTextValue,
|
|
22
23
|
} from '@brand-page';
|
|
23
24
|
import type { PageSettingsState } from '@shared/page-settings/store';
|
|
24
25
|
import type { StyleSettingsState } from '@shared/style-settings/store';
|
|
@@ -243,8 +244,38 @@ export const skipStyleDetection = (chain: ChainedCommands, marks?: Mark[]) =>
|
|
|
243
244
|
return true;
|
|
244
245
|
});
|
|
245
246
|
|
|
246
|
-
|
|
247
|
-
|
|
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;
|
|
248
279
|
|
|
249
280
|
const trimmed = content.trim();
|
|
250
281
|
const tagWrapRegex = /^<([A-Za-z][A-Za-z0-9-]*)\b[^>]*>[\s\S]*<\/\1>$/;
|
|
@@ -252,7 +283,11 @@ export const hasRTEContent = (content: string): boolean => {
|
|
|
252
283
|
return tagWrapRegex.test(trimmed);
|
|
253
284
|
};
|
|
254
285
|
|
|
255
|
-
export const getPlainTextFromRTE = (content:
|
|
286
|
+
export const getPlainTextFromRTE = (content: unknown): string => {
|
|
287
|
+
if (isTiptapDocument(content)) {
|
|
288
|
+
return getRteJsonPlainText(content);
|
|
289
|
+
}
|
|
290
|
+
|
|
256
291
|
if (typeof content !== 'string') {
|
|
257
292
|
return '';
|
|
258
293
|
}
|
|
@@ -274,6 +309,22 @@ export const getPlainTextFromRTE = (content: string): string => {
|
|
|
274
309
|
return plainText.trim();
|
|
275
310
|
};
|
|
276
311
|
|
|
312
|
+
/**
|
|
313
|
+
* BP-5787 — visible-text length of an RTE value, for the maker item-field
|
|
314
|
+
* max-length / emptiness checks. A plain string keeps its raw `.length`, so the
|
|
315
|
+
* pre-JSON behaviour is byte-for-byte identical; a Tiptap JSON doc measures its
|
|
316
|
+
* concatenated visible text. Without this, `doc.length` is `undefined` (silently
|
|
317
|
+
* disabling the check) and `doc.trim()` throws — the class of bug that survived
|
|
318
|
+
* the `RichTextValue` widening because Vue Options-API `this`/item access is `any`.
|
|
319
|
+
*/
|
|
320
|
+
export const getRteTextLength = (
|
|
321
|
+
value: RichTextValue | null | undefined,
|
|
322
|
+
): number => {
|
|
323
|
+
if (typeof value === 'string') return value.length;
|
|
324
|
+
|
|
325
|
+
return value ? getRteJsonPlainText(value).length : 0;
|
|
326
|
+
};
|
|
327
|
+
|
|
277
328
|
export const replaceTextInRTE = (
|
|
278
329
|
existingHtml: string,
|
|
279
330
|
newText: string,
|
|
@@ -721,16 +772,20 @@ export const switchAllContentToRTE = (
|
|
|
721
772
|
|
|
722
773
|
export const getUpdatedHTMLFromEditor = (
|
|
723
774
|
externalEditor: Editor,
|
|
724
|
-
contentText:
|
|
775
|
+
contentText: RichTextValue,
|
|
725
776
|
newContentStyle: TextStyleDeclaration,
|
|
726
777
|
) => {
|
|
727
778
|
if (!contentText) {
|
|
728
779
|
return contentText;
|
|
729
780
|
}
|
|
730
781
|
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
782
|
+
// BP-5787: accept a string (legacy HTML) or a Tiptap JSON doc. setContent
|
|
783
|
+
// handles both; only the newline-escaping shim applies to strings.
|
|
784
|
+
const content =
|
|
785
|
+
typeof contentText === 'string' &&
|
|
786
|
+
contentText.includes(ESCAPED_NEWLINE_CHARACTER)
|
|
787
|
+
? getRenderText(contentText)
|
|
788
|
+
: contentText;
|
|
734
789
|
|
|
735
790
|
externalEditor.commands.setContent(content);
|
|
736
791
|
|
|
@@ -788,9 +843,9 @@ export const getUpdatedHTMLFromEditor = (
|
|
|
788
843
|
chain = newContentStyle?.fontStyle ? chain.setItalic() : chain.unsetItalic();
|
|
789
844
|
|
|
790
845
|
chain.run();
|
|
791
|
-
const html = externalEditor.getHTML();
|
|
792
846
|
|
|
793
|
-
return
|
|
847
|
+
// BP-5787: return the JSON form, consistent with how RTE is now stored.
|
|
848
|
+
return externalEditor.getJSON();
|
|
794
849
|
};
|
|
795
850
|
|
|
796
851
|
// Helper to build stored marks from a style declaration
|
|
@@ -914,7 +969,7 @@ export const sanitiseClientSideRTE = (html: string): SanitizationResult => {
|
|
|
914
969
|
/**
|
|
915
970
|
* Validates if an href value is safe and returns a sanitized version
|
|
916
971
|
*/
|
|
917
|
-
const validateAndSanitizeHref = (href: string): string | null => {
|
|
972
|
+
export const validateAndSanitizeHref = (href: string): string | null => {
|
|
918
973
|
if (!href || typeof href !== 'string') return null;
|
|
919
974
|
|
|
920
975
|
// Trim whitespace and decode entities
|