@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
|
@@ -3,7 +3,6 @@ import type {
|
|
|
3
3
|
BrandPageModel,
|
|
4
4
|
TextStyleDeclaration,
|
|
5
5
|
} from '../../../brand-page/models';
|
|
6
|
-
import { tiptapJsonToHtml } from '../../helpers/rte.helpers';
|
|
7
6
|
import type { BuilderResult } from '../../models';
|
|
8
7
|
import {
|
|
9
8
|
isTiptapDocument,
|
|
@@ -41,7 +40,7 @@ export class ProfileSectionBuilder extends BaseSectionBuilder<ProfileContent> {
|
|
|
41
40
|
|
|
42
41
|
if (profileArgs.name !== undefined) {
|
|
43
42
|
if (isTiptapDocument(profileArgs.name)) {
|
|
44
|
-
resolved.content.name =
|
|
43
|
+
resolved.content.name = profileArgs.name;
|
|
45
44
|
resolved.content.nameStyle = resolveGlobalPresetIntoStyle(
|
|
46
45
|
resolved.content.nameStyle,
|
|
47
46
|
textStyleOptions,
|
|
@@ -53,7 +52,7 @@ export class ProfileSectionBuilder extends BaseSectionBuilder<ProfileContent> {
|
|
|
53
52
|
|
|
54
53
|
if (profileArgs.title !== undefined) {
|
|
55
54
|
if (isTiptapDocument(profileArgs.title)) {
|
|
56
|
-
resolved.content.title =
|
|
55
|
+
resolved.content.title = profileArgs.title;
|
|
57
56
|
resolved.content.titleStyle = resolveGlobalPresetIntoStyle(
|
|
58
57
|
resolved.content.titleStyle,
|
|
59
58
|
textStyleOptions,
|
|
@@ -65,9 +64,7 @@ export class ProfileSectionBuilder extends BaseSectionBuilder<ProfileContent> {
|
|
|
65
64
|
|
|
66
65
|
if (profileArgs.description !== undefined) {
|
|
67
66
|
if (isTiptapDocument(profileArgs.description)) {
|
|
68
|
-
resolved.content.description =
|
|
69
|
-
profileArgs.description,
|
|
70
|
-
);
|
|
67
|
+
resolved.content.description = profileArgs.description;
|
|
71
68
|
resolved.content.descriptionStyle = resolveGlobalPresetIntoStyle(
|
|
72
69
|
resolved.content.descriptionStyle,
|
|
73
70
|
textStyleOptions,
|
|
@@ -5,7 +5,6 @@ import type {
|
|
|
5
5
|
TextShapeItem,
|
|
6
6
|
TextStyleDeclaration,
|
|
7
7
|
} from '../../../brand-page/models';
|
|
8
|
-
import { tiptapJsonToHtml } from '../../helpers/rte.helpers';
|
|
9
8
|
import type { BuilderResult } from '../../models';
|
|
10
9
|
import {
|
|
11
10
|
isTiptapDocument,
|
|
@@ -195,7 +194,7 @@ export class ShapesSectionBuilder extends BaseSectionBuilder<ShapesSectionConten
|
|
|
195
194
|
|
|
196
195
|
if (args.title !== undefined) {
|
|
197
196
|
if (isTiptapDocument(args.title)) {
|
|
198
|
-
content.title =
|
|
197
|
+
content.title = args.title;
|
|
199
198
|
content.titleStyle = resolveGlobalPresetIntoStyle(
|
|
200
199
|
content.titleStyle,
|
|
201
200
|
textStyleOptions,
|
|
@@ -207,7 +206,7 @@ export class ShapesSectionBuilder extends BaseSectionBuilder<ShapesSectionConten
|
|
|
207
206
|
|
|
208
207
|
if (args.description !== undefined) {
|
|
209
208
|
if (isTiptapDocument(args.description)) {
|
|
210
|
-
content.description =
|
|
209
|
+
content.description = args.description;
|
|
211
210
|
content.descriptionStyle = resolveGlobalPresetIntoStyle(
|
|
212
211
|
content.descriptionStyle,
|
|
213
212
|
textStyleOptions,
|
|
@@ -4,7 +4,6 @@ import type {
|
|
|
4
4
|
BrandPageModel,
|
|
5
5
|
TextStyleDeclaration,
|
|
6
6
|
} from '../../../brand-page/models';
|
|
7
|
-
import { tiptapJsonToHtml } from '../../helpers/rte.helpers';
|
|
8
7
|
import {
|
|
9
8
|
isTiptapDocument,
|
|
10
9
|
type TestimonialItem,
|
|
@@ -173,7 +172,7 @@ export class TestimonialSectionBuilder extends BaseSectionBuilder<TestimonialSec
|
|
|
173
172
|
|
|
174
173
|
if (args.title !== undefined) {
|
|
175
174
|
if (isTiptapDocument(args.title)) {
|
|
176
|
-
content.title =
|
|
175
|
+
content.title = args.title;
|
|
177
176
|
content.titleStyle = resolveGlobalPresetIntoStyle(
|
|
178
177
|
content.titleStyle,
|
|
179
178
|
textStyleOptions,
|
|
@@ -4,9 +4,9 @@ import type {
|
|
|
4
4
|
TextItem,
|
|
5
5
|
TextStyleDeclaration,
|
|
6
6
|
BrandPageModel,
|
|
7
|
+
RichTextValue,
|
|
7
8
|
} from '../../../brand-page/models';
|
|
8
9
|
import type { TextStyleOptions } from '../../../brand-page/models/styles.model';
|
|
9
|
-
import { tiptapJsonToHtml } from '../../helpers/rte.helpers';
|
|
10
10
|
import {
|
|
11
11
|
isTiptapDocument,
|
|
12
12
|
type TextSectionArguments,
|
|
@@ -26,12 +26,18 @@ import {
|
|
|
26
26
|
repositionItemByAnchor,
|
|
27
27
|
} from '../item-id-utils';
|
|
28
28
|
|
|
29
|
-
/**
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Resolve a RichTextField to its stored RTE value. BP-5787: Tiptap JSON is
|
|
31
|
+
* stored as-is (no JSON→HTML round-trip — migration-20 is now a one-time
|
|
32
|
+
* legacy conversion), plain strings pass through. `wasTiptap` drives the
|
|
33
|
+
* text-style preset resolution for rich content.
|
|
34
|
+
*/
|
|
35
|
+
const resolveText = (
|
|
36
|
+
value: unknown,
|
|
37
|
+
): { value: RichTextValue; wasTiptap: boolean } => {
|
|
38
|
+
if (isTiptapDocument(value)) return { value, wasTiptap: true };
|
|
33
39
|
|
|
34
|
-
return {
|
|
40
|
+
return { value: typeof value === 'string' ? value : '', wasTiptap: false };
|
|
35
41
|
};
|
|
36
42
|
|
|
37
43
|
const clearTextStyleTypeIfTiptap = (
|
|
@@ -55,11 +61,11 @@ const createTextItem = (
|
|
|
55
61
|
item.text_style ? { ...baseStyle, ...item.text_style } : { ...baseStyle }
|
|
56
62
|
) as TextStyleDeclaration;
|
|
57
63
|
|
|
58
|
-
const {
|
|
64
|
+
const { value, wasTiptap } = resolveText(item.text);
|
|
59
65
|
|
|
60
66
|
return {
|
|
61
67
|
id: item.item_id || generateItemId(),
|
|
62
|
-
text:
|
|
68
|
+
text: value,
|
|
63
69
|
textStyle: clearTextStyleTypeIfTiptap(
|
|
64
70
|
textStyle,
|
|
65
71
|
wasTiptap,
|
|
@@ -111,9 +117,9 @@ const mergeTextItems = (
|
|
|
111
117
|
if (!existing) continue;
|
|
112
118
|
|
|
113
119
|
if (item.text !== undefined) {
|
|
114
|
-
const {
|
|
120
|
+
const { value, wasTiptap } = resolveText(item.text);
|
|
115
121
|
|
|
116
|
-
existing.text =
|
|
122
|
+
existing.text = value;
|
|
117
123
|
existing.textStyle = clearTextStyleTypeIfTiptap(
|
|
118
124
|
existing.textStyle || {},
|
|
119
125
|
wasTiptap,
|
|
@@ -160,12 +166,12 @@ const applySingleTextField = (
|
|
|
160
166
|
textStyleOptions: TextStyleOptions | undefined,
|
|
161
167
|
): void => {
|
|
162
168
|
if (args.text !== undefined) {
|
|
163
|
-
const {
|
|
169
|
+
const { value, wasTiptap } = resolveText(args.text);
|
|
164
170
|
|
|
165
171
|
if (Array.isArray(content.textItems) && content.textItems.length > 0) {
|
|
166
172
|
content.textItems[0] = {
|
|
167
173
|
...content.textItems[0],
|
|
168
|
-
text:
|
|
174
|
+
text: value,
|
|
169
175
|
textStyle: clearTextStyleTypeIfTiptap(
|
|
170
176
|
content.textItems[0].textStyle || {},
|
|
171
177
|
wasTiptap,
|
|
@@ -176,7 +182,7 @@ const applySingleTextField = (
|
|
|
176
182
|
content.textItems = [
|
|
177
183
|
{
|
|
178
184
|
id: generateItemId(),
|
|
179
|
-
text:
|
|
185
|
+
text: value,
|
|
180
186
|
textStyle: clearTextStyleTypeIfTiptap(
|
|
181
187
|
{ ...(content.textStyle || {}) },
|
|
182
188
|
wasTiptap,
|
|
@@ -1,114 +1,28 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { getRenderText } from '../../brand-page/utils';
|
|
5
|
-
import { ESCAPED_NEWLINE_CHARACTER } from '../../constants/ui';
|
|
6
|
-
import type { Editor } from '@tiptap/vue-3';
|
|
7
|
-
import {
|
|
8
|
-
isTiptapDocument,
|
|
9
|
-
type TiptapDocument,
|
|
10
|
-
} from '../parsers/section-tool-arguments';
|
|
11
|
-
|
|
12
|
-
export const applyStyleToRTEText = (
|
|
13
|
-
externalEditor: Editor,
|
|
14
|
-
contentText: string,
|
|
15
|
-
newContentStyle: TextStyleDeclaration,
|
|
16
|
-
) => {
|
|
17
|
-
if (!contentText) {
|
|
18
|
-
return contentText;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const content = contentText.includes(ESCAPED_NEWLINE_CHARACTER)
|
|
22
|
-
? getRenderText(contentText)
|
|
23
|
-
: contentText;
|
|
24
|
-
|
|
25
|
-
externalEditor.commands.setContent(content);
|
|
26
|
-
|
|
27
|
-
let chain = externalEditor.chain().selectAll();
|
|
28
|
-
|
|
29
|
-
if (newContentStyle?.fontFamily) {
|
|
30
|
-
chain = chain.setFontFamily(newContentStyle.fontFamily);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (newContentStyle?.fontSize) {
|
|
34
|
-
chain = chain.setFontSize(newContentStyle.fontSize);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (newContentStyle?.letterSpacing) {
|
|
38
|
-
chain = chain.setLetterSpacing(newContentStyle.letterSpacing);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (newContentStyle?.lineHeight) {
|
|
42
|
-
chain = chain.setLineHeight(newContentStyle.lineHeight);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (newContentStyle?.color) {
|
|
46
|
-
chain = chain.setMark('textStyle', {
|
|
47
|
-
color: getRTEColor(newContentStyle.color, 'text'),
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Handle backgroundColor
|
|
52
|
-
if ('backgroundColor' in (newContentStyle || {})) {
|
|
53
|
-
chain = newContentStyle.backgroundColor
|
|
54
|
-
? chain.setMark('highlight', {
|
|
55
|
-
color: getRTEColor(newContentStyle.backgroundColor, 'background'),
|
|
56
|
-
})
|
|
57
|
-
: chain.unsetHighlight();
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (newContentStyle?.textAlign) {
|
|
61
|
-
chain = chain.setTextAlign(newContentStyle.textAlign);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const hasStrike = newContentStyle?.textDecoration?.includes('line-through');
|
|
65
|
-
const hasUnderline = newContentStyle?.textDecoration?.includes('underline');
|
|
66
|
-
|
|
67
|
-
chain = hasStrike ? chain.setStrike() : chain.unsetStrike();
|
|
68
|
-
chain = hasUnderline ? chain.setUnderline() : chain.unsetUnderline();
|
|
69
|
-
|
|
70
|
-
chain =
|
|
71
|
-
newContentStyle?.fontWeight === 'bold'
|
|
72
|
-
? chain.setBold()
|
|
73
|
-
: chain.unsetBold();
|
|
74
|
-
chain =
|
|
75
|
-
newContentStyle?.fontStyle === 'italic'
|
|
76
|
-
? chain.setItalic()
|
|
77
|
-
: chain.unsetItalic();
|
|
78
|
-
|
|
79
|
-
chain.run();
|
|
80
|
-
const html = externalEditor.getHTML();
|
|
81
|
-
|
|
82
|
-
return html;
|
|
83
|
-
};
|
|
1
|
+
import type { RichTextValue } from '../../brand-page';
|
|
2
|
+
import { renderRteToHtml } from '../../brand-page-maker/utils';
|
|
3
|
+
import { isTiptapDocument } from '../parsers/section-tool-arguments';
|
|
84
4
|
|
|
85
5
|
/**
|
|
86
6
|
* Converts a Tiptap JSON document (ProseMirror document model) to an HTML
|
|
87
|
-
* string
|
|
7
|
+
* string via Tiptap's schema-driven static renderer (renderRteToHtml →
|
|
8
|
+
* renderToHTMLString). DOM-less and editor-free, so it runs on the server
|
|
9
|
+
* (Nitro/Node), not just the browser — unlike the previous headless-Editor
|
|
10
|
+
* implementation (BP-5787: the AI section builders no longer need a `document`).
|
|
88
11
|
*
|
|
89
12
|
* Returns the value unchanged if it is already a string.
|
|
90
|
-
* Returns an empty string for null/undefined.
|
|
13
|
+
* Returns an empty string for null/undefined or any non-Tiptap-document value.
|
|
91
14
|
*
|
|
92
|
-
* Note:
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
* `
|
|
97
|
-
*
|
|
98
|
-
* `BuilderResult.errors`, which the AI-chat retry loop reads via
|
|
99
|
-
* `tool_result.errorMessage` and uses to re-emit a corrected payload.
|
|
15
|
+
* Note: to prevent saved copy being wiped (BP-5669), structural validity of any
|
|
16
|
+
* AI-emitted Tiptap doc is checked upstream by `rteSchemaCheck` in
|
|
17
|
+
* `section-args-validator.ts` against the editor's real ProseMirror schema. The
|
|
18
|
+
* validator surfaces a clear error into `BuilderResult.errors`, which the
|
|
19
|
+
* AI-chat retry loop reads via `tool_result.errorMessage` and uses to re-emit a
|
|
20
|
+
* corrected payload.
|
|
100
21
|
*/
|
|
101
22
|
export const tiptapJsonToHtml = (value: unknown): string => {
|
|
102
23
|
if (typeof value === 'string') return value;
|
|
103
24
|
|
|
104
25
|
if (!isTiptapDocument(value)) return '';
|
|
105
26
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
externalEditor.commands.setContent(value as TiptapDocument);
|
|
109
|
-
const html = externalEditor.getHTML();
|
|
110
|
-
|
|
111
|
-
externalEditor.destroy();
|
|
112
|
-
|
|
113
|
-
return html;
|
|
27
|
+
return renderRteToHtml(value as RichTextValue);
|
|
114
28
|
};
|
|
@@ -8,8 +8,6 @@ import {
|
|
|
8
8
|
BUILDER_TYPES,
|
|
9
9
|
type BuilderResult,
|
|
10
10
|
} from './models';
|
|
11
|
-
import { UpdateContentTextBuilder } from './builders/update-content-text-builder';
|
|
12
|
-
import { UpdateContentTextStyleBuilder } from './builders/update-content-text-style-builder';
|
|
13
11
|
import {
|
|
14
12
|
BannerSectionBuilder,
|
|
15
13
|
BookingSectionBuilder,
|
|
@@ -48,9 +46,6 @@ type BuilderConstructor = new (
|
|
|
48
46
|
|
|
49
47
|
// Lazy initialization to avoid circular dependency issues
|
|
50
48
|
const getBuilderRegistry = (): Record<BuilderType, BuilderConstructor> => ({
|
|
51
|
-
[BUILDER_TYPES.UPDATE_CONTENT_TEXT]: UpdateContentTextBuilder,
|
|
52
|
-
[BUILDER_TYPES.UPDATE_CONTENT_TEXT_STYLE]: UpdateContentTextStyleBuilder,
|
|
53
|
-
|
|
54
49
|
// Section builders (create)
|
|
55
50
|
[BUILDER_TYPES.CREATE_BANNER_SECTION]: BannerSectionBuilder,
|
|
56
51
|
[BUILDER_TYPES.CREATE_BOOKING_SECTION]: BookingSectionBuilder,
|
|
@@ -3,9 +3,6 @@ import type { EventTracker } from '../brand-page-maker/composables/useEventTrack
|
|
|
3
3
|
import type { Logger } from './logger';
|
|
4
4
|
|
|
5
5
|
export const BUILDER_TYPES = {
|
|
6
|
-
UPDATE_CONTENT_TEXT: 'update-content-text',
|
|
7
|
-
UPDATE_CONTENT_TEXT_STYLE: 'update-content-text-style',
|
|
8
|
-
|
|
9
6
|
// Per-section builder types (create)
|
|
10
7
|
CREATE_BANNER_SECTION: 'create-banner-section',
|
|
11
8
|
CREATE_ABOUT_SECTION: 'create-about-section',
|
|
@@ -7,15 +7,10 @@ import type {
|
|
|
7
7
|
import type { SectionType, TextStyleArguments } from './section-tool-arguments';
|
|
8
8
|
|
|
9
9
|
// Tool names — must match the function names generated by the AI pipeline.
|
|
10
|
-
// Text element tools use camelCase names.
|
|
11
10
|
// Section tools follow the pattern: build_{sectionType}_section
|
|
12
11
|
// where sectionType matches the ContentType value (e.g. 'banner-section', 'profile').
|
|
13
12
|
// The mode ('create' | 'update') is a field in the tool arguments, not the tool name.
|
|
14
13
|
export const AI_ASSISTANT_TOOL_NAMES = {
|
|
15
|
-
// Text element agent tools
|
|
16
|
-
UPDATE_TEXT: 'updateText',
|
|
17
|
-
UPDATE_STYLE: 'updateStyle',
|
|
18
|
-
|
|
19
14
|
// Website builder agent tools — unified build
|
|
20
15
|
BUILD_PROFILE_SECTION: 'build_profile_section',
|
|
21
16
|
BUILD_TEXT_SECTION: 'build_text_section',
|
|
@@ -84,28 +79,6 @@ export interface ParsedToolCall {
|
|
|
84
79
|
usage_type?: 'free' | 'paid';
|
|
85
80
|
}
|
|
86
81
|
|
|
87
|
-
export interface BaseArguments {
|
|
88
|
-
message?: string;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// updateText tool arguments
|
|
92
|
-
export interface UpdateTextToolArguments extends BaseArguments {
|
|
93
|
-
contentId: string;
|
|
94
|
-
fieldName: string;
|
|
95
|
-
metadata: {
|
|
96
|
-
text: string;
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// updateStyle tool arguments
|
|
101
|
-
export interface UpdateStyleToolArguments extends BaseArguments {
|
|
102
|
-
contentId: string;
|
|
103
|
-
fieldName: string;
|
|
104
|
-
metadata: {
|
|
105
|
-
style: BrandPageCssStyle;
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
82
|
// Maps each section tool name to its SectionType.
|
|
110
83
|
// Mode is now determined from the tool arguments, not the tool name.
|
|
111
84
|
export const SECTION_TOOL_MAP: Record<string, { sectionType: SectionType }> = {
|
|
@@ -1,16 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import TextStyle from '@tiptap/extension-text-style';
|
|
5
|
-
import TextAlign from '@tiptap/extension-text-align';
|
|
6
|
-
import FontFamily from '@tiptap/extension-font-family';
|
|
7
|
-
import ColorExtension from '@tiptap/extension-color';
|
|
8
|
-
import Highlight from '@tiptap/extension-highlight';
|
|
9
|
-
import Link from '@tiptap/extension-link';
|
|
10
|
-
import { FontSize } from '../../brand-page-maker/composables/rich-text-editor/extensions/FontSize';
|
|
11
|
-
import { LetterSpacing } from '../../brand-page-maker/composables/rich-text-editor/extensions/LetterSpacing';
|
|
12
|
-
import { LineHeight } from '../../brand-page-maker/composables/rich-text-editor/extensions/LineHeight';
|
|
13
|
-
import { AutoLink } from '../../brand-page-maker/composables/rich-text-editor/extensions/AutoLink';
|
|
1
|
+
import { getSchema } from '@tiptap/core';
|
|
2
|
+
import type { Schema } from '@tiptap/pm/model';
|
|
3
|
+
import { getRteExtensions } from '../../brand-page-maker/composables/rich-text-editor/getRteExtensions';
|
|
14
4
|
import { isTiptapDocument } from '../parsers/section-tool-arguments';
|
|
15
5
|
import {
|
|
16
6
|
rteStyleCheck,
|
|
@@ -19,45 +9,30 @@ import {
|
|
|
19
9
|
} from './section-args-validator';
|
|
20
10
|
|
|
21
11
|
/**
|
|
22
|
-
*
|
|
23
|
-
* `
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
12
|
+
* The ProseMirror schema built from the SAME extension set the editor and
|
|
13
|
+
* renderer use (`getRteExtensions`), so validation matches real editor output.
|
|
14
|
+
* `getSchema` is DOM-less (no Editor instance) and the schema is pure, so it is
|
|
15
|
+
* built once and cached.
|
|
16
|
+
*
|
|
17
|
+
* We import `getRteExtensions` (a leaf module — @tiptap + prosemirror only), not
|
|
18
|
+
* the maker composable (`useExternalRichTextEditor`), which would pull in the
|
|
19
|
+
* AI executor → section-builders → section-rules graph and re-enter this file
|
|
20
|
+
* before its exports are bound.
|
|
27
21
|
*/
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
StarterKit,
|
|
33
|
-
Underline,
|
|
34
|
-
TextStyle.configure({ mergeNestedSpanStyles: true }),
|
|
35
|
-
TextAlign.configure({ types: ['heading', 'paragraph'] }),
|
|
36
|
-
FontFamily.configure({ types: ['textStyle'] }),
|
|
37
|
-
FontSize,
|
|
38
|
-
LetterSpacing,
|
|
39
|
-
LineHeight,
|
|
40
|
-
ColorExtension.configure({ types: ['textStyle'] }),
|
|
41
|
-
Highlight.configure({ multicolor: true }),
|
|
42
|
-
Link.configure({
|
|
43
|
-
openOnClick: false,
|
|
44
|
-
defaultProtocol: 'https',
|
|
45
|
-
autolink: true,
|
|
46
|
-
}),
|
|
47
|
-
AutoLink,
|
|
48
|
-
],
|
|
49
|
-
});
|
|
22
|
+
let cachedSchema: Schema | undefined;
|
|
23
|
+
|
|
24
|
+
const getRteSchema = (): Schema =>
|
|
25
|
+
(cachedSchema ??= getSchema(getRteExtensions({ includeLinkRel: true })));
|
|
50
26
|
|
|
51
27
|
/**
|
|
52
|
-
* Validates a Tiptap doc against the
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* the doc to `<p></p>` (BP-5669).
|
|
28
|
+
* Validates a Tiptap doc against the real ProseMirror schema
|
|
29
|
+
* (`schema.nodeFromJSON`). Catches malformed AI input — stray non-node children
|
|
30
|
+
* in `content`, unknown node types, malformed attrs — before the builder runs
|
|
31
|
+
* and the renderer's silent `setContent` drops the doc to `<p></p>` (BP-5669).
|
|
57
32
|
*
|
|
58
|
-
* Returns the field-prefixed error string so the AI's tool-call retry
|
|
59
|
-
*
|
|
60
|
-
*
|
|
33
|
+
* Returns the field-prefixed error string so the AI's tool-call retry loop reads
|
|
34
|
+
* it via `BuilderResult.errors` → `tool_result.errorMessage` and re-emits a
|
|
35
|
+
* corrected payload.
|
|
61
36
|
*/
|
|
62
37
|
export const rteSchemaCheck =
|
|
63
38
|
(field: string): SectionCheck =>
|
|
@@ -66,18 +41,14 @@ export const rteSchemaCheck =
|
|
|
66
41
|
|
|
67
42
|
if (!isTiptapDocument(value)) return null;
|
|
68
43
|
|
|
69
|
-
const editor = buildHeadlessEditor();
|
|
70
|
-
|
|
71
44
|
try {
|
|
72
|
-
|
|
45
|
+
getRteSchema().nodeFromJSON(value);
|
|
73
46
|
|
|
74
47
|
return null;
|
|
75
48
|
} catch (err) {
|
|
76
49
|
const detail = err instanceof Error ? err.message : String(err);
|
|
77
50
|
|
|
78
51
|
return `"${field}" is not valid Tiptap JSON: ${detail}`;
|
|
79
|
-
} finally {
|
|
80
|
-
editor.destroy();
|
|
81
52
|
}
|
|
82
53
|
};
|
|
83
54
|
|
|
@@ -175,7 +175,23 @@ export default defineComponent({
|
|
|
175
175
|
},
|
|
176
176
|
},
|
|
177
177
|
watch: {
|
|
178
|
-
value(newVal: string | undefined) {
|
|
178
|
+
value(newVal: string | undefined, oldVal: string | undefined) {
|
|
179
|
+
// BP-5787: RTE values are Tiptap JSON objects. The parent (TextItemsField)
|
|
180
|
+
// re-clones its items array (cloneDeep) on ANY change — including a sibling
|
|
181
|
+
// field's style sync — yielding a NEW object reference with identical text
|
|
182
|
+
// content. For objects Vue fires this watcher on reference change, so that
|
|
183
|
+
// spurious re-clone would overwrite `_value` and clobber an in-flight edit
|
|
184
|
+
// (a mark applied but not yet debounced to the store), silently dropping
|
|
185
|
+
// the user's formatting. Strings never hit this (same content = same
|
|
186
|
+
// reference = no fire), so mirror that: ignore re-clones whose content is
|
|
187
|
+
// unchanged from the previous prop value.
|
|
188
|
+
if (
|
|
189
|
+
this.showRichTextEditor &&
|
|
190
|
+
JSON.stringify(newVal) === JSON.stringify(oldVal)
|
|
191
|
+
) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
179
195
|
this._value = newVal;
|
|
180
196
|
|
|
181
197
|
if (this.showRichTextEditor) {
|
|
@@ -21,6 +21,9 @@ import {
|
|
|
21
21
|
replaceTextInRTE,
|
|
22
22
|
RTE_ALLOWED_SECTION_TYPES,
|
|
23
23
|
isRTEManagedStyleField,
|
|
24
|
+
isTiptapDocument,
|
|
25
|
+
renderRteToHtml,
|
|
26
|
+
toRteJson,
|
|
24
27
|
} from '@brand-page-maker/utils';
|
|
25
28
|
import { isDefaultContentText } from '@shared/content-settings/utils';
|
|
26
29
|
import { useUiLogoSettingsStore } from '../../../logo-settings/store';
|
|
@@ -327,10 +330,20 @@ export default defineComponent({
|
|
|
327
330
|
this.isToggled = isToggled;
|
|
328
331
|
},
|
|
329
332
|
onUseGeneratedText(generatedText: string) {
|
|
330
|
-
let formattedText = generatedText;
|
|
333
|
+
let formattedText: unknown = generatedText;
|
|
331
334
|
|
|
332
|
-
|
|
333
|
-
|
|
335
|
+
// BP-5787: preserve the field's formatting when dropping in AI text.
|
|
336
|
+
// replaceTextInRTE is HTML-based, so JSON is rendered/parsed around it
|
|
337
|
+
// (cold AI path, shared editor); string fields use it directly.
|
|
338
|
+
if (this.showRichTextEditor) {
|
|
339
|
+
if (isTiptapDocument(this.value)) {
|
|
340
|
+
formattedText =
|
|
341
|
+
toRteJson(
|
|
342
|
+
replaceTextInRTE(renderRteToHtml(this.value), generatedText),
|
|
343
|
+
) ?? generatedText;
|
|
344
|
+
} else if (typeof this.value === 'string') {
|
|
345
|
+
formattedText = replaceTextInRTE(this.value, generatedText);
|
|
346
|
+
}
|
|
334
347
|
}
|
|
335
348
|
|
|
336
349
|
this.contentSettingsStore.updateItemValue({
|
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
ColorReplacement,
|
|
5
5
|
Content,
|
|
6
6
|
LogoTheme,
|
|
7
|
+
RichTextValue,
|
|
7
8
|
TestimonialItems,
|
|
8
9
|
} from '@brand-page';
|
|
9
10
|
import {
|
|
@@ -17,7 +18,7 @@ import debounce from 'lodash.debounce';
|
|
|
17
18
|
import type { UpdateItemValuePayload } from '../../models';
|
|
18
19
|
import cloneDeep from 'lodash.clonedeep';
|
|
19
20
|
import { useSnapshotStore } from '@brand-page-maker/stores';
|
|
20
|
-
import { getPlainTextFromRTE } from '@brand-page-maker/utils';
|
|
21
|
+
import { getPlainTextFromRTE, getRteTextLength } from '@brand-page-maker/utils';
|
|
21
22
|
import { isDefaultContentText } from '../../utils';
|
|
22
23
|
import { decodeHtmlEntities } from '@brand-page/utils';
|
|
23
24
|
import { ESCAPED_NEWLINE_CHARACTER } from '../../../../constants/ui';
|
|
@@ -91,8 +92,10 @@ export default defineComponent({
|
|
|
91
92
|
created() {
|
|
92
93
|
this.options = cloneDeep(this.value);
|
|
93
94
|
|
|
95
|
+
// BP-5787: testimonials may be Tiptap JSON (migrated legacy content), so
|
|
96
|
+
// read visible text via getPlainTextFromRTE rather than `.trim()` on a doc.
|
|
94
97
|
this.isEmpty = this.options.map(
|
|
95
|
-
(option) => option.testimonials
|
|
98
|
+
(option) => getPlainTextFromRTE(option.testimonials).length === 0,
|
|
96
99
|
);
|
|
97
100
|
|
|
98
101
|
if (!this.changeDebounce) {
|
|
@@ -206,11 +209,17 @@ export default defineComponent({
|
|
|
206
209
|
this.options[index][payload.name] = payload.value;
|
|
207
210
|
this.emitChange();
|
|
208
211
|
},
|
|
209
|
-
getItemTextPreview(text:
|
|
212
|
+
getItemTextPreview(text: RichTextValue): string {
|
|
213
|
+
// BP-5787: a JSON doc has no `.replace` — take its visible text first.
|
|
214
|
+
const value = typeof text === 'string' ? text : getPlainTextFromRTE(text);
|
|
215
|
+
|
|
210
216
|
return decodeHtmlEntities(
|
|
211
|
-
|
|
217
|
+
value.replace(new RegExp(ESCAPED_NEWLINE_CHARACTER, 'g'), ' '),
|
|
212
218
|
);
|
|
213
219
|
},
|
|
220
|
+
rteTextLength(value: RichTextValue): number {
|
|
221
|
+
return getRteTextLength(value);
|
|
222
|
+
},
|
|
214
223
|
getPlainTextValue(index: number): string {
|
|
215
224
|
const text = this.options[index]?.testimonials || '';
|
|
216
225
|
const plainText = getPlainTextFromRTE(text);
|
package/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.vue
CHANGED
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
:error="
|
|
27
27
|
isEmpty[option.index] ||
|
|
28
28
|
(options[option.index].testimonials &&
|
|
29
|
-
options[option.index].testimonials
|
|
29
|
+
rteTextLength(options[option.index].testimonials) >=
|
|
30
|
+
maxTestimonialLength)
|
|
30
31
|
"
|
|
31
32
|
:resize="false"
|
|
32
33
|
class="tw-w-full"
|
|
@@ -42,7 +43,8 @@
|
|
|
42
43
|
<p
|
|
43
44
|
v-else-if="
|
|
44
45
|
options[option.index].testimonials &&
|
|
45
|
-
options[option.index].testimonials
|
|
46
|
+
rteTextLength(options[option.index].testimonials) >=
|
|
47
|
+
maxTestimonialLength
|
|
46
48
|
"
|
|
47
49
|
class="tw-text-error-500 tw-text-sm"
|
|
48
50
|
>
|
|
@@ -127,7 +129,7 @@
|
|
|
127
129
|
v-if="
|
|
128
130
|
options[option.index] &&
|
|
129
131
|
options[option.index].name &&
|
|
130
|
-
options[option.index].name
|
|
132
|
+
rteTextLength(options[option.index].name) >= maxTitleLength
|
|
131
133
|
"
|
|
132
134
|
class="tw-text-error-500 tw-text-sm"
|
|
133
135
|
>
|
|
@@ -158,7 +160,7 @@
|
|
|
158
160
|
v-if="
|
|
159
161
|
options[option.index] &&
|
|
160
162
|
options[option.index].position &&
|
|
161
|
-
options[option.index].position
|
|
163
|
+
rteTextLength(options[option.index].position) >= maxTitleLength
|
|
162
164
|
"
|
|
163
165
|
class="tw-text-error-500 tw-text-sm"
|
|
164
166
|
>
|
|
@@ -97,10 +97,10 @@
|
|
|
97
97
|
:attrs="{
|
|
98
98
|
'data-test-tag': 'image-title-text-input',
|
|
99
99
|
}"
|
|
100
|
-
:error="options[option.index].title
|
|
100
|
+
:error="rteTextLength(options[option.index].title) >= titleLength"
|
|
101
101
|
/>
|
|
102
102
|
<p
|
|
103
|
-
v-if="options[option.index].title
|
|
103
|
+
v-if="rteTextLength(options[option.index].title) >= titleLength"
|
|
104
104
|
class="tw-text-error-500 tw-text-sm tw-mt-2"
|
|
105
105
|
>
|
|
106
106
|
{{ maxLengthError }}
|
|
@@ -134,7 +134,9 @@
|
|
|
134
134
|
:attrs="{
|
|
135
135
|
'data-test-tag': 'image-description-text-input',
|
|
136
136
|
}"
|
|
137
|
-
:error="
|
|
137
|
+
:error="
|
|
138
|
+
rteTextLength(options[option.index].paragraph) >= descriptionLength
|
|
139
|
+
"
|
|
138
140
|
@update:model-value="onTextAreaTextChange($event, option.index)"
|
|
139
141
|
/>
|
|
140
142
|
<PersonalisationOverrideField
|
|
@@ -311,6 +313,7 @@ import debounce from 'lodash.debounce';
|
|
|
311
313
|
import type { UpdateItemValuePayload } from '../../models';
|
|
312
314
|
import cloneDeep from 'lodash.clonedeep';
|
|
313
315
|
import { isURL } from '../../../../utils/validator.util';
|
|
316
|
+
import { getRteTextLength as rteTextLength } from '@brand-page-maker/utils';
|
|
314
317
|
|
|
315
318
|
const DEBOUNCE_IN_MS = 250;
|
|
316
319
|
|