@designcrowd/library.brand-page 6.1.1-json-prereq-3 → 6.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/src/{CreatedOnBrandCrowd-de-de-hj_ievMc.js → CreatedOnBrandCrowd-de-de-B5B5ThJ6.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-en-us-jAOSpfCw.js → CreatedOnBrandCrowd-en-us-a48CaORy.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-es-es-DZMVc-y3.js → CreatedOnBrandCrowd-es-es-xpuZ317-.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-fr-DvB54H38.js → CreatedOnBrandCrowd-fr-ca-V-iUMArW.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-ca-DvB54H38.js → CreatedOnBrandCrowd-fr-fr-V-iUMArW.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-pt-bf-x1xhi.js → CreatedOnBrandCrowd-pt-br-DZ0NKSAy.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-br-bf-x1xhi.js → CreatedOnBrandCrowd-pt-pt-DZ0NKSAy.js} +1 -1
- package/lib/src/{CreatedOnDcom-de-de-B-NY1B44.js → CreatedOnDcom-de-de-_950cgGp.js} +1 -1
- package/lib/src/{CreatedOnDcom-en-us-Cd4C6KQf.js → CreatedOnDcom-en-us-DogKzH-g.js} +1 -1
- package/lib/src/{CreatedOnDcom-es-es-DONlsQsm.js → CreatedOnDcom-es-es-CmGM3IPX.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-ca-Dsv3FRpZ.js → CreatedOnDcom-fr-ca-BonEDkWZ.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-fr-DOgyvY5C.js → CreatedOnDcom-fr-fr-DQHtKrd_.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-br-DX66MhJS.js → CreatedOnDcom-pt-br-CtYJSBjs.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-pt-COe2G0bU.js → CreatedOnDcom-pt-pt-DoO9r4lA.js} +1 -1
- package/lib/src/brand-page/components/contact-form/ContactForm.mixin.js +7 -8
- package/lib/src/brand-page/components/mailing-list/MailingList.mixin.js +6 -4
- package/lib/src/brand-page/components/page-footer/PageFooter.mixin.d.ts +1 -0
- package/lib/src/brand-page/components/page-footer/PageFooter.mixin.js +4 -1
- package/lib/src/brand-page/components/payment/Payment.mixin.js +4 -3
- package/lib/src/brand-page/components/payment/__fixtures__/request-payment.d.ts +1 -0
- package/lib/src/brand-page/components/payment/__fixtures__/support-me.d.ts +1 -0
- package/lib/src/brand-page/components/payment/request-payment/RequestPayment.mixin.d.ts +1 -0
- package/lib/src/brand-page/components/payment/request-payment/RequestPayment.mixin.js +4 -1
- package/lib/src/brand-page/composables/consent.composable.d.ts +2 -0
- package/lib/src/brand-page/composables/consent.composable.js +9 -3
- package/lib/src/brand-page/models/brand-page-context.model.d.ts +7 -0
- package/lib/src/brand-page/models/index.d.ts +26 -34
- package/lib/src/brand-page/utils/context.util.d.ts +8 -0
- package/lib/src/brand-page/utils/context.util.js +8 -0
- package/lib/src/brand-page/utils/migration.util.js +0 -11
- package/lib/src/brand-page/utils/text.util.d.ts +1 -13
- package/lib/src/brand-page/utils/text.util.js +0 -64
- package/lib/src/brand-page/utils/vcard.util.js +4 -4
- package/lib/src/brand-page/utils/view-model.util.d.ts +0 -1
- package/lib/src/brand-page/utils/view-model.util.js +20 -68
- package/lib/src/brand-page/view-models.d.ts +3 -3
- package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +1 -1
- package/lib/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.js +51 -18
- package/lib/src/brand-page-maker/composables/ai-chat/useAiMetadataBuilder.d.ts +1 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/bubbleMenu.d.ts +9 -2
- package/lib/src/brand-page-maker/composables/rich-text-editor/bubbleMenu.js +10 -6
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontSize.d.ts +20 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontSize.js +44 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/LineHeight.d.ts +27 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/LineHeight.js +44 -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 +103 -24
- package/lib/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.js +8 -0
- package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.js +9 -3
- package/lib/src/brand-page-maker/stores/ui/ui-store.d.ts +1 -1
- package/lib/src/brand-page-maker/utils/index.d.ts +0 -2
- package/lib/src/brand-page-maker/utils/index.js +0 -2
- package/lib/src/brand-page-maker/utils/rich-text-editor.util.d.ts +5 -28
- package/lib/src/brand-page-maker/utils/rich-text-editor.util.js +5 -48
- package/lib/src/brand-page-maker/validators/field.validator.js +2 -10
- package/lib/src/clients/models.d.ts +2 -0
- package/lib/src/css/library.brand-page-brandCrowd.min.css +99 -102
- package/lib/src/css/library.brand-page-brandPage.min.css +99 -102
- package/lib/src/css/library.brand-page-designCom.min.css +99 -102
- package/lib/src/css/library.brand-page-preview.min.css +99 -102
- package/lib/src/index.d.ts +1 -1
- package/lib/src/index.mjs +219 -221
- package/lib/src/{lib-scDtk-Pi.js → lib-DtalJKI3.js} +72445 -75920
- package/lib/src/lib.js +1 -2
- package/lib/src/metadata-builder/ai-metadata-executor.d.ts +1 -0
- package/lib/src/metadata-builder/ai-metadata-executor.js +19 -0
- package/lib/src/metadata-builder/builders/section-builders/about-section-builder.js +3 -2
- package/lib/src/metadata-builder/builders/section-builders/banner-section-builder.js +3 -2
- package/lib/src/metadata-builder/builders/section-builders/booking-section-builder.js +3 -2
- package/lib/src/metadata-builder/builders/section-builders/contact-form-builder.js +4 -3
- package/lib/src/metadata-builder/builders/section-builders/images-section-builder.js +3 -2
- package/lib/src/metadata-builder/builders/section-builders/mailing-list-builder.js +2 -1
- package/lib/src/metadata-builder/builders/section-builders/mailing-list-section-builder.js +2 -1
- package/lib/src/metadata-builder/builders/section-builders/profile-section-builder.js +4 -3
- package/lib/src/metadata-builder/builders/section-builders/shapes-section-builder.js +3 -2
- package/lib/src/metadata-builder/builders/section-builders/testimonial-section-builder.js +2 -1
- package/lib/src/metadata-builder/builders/section-builders/text-section-builder.js +11 -15
- package/lib/src/metadata-builder/builders/update-content-text-builder.d.ts +17 -0
- package/lib/src/metadata-builder/builders/update-content-text-builder.js +103 -0
- package/lib/src/metadata-builder/builders/update-content-text-style-builder.d.ts +16 -0
- package/lib/src/metadata-builder/builders/update-content-text-style-builder.js +216 -0
- package/lib/src/metadata-builder/helpers/rte.helpers.d.ts +13 -11
- package/lib/src/metadata-builder/helpers/rte.helpers.js +73 -14
- package/lib/src/metadata-builder/metadata-executor.js +4 -0
- package/lib/src/metadata-builder/models.d.ts +2 -0
- package/lib/src/metadata-builder/models.js +2 -0
- package/lib/src/metadata-builder/parsers/ai-toolcall-parser.d.ts +19 -0
- package/lib/src/metadata-builder/parsers/ai-toolcall-parser.js +4 -0
- package/lib/src/metadata-builder/sanitizers/rte-schema-check.d.ts +8 -7
- package/lib/src/metadata-builder/sanitizers/rte-schema-check.js +52 -21
- package/lib/src/shared/content-settings/components/GenericTextInput.mixin.d.ts +1 -1
- package/lib/src/shared/content-settings/components/GenericTextInput.mixin.js +10 -35
- package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.d.ts +1 -1
- package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.js +3 -12
- package/lib/src/shared/content-settings/components/email-field/EmailField.mixin.d.ts +1 -1
- package/lib/src/shared/content-settings/components/tel-field/TelField.mixin.d.ts +1 -1
- package/lib/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.mixin.d.ts +2 -3
- package/lib/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.mixin.js +3 -10
- package/lib/src/shared/content-settings/components/text-field/TextField.mixin.d.ts +1 -1
- package/lib/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.d.ts +1 -2
- package/lib/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.js +1 -4
- package/lib/src/shared/content-settings/components/textarea-field/TextareaField.mixin.d.ts +1 -1
- package/lib/src/shared/content-settings/components/url-field/UrlField.mixin.d.ts +1 -1
- package/lib/src/shared/content-settings/components/youtube-video-field/YoutubeVideoField.mixin.d.ts +1 -1
- package/lib/src/shared/logo-settings/components/EditLogoSettings.mixin.d.ts +1 -1
- package/lib/src/shared/logo-versions/LogoVersions.mixin.d.ts +1 -1
- package/lib/src/shared/my-designs/MyDesigns.mixin.d.ts +1 -1
- package/lib/src/shared/my-uploads/MyMedia.mixin.d.ts +1 -1
- package/lib/src/shared/my-uploads/MyUploads.mixin.d.ts +1 -1
- package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +1 -1
- package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +1 -1
- package/lib/src/shared/style-settings/components/StyleSettings.mixin.d.ts +1 -1
- package/lib/src/shared/template-settings/components/TemplatesSettings.mixin.d.ts +1 -1
- package/lib/src/shared/template-settings/store/template-settings-store.d.ts +2 -0
- package/lib/src/shared/template-settings/store/template-settings-store.js +72 -24
- package/lib/src/shared/unsplash-library/UnsplashLibrary.mixin.d.ts +1 -1
- package/lib/src/utils/minify-brand-page-metadata.js +1 -4
- package/package.json +15 -17
- package/src/brand-page/components/contact-form/ContactForm.mixin.ts +9 -7
- package/src/brand-page/components/cookie-consent/CookieConsent.vue +1 -0
- package/src/brand-page/components/mailing-list/MailingList.mixin.ts +8 -3
- package/src/brand-page/components/navigation/navigation-carousel/NavigationCarousel.vue +5 -1
- package/src/brand-page/components/page-footer/PageFooter.mixin.ts +4 -1
- package/src/brand-page/components/page-footer/PageFooter.vue +1 -1
- package/src/brand-page/components/payment/Payment.mixin.ts +4 -3
- package/src/brand-page/components/payment/request-payment/RequestPayment.mixin.ts +8 -1
- package/src/brand-page/components/payment/request-payment/RequestPayment.vue +1 -1
- package/src/brand-page/components/save-contact/SaveContactWidget.vue +2 -1
- package/src/brand-page/components/shop/Shop.vue +6 -3
- package/src/brand-page/components/social-links/SocialLinks.vue +5 -2
- package/src/brand-page/components/text-wrapper/TextWrapper.vue +6 -17
- package/src/brand-page/composables/consent.composable.ts +18 -4
- package/src/brand-page/models/brand-page-context.model.ts +7 -0
- package/src/brand-page/models/index.ts +26 -35
- package/src/brand-page/utils/__snapshots__/migration.util.test.ts.snap +3 -3
- package/src/brand-page/utils/context.util.test.ts +43 -0
- package/src/brand-page/utils/context.util.ts +10 -0
- package/src/brand-page/utils/migration.util.ts +0 -12
- package/src/brand-page/utils/style.util.test.ts +0 -51
- package/src/brand-page/utils/text.util.ts +4 -83
- package/src/brand-page/utils/vcard.util.ts +4 -6
- package/src/brand-page/utils/view-model.util.ts +26 -94
- package/src/brand-page/view-models.ts +2 -3
- package/src/brand-page-maker/components/rich-text-editor/RichTextEditor.vue +4 -5
- package/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.test.ts +135 -0
- package/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.ts +62 -21
- package/src/brand-page-maker/composables/ai-chat/useAiMetadataBuilder.ts +1 -0
- package/src/brand-page-maker/composables/rich-text-editor/bubbleMenu.ts +10 -6
- package/src/brand-page-maker/composables/rich-text-editor/extensions/FontSize.ts +73 -0
- package/src/brand-page-maker/composables/rich-text-editor/extensions/LineHeight.ts +85 -0
- package/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.ts +111 -27
- package/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.ts +9 -0
- package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.ts +11 -3
- package/src/brand-page-maker/stores/ui/ui-store.ts +1 -1
- package/src/brand-page-maker/utils/index.ts +0 -2
- package/src/brand-page-maker/utils/rich-text-editor.util.test.ts +3 -37
- package/src/brand-page-maker/utils/rich-text-editor.util.ts +11 -66
- package/src/brand-page-maker/validators/field.validator.ts +2 -10
- package/src/clients/models.ts +3 -0
- package/src/lib.ts +1 -9
- package/src/metadata-builder/ai-metadata-executor.ts +26 -0
- package/src/metadata-builder/builders/section-builders/about-section-builder.ts +3 -2
- package/src/metadata-builder/builders/section-builders/banner-section-builder.ts +3 -2
- package/src/metadata-builder/builders/section-builders/booking-section-builder.ts +5 -2
- package/src/metadata-builder/builders/section-builders/contact-form-builder.ts +4 -3
- package/src/metadata-builder/builders/section-builders/images-section-builder.ts +3 -2
- package/src/metadata-builder/builders/section-builders/mailing-list-builder.ts +2 -1
- package/src/metadata-builder/builders/section-builders/mailing-list-section-builder.ts +2 -1
- package/src/metadata-builder/builders/section-builders/profile-section-builder.ts +6 -3
- package/src/metadata-builder/builders/section-builders/shapes-section-builder.ts +3 -2
- package/src/metadata-builder/builders/section-builders/testimonial-section-builder.ts +2 -1
- package/src/metadata-builder/builders/section-builders/text-section-builder.ts +13 -19
- package/src/metadata-builder/builders/update-content-text-builder.ts +148 -0
- package/src/metadata-builder/builders/update-content-text-style-builder.ts +352 -0
- package/src/metadata-builder/helpers/rte.helpers.ts +101 -15
- package/src/metadata-builder/metadata-executor.ts +5 -0
- package/src/metadata-builder/models.ts +3 -0
- package/src/metadata-builder/parsers/ai-toolcall-parser.ts +27 -0
- package/src/metadata-builder/sanitizers/rte-schema-check.ts +53 -24
- package/src/shared/content-settings/components/GenericTextInput.mixin.ts +11 -42
- package/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.ts +3 -16
- package/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.mixin.ts +4 -13
- package/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.vue +4 -6
- package/src/shared/content-settings/components/text-image-field/TextImageField.vue +3 -6
- package/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.ts +1 -6
- package/src/shared/content-settings/components/text-items-field/TextItemsField.vue +1 -1
- package/src/shared/template-settings/store/template-settings-store.ts +93 -27
- package/src/utils/minify-brand-page-metadata.ts +1 -4
- package/lib/src/brand-page/utils/migration/migration-20.d.ts +0 -2
- package/lib/src/brand-page/utils/migration/migration-20.js +0 -66
- package/lib/src/brand-page-maker/__fixtures__/complex-rte.fixture.d.ts +0 -22
- package/lib/src/brand-page-maker/__fixtures__/complex-rte.fixture.js +0 -22
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.d.ts +0 -14
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.js +0 -39
- package/lib/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.d.ts +0 -28
- package/lib/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.js +0 -117
- package/lib/src/brand-page-maker/utils/rte-render.util.d.ts +0 -63
- package/lib/src/brand-page-maker/utils/rte-render.util.js +0 -95
- package/lib/src/brand-page-maker/utils/rte-sanitize.util.d.ts +0 -6
- package/lib/src/brand-page-maker/utils/rte-sanitize.util.js +0 -156
- package/src/brand-page/utils/migration/migration-20.server.test.ts +0 -125
- package/src/brand-page/utils/migration/migration-20.test.ts +0 -72
- package/src/brand-page/utils/migration/migration-20.ts +0 -87
- package/src/brand-page/utils/text.util.test.ts +0 -106
- package/src/brand-page/utils/view-model.pageid-rte.test.ts +0 -104
- package/src/brand-page-maker/__fixtures__/complex-rte.fixture.ts +0 -23
- package/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.test.ts +0 -52
- package/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.ts +0 -42
- package/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.ts +0 -162
- package/src/brand-page-maker/utils/__snapshots__/rte-render-parity.test.ts.snap +0 -202
- package/src/brand-page-maker/utils/rte-features.test.ts +0 -378
- package/src/brand-page-maker/utils/rte-render-parity.test.ts +0 -164
- package/src/brand-page-maker/utils/rte-render.util.ts +0 -165
- package/src/brand-page-maker/utils/rte-sanitize-json.test.ts +0 -172
- package/src/brand-page-maker/utils/rte-sanitize.util.ts +0 -192
- package/src/brand-page-maker/validators/field.validator.test.ts +0 -55
|
@@ -6,7 +6,7 @@ import { TextInput, Textarea } from '@designcrowd/fe-shared-lib';
|
|
|
6
6
|
import type { ContentItemFieldSettings } from '../models';
|
|
7
7
|
import {
|
|
8
8
|
encodeContentText,
|
|
9
|
-
|
|
9
|
+
escapeContentText,
|
|
10
10
|
unescapeContentText,
|
|
11
11
|
} from '@brand-page/utils';
|
|
12
12
|
import { genericTextInputTr } from '@src/composables/useTranslate';
|
|
@@ -107,6 +107,7 @@ export default defineComponent({
|
|
|
107
107
|
_oldValue: '' as string,
|
|
108
108
|
isDirty: false,
|
|
109
109
|
emitChangeDebounce: undefined as DebounceHandle | undefined,
|
|
110
|
+
lengthError: false,
|
|
110
111
|
// Whether every write in the current debounce burst was an RTE
|
|
111
112
|
// hydration write-back (see UpdateItemValuePayload.isHydrationWrite).
|
|
112
113
|
// undefined = no burst pending; any user write downgrades it to false.
|
|
@@ -129,30 +130,6 @@ export default defineComponent({
|
|
|
129
130
|
return this.settings?.isValid;
|
|
130
131
|
},
|
|
131
132
|
|
|
132
|
-
// Derived from the current value + maxLength (not hand-synced in a watcher):
|
|
133
|
-
// stays correct when maxLength or showRichTextEditor change and on first
|
|
134
|
-
// render, and flips the instant `_value` updates rather than after the emit
|
|
135
|
-
// round-trips back through the `value` prop. `>=` (not `>`) because the RTE
|
|
136
|
-
// MaxLength extension hard-gates the length, so "reached the cap" is the
|
|
137
|
-
// signal to surface. Length is measured with getContentTextLength so it
|
|
138
|
-
// matches the editor's own char count (ProseMirror textContent) — NOT
|
|
139
|
-
// escapeContentText, whose space-join over-counts by one per mark boundary
|
|
140
|
-
// (e.g. hyperlinking a word), reporting length+1 at the cap (BP-5787).
|
|
141
|
-
lengthError(): boolean {
|
|
142
|
-
const value = this._value;
|
|
143
|
-
const maxLength = this.maxLength;
|
|
144
|
-
|
|
145
|
-
if (maxLength == null || value === false || value == null) {
|
|
146
|
-
return false;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const length = this.showRichTextEditor
|
|
150
|
-
? getContentTextLength(value)
|
|
151
|
-
: value.length;
|
|
152
|
-
|
|
153
|
-
return length >= maxLength;
|
|
154
|
-
},
|
|
155
|
-
|
|
156
133
|
label(): string {
|
|
157
134
|
return this.name.replace(/./, (c: string) => c.toUpperCase());
|
|
158
135
|
},
|
|
@@ -198,24 +175,16 @@ export default defineComponent({
|
|
|
198
175
|
},
|
|
199
176
|
},
|
|
200
177
|
watch: {
|
|
201
|
-
value(newVal: string | undefined
|
|
202
|
-
// BP-5787: RTE values are Tiptap JSON objects. The parent (TextItemsField)
|
|
203
|
-
// re-clones its items array (cloneDeep) on ANY change — including a sibling
|
|
204
|
-
// field's style sync — yielding a NEW object reference with identical text
|
|
205
|
-
// content. For objects Vue fires this watcher on reference change, so that
|
|
206
|
-
// spurious re-clone would overwrite `_value` and clobber an in-flight edit
|
|
207
|
-
// (a mark applied but not yet debounced to the store), silently dropping
|
|
208
|
-
// the user's formatting. Strings never hit this (same content = same
|
|
209
|
-
// reference = no fire), so mirror that: ignore re-clones whose content is
|
|
210
|
-
// unchanged from the previous prop value.
|
|
211
|
-
if (
|
|
212
|
-
this.showRichTextEditor &&
|
|
213
|
-
JSON.stringify(newVal) === JSON.stringify(oldVal)
|
|
214
|
-
) {
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
|
|
178
|
+
value(newVal: string | undefined) {
|
|
218
179
|
this._value = newVal;
|
|
180
|
+
|
|
181
|
+
if (this.showRichTextEditor) {
|
|
182
|
+
const textValue = escapeContentText(this._value);
|
|
183
|
+
|
|
184
|
+
this.lengthError = textValue && textValue.length >= this.maxLength;
|
|
185
|
+
} else {
|
|
186
|
+
this.lengthError = this._value.length >= this.maxLength;
|
|
187
|
+
}
|
|
219
188
|
},
|
|
220
189
|
|
|
221
190
|
isToggled(newVal: boolean) {
|
|
@@ -21,9 +21,6 @@ import {
|
|
|
21
21
|
replaceTextInRTE,
|
|
22
22
|
RTE_ALLOWED_SECTION_TYPES,
|
|
23
23
|
isRTEManagedStyleField,
|
|
24
|
-
isTiptapDocument,
|
|
25
|
-
renderRteToHtml,
|
|
26
|
-
toRteJson,
|
|
27
24
|
} from '@brand-page-maker/utils';
|
|
28
25
|
import { isDefaultContentText } from '@shared/content-settings/utils';
|
|
29
26
|
import { useUiLogoSettingsStore } from '../../../logo-settings/store';
|
|
@@ -330,20 +327,10 @@ export default defineComponent({
|
|
|
330
327
|
this.isToggled = isToggled;
|
|
331
328
|
},
|
|
332
329
|
onUseGeneratedText(generatedText: string) {
|
|
333
|
-
let formattedText
|
|
330
|
+
let formattedText = generatedText;
|
|
334
331
|
|
|
335
|
-
|
|
336
|
-
|
|
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
|
-
}
|
|
332
|
+
if (this.showRichTextEditor && typeof this.value === 'string') {
|
|
333
|
+
formattedText = replaceTextInRTE(this.value, generatedText);
|
|
347
334
|
}
|
|
348
335
|
|
|
349
336
|
this.contentSettingsStore.updateItemValue({
|
|
@@ -4,7 +4,6 @@ import type {
|
|
|
4
4
|
ColorReplacement,
|
|
5
5
|
Content,
|
|
6
6
|
LogoTheme,
|
|
7
|
-
RichTextValue,
|
|
8
7
|
TestimonialItems,
|
|
9
8
|
} from '@brand-page';
|
|
10
9
|
import {
|
|
@@ -18,7 +17,7 @@ import debounce from 'lodash.debounce';
|
|
|
18
17
|
import type { UpdateItemValuePayload } from '../../models';
|
|
19
18
|
import cloneDeep from 'lodash.clonedeep';
|
|
20
19
|
import { useSnapshotStore } from '@brand-page-maker/stores';
|
|
21
|
-
import { getPlainTextFromRTE
|
|
20
|
+
import { getPlainTextFromRTE } from '@brand-page-maker/utils';
|
|
22
21
|
import { isDefaultContentText } from '../../utils';
|
|
23
22
|
import { decodeHtmlEntities } from '@brand-page/utils';
|
|
24
23
|
import { ESCAPED_NEWLINE_CHARACTER } from '../../../../constants/ui';
|
|
@@ -92,10 +91,8 @@ export default defineComponent({
|
|
|
92
91
|
created() {
|
|
93
92
|
this.options = cloneDeep(this.value);
|
|
94
93
|
|
|
95
|
-
// BP-5787: testimonials may be Tiptap JSON (migrated legacy content), so
|
|
96
|
-
// read visible text via getPlainTextFromRTE rather than `.trim()` on a doc.
|
|
97
94
|
this.isEmpty = this.options.map(
|
|
98
|
-
(option) =>
|
|
95
|
+
(option) => option.testimonials.trim().length === 0,
|
|
99
96
|
);
|
|
100
97
|
|
|
101
98
|
if (!this.changeDebounce) {
|
|
@@ -209,17 +206,11 @@ export default defineComponent({
|
|
|
209
206
|
this.options[index][payload.name] = payload.value;
|
|
210
207
|
this.emitChange();
|
|
211
208
|
},
|
|
212
|
-
getItemTextPreview(text:
|
|
213
|
-
// BP-5787: a JSON doc has no `.replace` — take its visible text first.
|
|
214
|
-
const value = typeof text === 'string' ? text : getPlainTextFromRTE(text);
|
|
215
|
-
|
|
209
|
+
getItemTextPreview(text: string): string {
|
|
216
210
|
return decodeHtmlEntities(
|
|
217
|
-
|
|
211
|
+
text.replace(new RegExp(ESCAPED_NEWLINE_CHARACTER, 'g'), ' '),
|
|
218
212
|
);
|
|
219
213
|
},
|
|
220
|
-
rteTextLength(value: RichTextValue): number {
|
|
221
|
-
return getRteTextLength(value);
|
|
222
|
-
},
|
|
223
214
|
getPlainTextValue(index: number): string {
|
|
224
215
|
const text = this.options[index]?.testimonials || '';
|
|
225
216
|
const plainText = getPlainTextFromRTE(text);
|
package/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.vue
CHANGED
|
@@ -26,8 +26,7 @@
|
|
|
26
26
|
:error="
|
|
27
27
|
isEmpty[option.index] ||
|
|
28
28
|
(options[option.index].testimonials &&
|
|
29
|
-
|
|
30
|
-
maxTestimonialLength)
|
|
29
|
+
options[option.index].testimonials.length >= maxTestimonialLength)
|
|
31
30
|
"
|
|
32
31
|
:resize="false"
|
|
33
32
|
class="tw-w-full"
|
|
@@ -43,8 +42,7 @@
|
|
|
43
42
|
<p
|
|
44
43
|
v-else-if="
|
|
45
44
|
options[option.index].testimonials &&
|
|
46
|
-
|
|
47
|
-
maxTestimonialLength
|
|
45
|
+
options[option.index].testimonials.length >= maxTestimonialLength
|
|
48
46
|
"
|
|
49
47
|
class="tw-text-error-500 tw-text-sm"
|
|
50
48
|
>
|
|
@@ -129,7 +127,7 @@
|
|
|
129
127
|
v-if="
|
|
130
128
|
options[option.index] &&
|
|
131
129
|
options[option.index].name &&
|
|
132
|
-
|
|
130
|
+
options[option.index].name.length >= maxTitleLength
|
|
133
131
|
"
|
|
134
132
|
class="tw-text-error-500 tw-text-sm"
|
|
135
133
|
>
|
|
@@ -160,7 +158,7 @@
|
|
|
160
158
|
v-if="
|
|
161
159
|
options[option.index] &&
|
|
162
160
|
options[option.index].position &&
|
|
163
|
-
|
|
161
|
+
options[option.index].position.length >= maxTitleLength
|
|
164
162
|
"
|
|
165
163
|
class="tw-text-error-500 tw-text-sm"
|
|
166
164
|
>
|
|
@@ -97,10 +97,10 @@
|
|
|
97
97
|
:attrs="{
|
|
98
98
|
'data-test-tag': 'image-title-text-input',
|
|
99
99
|
}"
|
|
100
|
-
:error="
|
|
100
|
+
:error="options[option.index].title.length >= titleLength"
|
|
101
101
|
/>
|
|
102
102
|
<p
|
|
103
|
-
v-if="
|
|
103
|
+
v-if="options[option.index].title.length >= titleLength"
|
|
104
104
|
class="tw-text-error-500 tw-text-sm tw-mt-2"
|
|
105
105
|
>
|
|
106
106
|
{{ maxLengthError }}
|
|
@@ -134,9 +134,7 @@
|
|
|
134
134
|
:attrs="{
|
|
135
135
|
'data-test-tag': 'image-description-text-input',
|
|
136
136
|
}"
|
|
137
|
-
:error="
|
|
138
|
-
rteTextLength(options[option.index].paragraph) >= descriptionLength
|
|
139
|
-
"
|
|
137
|
+
:error="options[option.index].paragraph.length >= descriptionLength"
|
|
140
138
|
@update:model-value="onTextAreaTextChange($event, option.index)"
|
|
141
139
|
/>
|
|
142
140
|
<PersonalisationOverrideField
|
|
@@ -313,7 +311,6 @@ import debounce from 'lodash.debounce';
|
|
|
313
311
|
import type { UpdateItemValuePayload } from '../../models';
|
|
314
312
|
import cloneDeep from 'lodash.clonedeep';
|
|
315
313
|
import { isURL } from '../../../../utils/validator.util';
|
|
316
|
-
import { getRteTextLength as rteTextLength } from '@brand-page-maker/utils';
|
|
317
314
|
|
|
318
315
|
const DEBOUNCE_IN_MS = 250;
|
|
319
316
|
|
|
@@ -3,7 +3,6 @@ import type { PropType } from 'vue';
|
|
|
3
3
|
import type {
|
|
4
4
|
Content,
|
|
5
5
|
LogoTheme,
|
|
6
|
-
RichTextValue,
|
|
7
6
|
TextItem,
|
|
8
7
|
TextStyleDeclaration,
|
|
9
8
|
} from '@brand-page/models';
|
|
@@ -20,7 +19,7 @@ import type { UpdateItemValuePayload } from '../../models';
|
|
|
20
19
|
import cloneDeep from 'lodash.clonedeep';
|
|
21
20
|
import { decodeHtmlEntities, escapeContentText } from '@brand-page/utils';
|
|
22
21
|
import { useSnapshotStore } from '@brand-page-maker/stores';
|
|
23
|
-
import { getPlainTextFromRTE
|
|
22
|
+
import { getPlainTextFromRTE } from '@brand-page-maker/utils';
|
|
24
23
|
import { isDefaultContentText } from '../../utils';
|
|
25
24
|
|
|
26
25
|
const DEBOUNCE_IN_MS = 250;
|
|
@@ -235,10 +234,6 @@ export default defineComponent({
|
|
|
235
234
|
this.emitChange();
|
|
236
235
|
},
|
|
237
236
|
|
|
238
|
-
rteTextLength(value: RichTextValue): number {
|
|
239
|
-
return getRteTextLength(value);
|
|
240
|
-
},
|
|
241
|
-
|
|
242
237
|
getPlainTextValue(index: number): string {
|
|
243
238
|
const text = this.options[index]?.text || '';
|
|
244
239
|
const plainText = getPlainTextFromRTE(text);
|
|
@@ -26,7 +26,10 @@ import {
|
|
|
26
26
|
useUiStore,
|
|
27
27
|
} from '@brand-page-maker/stores';
|
|
28
28
|
import { useUiSideBarStore } from '../../side-bar/store';
|
|
29
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
BrandPageModel,
|
|
31
|
+
type UserPersonalisationContent,
|
|
32
|
+
} from '@brand-page/models';
|
|
30
33
|
import { migrateBrandPageModel } from '@brand-page/utils';
|
|
31
34
|
import { getAiContentIndexByTemplateTokenAndNumberOfVariations } from '@brand-page-maker/utils/ai-content-selection.util';
|
|
32
35
|
import {
|
|
@@ -50,6 +53,7 @@ const POST_PURCHASE_PAGE_SECTION_BY_TAB: Record<string, string> = {
|
|
|
50
53
|
|
|
51
54
|
const PRE_PURCHASE_PAGE_SECTION_BY_TAB: Record<string, string> = {
|
|
52
55
|
[TEMPLATE_TABS.all]: 'MakerPrePurchaseSidebarTemplatesAllTab',
|
|
56
|
+
[TEMPLATE_TABS.AI]: 'MakerPrePurchaseSidebarTemplatesAiTab',
|
|
53
57
|
[TEMPLATE_TABS.categories]: 'MakerPrePurchaseSidebarTemplatesCategoryTab',
|
|
54
58
|
};
|
|
55
59
|
const LOAD_PAGE_RETRY_DELAY = 250;
|
|
@@ -76,6 +80,7 @@ export interface TemplatesSettingState {
|
|
|
76
80
|
selectedTab: SelectedTabType;
|
|
77
81
|
selectedCategory: string | undefined;
|
|
78
82
|
searchTerm: string | undefined;
|
|
83
|
+
searchPersonalisationData: UserPersonalisationContent[] | undefined;
|
|
79
84
|
tabsWithTranslations: SelectedTabType[];
|
|
80
85
|
logoChangeWatcherInitialized: boolean;
|
|
81
86
|
initialSearchTerm: string | undefined;
|
|
@@ -98,6 +103,7 @@ export const useUiTemplateSettingsStore = defineStore('uiTemplateSettings', {
|
|
|
98
103
|
displayName: templateSettingsTr(`headerAll`),
|
|
99
104
|
},
|
|
100
105
|
searchTerm: undefined,
|
|
106
|
+
searchPersonalisationData: undefined,
|
|
101
107
|
selectedCategory: undefined,
|
|
102
108
|
templateCategories: [],
|
|
103
109
|
tabsWithTranslations: [
|
|
@@ -321,17 +327,25 @@ export const useUiTemplateSettingsStore = defineStore('uiTemplateSettings', {
|
|
|
321
327
|
const isBrandSite =
|
|
322
328
|
this.templateType === BP_BRAND_SITE_TEMPLATE_TYPE_NAME;
|
|
323
329
|
|
|
324
|
-
//
|
|
325
|
-
//
|
|
326
|
-
//
|
|
327
|
-
|
|
330
|
+
// With no user search, the All tab must still show industry-relevant templates rather than
|
|
331
|
+
// generic ones (searchIndustries alone doesn't rank the non-AI half). Seed searchTerm as an
|
|
332
|
+
// invisible default: pre-purchase has no logo to read an industry from, so it uses the seeded
|
|
333
|
+
// keyword from the URL/cookie (bp-5454); post-purchase falls back to the logo draft's industry
|
|
334
|
+
// (bp-5789). Once the user searches, this.searchTerm takes over. this.searchTerm stays unset
|
|
335
|
+
// here, so AI content-key resolution below is unaffected.
|
|
336
|
+
const useSeededSearchTerm =
|
|
328
337
|
!this.searchTerm &&
|
|
329
338
|
!this.selectedCategory &&
|
|
330
|
-
isPrePurchase &&
|
|
331
339
|
isBrandSite &&
|
|
332
|
-
this.selectedTab.key === TEMPLATE_TABS.all
|
|
333
|
-
|
|
334
|
-
|
|
340
|
+
this.selectedTab.key === TEMPLATE_TABS.all;
|
|
341
|
+
|
|
342
|
+
const seededSearchTerm = isPrePurchase
|
|
343
|
+
? this.initialSearchTerm
|
|
344
|
+
: brandPageMakerStore.industry;
|
|
345
|
+
|
|
346
|
+
const effectiveSearchTerm = useSeededSearchTerm
|
|
347
|
+
? seededSearchTerm
|
|
348
|
+
: this.searchTerm;
|
|
335
349
|
|
|
336
350
|
const pageSection = isBrandSite
|
|
337
351
|
? (isPrePurchase
|
|
@@ -397,6 +411,23 @@ export const useUiTemplateSettingsStore = defineStore('uiTemplateSettings', {
|
|
|
397
411
|
}
|
|
398
412
|
}
|
|
399
413
|
|
|
414
|
+
// searchPersonalisation is a per-search payload established on page 1; only set it there
|
|
415
|
+
// (like abTestValuesToBePushed above) so paginating to page 2+ can't reset it to undefined.
|
|
416
|
+
// Every context change (search/clear/category/logo swap) reloads from page 1, so this still
|
|
417
|
+
// clears stale search content on a new search.
|
|
418
|
+
//
|
|
419
|
+
// Set BEFORE the mapping loop so tiles and the apply path read the same value — reading the
|
|
420
|
+
// per-page `result.searchPersonalisation` here made page-2 tiles fall back to page-load
|
|
421
|
+
// content while applying used page-1 search content.
|
|
422
|
+
//
|
|
423
|
+
// Empty [] is stored as undefined: it wins `??` but fails `?.length`, so it would strand
|
|
424
|
+
// the downstream fallback to uiStore.personalisationData and silently skip the apply.
|
|
425
|
+
if (pageNo === 1) {
|
|
426
|
+
this.searchPersonalisationData = result.searchPersonalisation?.length
|
|
427
|
+
? result.searchPersonalisation
|
|
428
|
+
: undefined;
|
|
429
|
+
}
|
|
430
|
+
|
|
400
431
|
const templates = await Promise.all(
|
|
401
432
|
result.paginatedItems.map(async (item) => {
|
|
402
433
|
try {
|
|
@@ -411,17 +442,20 @@ export const useUiTemplateSettingsStore = defineStore('uiTemplateSettings', {
|
|
|
411
442
|
let personalisationContentIndex: number | undefined;
|
|
412
443
|
let contentKey: string | undefined;
|
|
413
444
|
|
|
414
|
-
|
|
445
|
+
const activePersonalisation =
|
|
446
|
+
this.searchPersonalisationData ?? uiStore.personalisationData;
|
|
447
|
+
|
|
448
|
+
if (activePersonalisation?.length) {
|
|
415
449
|
personalisationContentIndex =
|
|
416
450
|
await getAiContentIndexByTemplateTokenAndNumberOfVariations(
|
|
417
451
|
item.designTemplateToken,
|
|
418
|
-
|
|
452
|
+
activePersonalisation.length,
|
|
419
453
|
);
|
|
420
454
|
|
|
421
455
|
contentKey =
|
|
422
|
-
|
|
456
|
+
activePersonalisation[personalisationContentIndex]
|
|
423
457
|
.contentKey;
|
|
424
|
-
} else if (item.isPersonalisable) {
|
|
458
|
+
} else if (item.isPersonalisable && !this.searchTerm) {
|
|
425
459
|
// Pre-purchase has no personalisationData; personalise AI
|
|
426
460
|
// template previews with the prompt content key (bp-5742).
|
|
427
461
|
contentKey = uiStore.userPromptContentKey;
|
|
@@ -431,10 +465,9 @@ export const useUiTemplateSettingsStore = defineStore('uiTemplateSettings', {
|
|
|
431
465
|
id: item.designTemplateToken,
|
|
432
466
|
isPersonalisable: item.isPersonalisable,
|
|
433
467
|
contentKey,
|
|
434
|
-
//
|
|
435
|
-
//
|
|
436
|
-
//
|
|
437
|
-
// holds placeholders post-purchase (bp-5454).
|
|
468
|
+
// userPromptContentKey signals the content lives in the userPrompt S3
|
|
469
|
+
// bucket (post- or pre-purchase); the preview must resolve via
|
|
470
|
+
// userPromptContentKey, not the regular bucket (bp-5454).
|
|
438
471
|
usesUserPromptContentKey: !!uiStore.userPromptContentKey,
|
|
439
472
|
previewImageUrl: item.previewImageUrl,
|
|
440
473
|
} as TemplateSettingsTemplate;
|
|
@@ -450,13 +483,25 @@ export const useUiTemplateSettingsStore = defineStore('uiTemplateSettings', {
|
|
|
450
483
|
(t): t is TemplateSettingsTemplate => t !== null,
|
|
451
484
|
);
|
|
452
485
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
//
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
486
|
+
let displayTemplates = validTemplates;
|
|
487
|
+
|
|
488
|
+
// The All tab shows backend-interleaved AI templates (gated server-side on the AB
|
|
489
|
+
// variation) even when the dedicated AI tab is hidden — e.g. regular templates
|
|
490
|
+
// pre-purchase (bp-5789). So only strip AI when the AI tab is hidden AND we're not on
|
|
491
|
+
// the All tab; the backend is the interleave gate, control buckets return no AI here.
|
|
492
|
+
if (
|
|
493
|
+
!uiStore.showAiTemplatesTab &&
|
|
494
|
+
this.selectedTab.key !== TEMPLATE_TABS.all
|
|
495
|
+
) {
|
|
496
|
+
displayTemplates = displayTemplates.filter(
|
|
497
|
+
(t) => !t.isPersonalisable,
|
|
498
|
+
);
|
|
499
|
+
} else if (this.selectedTab.key === TEMPLATE_TABS.categories) {
|
|
500
|
+
displayTemplates = displayTemplates.map((t) => ({
|
|
501
|
+
...t,
|
|
502
|
+
isPersonalisable: false,
|
|
503
|
+
}));
|
|
504
|
+
}
|
|
460
505
|
|
|
461
506
|
this._addTemplates({ templates: displayTemplates });
|
|
462
507
|
this._setPageNoLoaded({ pageNoLoaded: pageNo });
|
|
@@ -529,6 +574,11 @@ export const useUiTemplateSettingsStore = defineStore('uiTemplateSettings', {
|
|
|
529
574
|
searchTerm: payload.searchTerm,
|
|
530
575
|
});
|
|
531
576
|
|
|
577
|
+
// Drive AI industry selection from the typed term; fall back to the
|
|
578
|
+
// seeded keyword on clear so the AI tab doesn't go generic (bp-5789).
|
|
579
|
+
this.aiTemplateKeywords =
|
|
580
|
+
payload.searchTerm || this.initialSearchTerm || '';
|
|
581
|
+
|
|
532
582
|
this.onSearchTermChanged?.(payload.searchTerm);
|
|
533
583
|
|
|
534
584
|
await this._setPageNo({ pageNo: 1 });
|
|
@@ -617,7 +667,8 @@ export const useUiTemplateSettingsStore = defineStore('uiTemplateSettings', {
|
|
|
617
667
|
return;
|
|
618
668
|
}
|
|
619
669
|
|
|
620
|
-
//
|
|
670
|
+
// Refetch previews on any logo change (pre- and post-purchase) so the new logo
|
|
671
|
+
// shows in the thumbnails.
|
|
621
672
|
const hasLogoChanged =
|
|
622
673
|
newLogoImage.logoDraftToken !== oldLogoImage.logoDraftToken ||
|
|
623
674
|
newLogoImage.logoVariation !== oldLogoImage.logoVariation;
|
|
@@ -627,8 +678,23 @@ export const useUiTemplateSettingsStore = defineStore('uiTemplateSettings', {
|
|
|
627
678
|
newLogoImage.logoDraftToken &&
|
|
628
679
|
hasLogoChanged
|
|
629
680
|
) {
|
|
630
|
-
|
|
631
|
-
|
|
681
|
+
const uiStore = useUiStore();
|
|
682
|
+
const isPrePurchase = useBrandPageContainerStore().isPrePurchase;
|
|
683
|
+
|
|
684
|
+
uiStore.setUserPersonalisationData({
|
|
685
|
+
personalisationData: undefined,
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
if (
|
|
689
|
+
!isPrePurchase &&
|
|
690
|
+
newLogoImage.logoDraftToken !== oldLogoImage.logoDraftToken
|
|
691
|
+
) {
|
|
692
|
+
uiStore.setUserPromptContentKey({
|
|
693
|
+
userPromptContentKey: undefined,
|
|
694
|
+
});
|
|
695
|
+
uiStore.setShowAiTemplatesTab({ showAiTemplatesTab: false });
|
|
696
|
+
}
|
|
697
|
+
|
|
632
698
|
await this._setTemplates({ templates: [] });
|
|
633
699
|
await this._setPageNoLoaded({ pageNoLoaded: 0 });
|
|
634
700
|
await this._setPageNo({ pageNo: 1 });
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from '../brand-page/models';
|
|
11
11
|
import type { Content, ContentType } from '../brand-page/models/content.model';
|
|
12
12
|
import type { TextStyleDeclaration } from '../brand-page/models/styles.model';
|
|
13
|
-
import { hasRTEContent
|
|
13
|
+
import { hasRTEContent } from '../brand-page-maker/utils';
|
|
14
14
|
import { useExternalRichTextEditor } from '../brand-page-maker/composables/rich-text-editor/useRichTextEditor';
|
|
15
15
|
import type { Editor, JSONContent } from '@tiptap/vue-3';
|
|
16
16
|
import {
|
|
@@ -106,9 +106,6 @@ const convertText = (
|
|
|
106
106
|
value: unknown,
|
|
107
107
|
editor: Editor,
|
|
108
108
|
): RichTextValue | undefined => {
|
|
109
|
-
// Already Tiptap JSON (BP-5787 storage) — keep as-is.
|
|
110
|
-
if (isTiptapDocument(value)) return value;
|
|
111
|
-
|
|
112
109
|
if (typeof value !== 'string' || !value) return undefined;
|
|
113
110
|
|
|
114
111
|
if (hasRTEContent(value)) {
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { hasRTEContent, toRteJson, resolveHtmlParser, } from '../../../brand-page-maker/utils';
|
|
2
|
-
/**
|
|
3
|
-
* BP-5787 — migrate existing inline-HTML RTE content to Tiptap JSON.
|
|
4
|
-
*
|
|
5
|
-
* The editor emits, and the renderer reads, Tiptap JSON. Existing
|
|
6
|
-
* published/saved sites still hold RTE fields as HTML strings. This converts
|
|
7
|
-
* them on load, in EVERY environment:
|
|
8
|
-
* - maker (browser): the loaded model is JSON, so the next save persists JSON.
|
|
9
|
-
* - web-renderer (SSR / server API routes): converted on read, so old sites
|
|
10
|
-
* are normalised without a re-save and downstream code can treat RTE as JSON.
|
|
11
|
-
*
|
|
12
|
-
* This is a one-time, versioned migration (runAlways is NOT set): legacy sites
|
|
13
|
-
* are at model version < 20 and convert on first load in any environment; once
|
|
14
|
-
* `v` is stamped to 20 the conversion is skipped. This is only safe because the
|
|
15
|
-
* AI section builders now emit Tiptap JSON directly (they no longer write HTML),
|
|
16
|
-
* so there is no recurring post-load source of HTML to normalise.
|
|
17
|
-
*
|
|
18
|
-
* HTML→JSON parsing needs a DOM parser. Rather than mutate a global `window`,
|
|
19
|
-
* the parser is resolved once (native `DOMParser` in the browser, a jsdom
|
|
20
|
-
* `DOMParser` instance on the server) and threaded explicitly through the
|
|
21
|
-
* conversion — see resolveHtmlParser / toRteJson. No global state is touched, so
|
|
22
|
-
* the pass is safe on any server runtime and under concurrent requests.
|
|
23
|
-
*
|
|
24
|
-
* Scope: only HTML-RTE strings (hasRTEContent) are converted. Plain strings,
|
|
25
|
-
* numbers, and already-JSON docs are left untouched, so the migration is
|
|
26
|
-
* idempotent (re-running on JSON content is a no-op — non-string values are
|
|
27
|
-
* skipped). Nested RTE inside item arrays (textItems, testimonials, images…)
|
|
28
|
-
* is handled by recursing into arrays; style objects are intentionally not
|
|
29
|
-
* descended into (they never hold RTE content).
|
|
30
|
-
*/
|
|
31
|
-
const convertRteStringsToJson = (value, parser) => {
|
|
32
|
-
if (Array.isArray(value)) {
|
|
33
|
-
value.forEach((item) => convertRteStringsToJson(item, parser));
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
if (!value || typeof value !== 'object')
|
|
37
|
-
return;
|
|
38
|
-
const record = value;
|
|
39
|
-
Object.keys(record).forEach((key) => {
|
|
40
|
-
const field = record[key];
|
|
41
|
-
if (typeof field === 'string' && hasRTEContent(field)) {
|
|
42
|
-
const json = toRteJson(field, parser);
|
|
43
|
-
if (json)
|
|
44
|
-
record[key] = json;
|
|
45
|
-
}
|
|
46
|
-
else if (Array.isArray(field)) {
|
|
47
|
-
convertRteStringsToJson(field, parser);
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
};
|
|
51
|
-
const convertModel = (brandPageModel, parser) => {
|
|
52
|
-
for (const page of brandPageModel.pages) {
|
|
53
|
-
for (const content of page.contents) {
|
|
54
|
-
convertRteStringsToJson(content, parser);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
export const migration20 = async (migrationResult) => {
|
|
59
|
-
const { brandPageModel } = migrationResult;
|
|
60
|
-
// Resolve a DOM parser for this environment (native in the browser, a jsdom
|
|
61
|
-
// instance on the server — never touching globals), then convert every
|
|
62
|
-
// HTML-RTE field to Tiptap JSON with it.
|
|
63
|
-
const parser = await resolveHtmlParser();
|
|
64
|
-
convertModel(brandPageModel, parser);
|
|
65
|
-
return migrationResult;
|
|
66
|
-
};
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* BP-5787 — golden-master fixture for the RTE HTML→JSON migration.
|
|
3
|
-
*
|
|
4
|
-
* A single rich-text value that exercises every mark/node the editor can emit,
|
|
5
|
-
* so one snapshot guards the whole serialisation surface. Captured as a baseline
|
|
6
|
-
* on the current HTML pipeline; after the migration the JSON pipeline must
|
|
7
|
-
* reproduce byte-identical rendered output (see rte-render-parity.test.ts).
|
|
8
|
-
*
|
|
9
|
-
* Coverage:
|
|
10
|
-
* - block nodes: heading (h2), paragraph, text-align (center / right)
|
|
11
|
-
* - inline marks: bold, italic, underline, strike
|
|
12
|
-
* - textStyle attrs: fontFamily, fontSize (em), letterSpacing (em), lineHeight (unitless)
|
|
13
|
-
* - colour: theme TOKEN ref (var(--logo-text-color)) AND a literal hex (#FF0000)
|
|
14
|
-
* - highlight (background colour via <mark>)
|
|
15
|
-
* - links: internal (target=_self) and external (target=_blank rel=noopener)
|
|
16
|
-
*/
|
|
17
|
-
export declare const COMPLEX_RTE_HTML = "<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\">highlight</mark> <span style=\"color: #FF0000\">literal hex</span> <a target=\"_self\" href=\"/about\">internal link</a> <a target=\"_blank\" rel=\"noopener noreferrer nofollow\" href=\"https://example.com\">external link</a></p><p style=\"text-align: right\">second paragraph, right-aligned</p>";
|
|
18
|
-
/**
|
|
19
|
-
* The theme-token CSS variable that MUST survive the HTML→JSON round-trip
|
|
20
|
-
* (BP-5787 stores token refs instead of flattening them to rgb()).
|
|
21
|
-
*/
|
|
22
|
-
export declare const THEME_TOKEN_REF = "var(--logo-text-color)";
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* BP-5787 — golden-master fixture for the RTE HTML→JSON migration.
|
|
3
|
-
*
|
|
4
|
-
* A single rich-text value that exercises every mark/node the editor can emit,
|
|
5
|
-
* so one snapshot guards the whole serialisation surface. Captured as a baseline
|
|
6
|
-
* on the current HTML pipeline; after the migration the JSON pipeline must
|
|
7
|
-
* reproduce byte-identical rendered output (see rte-render-parity.test.ts).
|
|
8
|
-
*
|
|
9
|
-
* Coverage:
|
|
10
|
-
* - block nodes: heading (h2), paragraph, text-align (center / right)
|
|
11
|
-
* - inline marks: bold, italic, underline, strike
|
|
12
|
-
* - textStyle attrs: fontFamily, fontSize (em), letterSpacing (em), lineHeight (unitless)
|
|
13
|
-
* - colour: theme TOKEN ref (var(--logo-text-color)) AND a literal hex (#FF0000)
|
|
14
|
-
* - highlight (background colour via <mark>)
|
|
15
|
-
* - links: internal (target=_self) and external (target=_blank rel=noopener)
|
|
16
|
-
*/
|
|
17
|
-
export const COMPLEX_RTE_HTML = `<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">highlight</mark> <span style="color: #FF0000">literal hex</span> <a target="_self" href="/about">internal link</a> <a target="_blank" rel="noopener noreferrer nofollow" href="https://example.com">external link</a></p><p style="text-align: right">second paragraph, right-aligned</p>`;
|
|
18
|
-
/**
|
|
19
|
-
* The theme-token CSS variable that MUST survive the HTML→JSON round-trip
|
|
20
|
-
* (BP-5787 stores token refs instead of flattening them to rgb()).
|
|
21
|
-
*/
|
|
22
|
-
export const THEME_TOKEN_REF = 'var(--logo-text-color)';
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* BP-5787 — font-family with quote-stripping parse.
|
|
3
|
-
*
|
|
4
|
-
* The stock extension stores whatever `element.style.fontFamily` returns. For
|
|
5
|
-
* multi-word families (e.g. "Open Sans", "Playwrite AU") the browser quotes the
|
|
6
|
-
* value, so parsing HTML — pasting RTE-styled text between editors, or the
|
|
7
|
-
* HTML→JSON migration — yields a value that still contains the quote chars
|
|
8
|
-
* (`"Open Sans"`). That value then (a) no longer matches the font list and,
|
|
9
|
-
* worse, (b) is dropped by the native RTE sanitiser (`sanitizeRteJson`), whose
|
|
10
|
-
* `isSafeCssValue` rejects any value containing `"` `'` `;` — so the font is
|
|
11
|
-
* silently lost. Mirror the FontSize/LetterSpacing extensions and strip the
|
|
12
|
-
* quotes on parse so the stored value is always the bare family name.
|
|
13
|
-
*/
|
|
14
|
-
export declare const FontFamily: import("@tiptap/core").Extension<import("@tiptap/extension-text-style").FontFamilyOptions, any>;
|