@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
|
@@ -22,7 +22,6 @@ import { migration16 } from './migration/migration-16';
|
|
|
22
22
|
import { migration17 } from './migration/migration-17';
|
|
23
23
|
import { migration18 } from './migration/migration-18';
|
|
24
24
|
import { migration19 } from './migration/migration-19';
|
|
25
|
-
import { migration20 } from './migration/migration-20';
|
|
26
25
|
|
|
27
26
|
export interface MigrationData {
|
|
28
27
|
brandPageModel: BrandPageModel;
|
|
@@ -180,17 +179,6 @@ const MIGRATION_TABLE = [
|
|
|
180
179
|
description:
|
|
181
180
|
'BP-5800: seed the button fill colour as each block’s own container colour so it is independent of the site button (existing sites render unchanged) — backgroundStyle for mailing-list, request-payment and support-me (all types); cardStyle for the BP/BC contact form (skipped on brand-site)',
|
|
182
181
|
},
|
|
183
|
-
{
|
|
184
|
-
version: 20,
|
|
185
|
-
migrateFn: migration20,
|
|
186
|
-
// One-time versioned migration (NOT runAlways): legacy sites (v < 20)
|
|
187
|
-
// convert on their first load in any environment — the maker (browser DOM)
|
|
188
|
-
// and web-renderer SSR (migration-20 installs a jsdom window). It no longer
|
|
189
|
-
// needs to re-run every load because the AI section builders now emit Tiptap
|
|
190
|
-
// JSON directly, so there is no recurring post-load source of HTML to catch.
|
|
191
|
-
description:
|
|
192
|
-
'BP-5787: convert inline-HTML RTE content to Tiptap JSON on first load (client or server); AI builders emit JSON directly, so this runs once per site',
|
|
193
|
-
},
|
|
194
182
|
] as MigrationTableItem[];
|
|
195
183
|
|
|
196
184
|
export const GetLatestModelVersion = () =>
|
|
@@ -87,57 +87,6 @@ describe('style.util', () => {
|
|
|
87
87
|
);
|
|
88
88
|
});
|
|
89
89
|
|
|
90
|
-
test('applyLogoThemeToBrandPageModel preserves var() token refs in JSON RTE content (BP-5787)', () => {
|
|
91
|
-
// Arrange — title stored as Tiptap JSON with a theme-token colour mark.
|
|
92
|
-
const logoTheme = logoWithFlatColorsTheme;
|
|
93
|
-
const titleDoc = {
|
|
94
|
-
type: 'doc',
|
|
95
|
-
content: [
|
|
96
|
-
{
|
|
97
|
-
type: 'heading',
|
|
98
|
-
attrs: { level: 2, textAlign: 'center' },
|
|
99
|
-
content: [
|
|
100
|
-
{
|
|
101
|
-
type: 'text',
|
|
102
|
-
marks: [
|
|
103
|
-
{
|
|
104
|
-
type: 'textStyle',
|
|
105
|
-
attrs: { color: `var(--${STYLE_THEME_KEYS.LogoTextColor})` },
|
|
106
|
-
},
|
|
107
|
-
],
|
|
108
|
-
text: 'Themed',
|
|
109
|
-
},
|
|
110
|
-
],
|
|
111
|
-
},
|
|
112
|
-
],
|
|
113
|
-
};
|
|
114
|
-
const brandPageModel = new BrandPageModel({
|
|
115
|
-
...BRAND_SITE,
|
|
116
|
-
pages: [
|
|
117
|
-
{
|
|
118
|
-
title: '1',
|
|
119
|
-
slug: '1',
|
|
120
|
-
contents: [
|
|
121
|
-
{
|
|
122
|
-
...createInitialBannerSectionContent(),
|
|
123
|
-
title: titleDoc,
|
|
124
|
-
} as any,
|
|
125
|
-
],
|
|
126
|
-
},
|
|
127
|
-
],
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
// Act
|
|
131
|
-
const { model } = applyLogoThemeToBrandPageModel(logoTheme, brandPageModel);
|
|
132
|
-
|
|
133
|
-
// Assert — JSON RTE content is an object, so the var() token is NOT
|
|
134
|
-
// flattened to rgb(); it survives so a new theme can still propagate.
|
|
135
|
-
const title = JSON.stringify((model.pages[0].contents[0] as any).title);
|
|
136
|
-
|
|
137
|
-
expect(title).toContain(`var(--${STYLE_THEME_KEYS.LogoTextColor})`);
|
|
138
|
-
expect(title).not.toContain(logoTheme.logoTextColor.rgb);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
90
|
test('applyLogoThemeToBrandPageModel should apply logo theme color to content color when content color has a color matched theme key', () => {
|
|
142
91
|
// Arrange
|
|
143
92
|
const logoTheme = logoWithFlatColorsTheme;
|
|
@@ -1,56 +1,17 @@
|
|
|
1
1
|
import { ESCAPED_NEWLINE_CHARACTER } from '../../constants/ui';
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* BP-5787 — a Tiptap JSON document is an object with `type: 'doc'`.
|
|
5
|
-
* Inlined here (rather than importing from brand-page-maker) to avoid a
|
|
6
|
-
* circular import: rich-text-editor.util imports getRenderText from this file.
|
|
7
|
-
*/
|
|
8
|
-
const isTiptapDoc = (value: unknown): value is { type: 'doc' } =>
|
|
9
|
-
typeof value === 'object' &&
|
|
10
|
-
value !== null &&
|
|
11
|
-
(value as { type?: string }).type === 'doc';
|
|
12
|
-
|
|
13
|
-
const jsonDocPlainText = (doc: unknown): string => {
|
|
14
|
-
const parts: string[] = [];
|
|
15
|
-
|
|
16
|
-
const walk = (node: any): void => {
|
|
17
|
-
if (typeof node?.text === 'string') parts.push(node.text);
|
|
18
|
-
|
|
19
|
-
node?.content?.forEach(walk);
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
walk(doc);
|
|
23
|
-
|
|
24
|
-
return parts.join(' ').replace(/\s+/g, ' ').trim();
|
|
25
|
-
};
|
|
26
|
-
|
|
27
3
|
export const getRenderText = (text: string): string => {
|
|
28
|
-
// BP-5787: RTE fields may now be Tiptap JSON objects. Non-strings pass
|
|
29
|
-
// through untouched so the renderer (TextWrapper) handles them via its
|
|
30
|
-
// JSON-aware path, instead of throwing on `.replace`.
|
|
31
|
-
if (typeof text !== 'string') {
|
|
32
|
-
return text as unknown as string;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
4
|
return text.replace(
|
|
36
5
|
new RegExp(`(${ESCAPED_NEWLINE_CHARACTER}|\\n)`, 'g'),
|
|
37
6
|
'<br/>',
|
|
38
7
|
);
|
|
39
8
|
};
|
|
40
9
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
10
|
+
export const escapeContentText = (
|
|
11
|
+
text: string | undefined,
|
|
12
|
+
): string | undefined => {
|
|
44
13
|
if (!text) {
|
|
45
|
-
return text
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (isTiptapDoc(text)) {
|
|
49
|
-
return jsonDocPlainText(text);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (typeof text !== 'string') {
|
|
53
|
-
return undefined;
|
|
14
|
+
return text;
|
|
54
15
|
}
|
|
55
16
|
|
|
56
17
|
return text
|
|
@@ -58,46 +19,6 @@ export const escapeContentText = (text: unknown): string | undefined => {
|
|
|
58
19
|
.replace(/(\r\n|\r|\n)/g, ESCAPED_NEWLINE_CHARACTER);
|
|
59
20
|
};
|
|
60
21
|
|
|
61
|
-
/**
|
|
62
|
-
* Character length of RTE content as the *editor* counts it — ProseMirror
|
|
63
|
-
* `doc.textContent`: text nodes concatenated with NO separator. This is the
|
|
64
|
-
* metric the MaxLength extension gates on, so length indicators must use it too.
|
|
65
|
-
*
|
|
66
|
-
* Do NOT use escapeContentText/jsonDocPlainText for length limiting: those
|
|
67
|
-
* join text nodes with a space for readable plain-text extraction, so they
|
|
68
|
-
* over-count by one character per inline mark boundary. Converting a word to a
|
|
69
|
-
* hyperlink (or bolding it, etc.) splits its text node, making the counter
|
|
70
|
-
* report length+1 for content the editor considers exactly at the cap (BP-5787).
|
|
71
|
-
*/
|
|
72
|
-
export const getContentTextLength = (value: unknown): number => {
|
|
73
|
-
if (!value) {
|
|
74
|
-
return 0;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (isTiptapDoc(value)) {
|
|
78
|
-
let length = 0;
|
|
79
|
-
|
|
80
|
-
const walk = (node: any): void => {
|
|
81
|
-
if (typeof node?.text === 'string') {
|
|
82
|
-
length += node.text.length;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
node?.content?.forEach(walk);
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
walk(value);
|
|
89
|
-
|
|
90
|
-
return length;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (typeof value !== 'string') {
|
|
94
|
-
return 0;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Legacy inline-HTML string: count visible text with tags stripped.
|
|
98
|
-
return value.replace(/(<([^>]+)>)/gi, '').length;
|
|
99
|
-
};
|
|
100
|
-
|
|
101
22
|
export const decodeHtmlEntities = (text: string): string => {
|
|
102
23
|
return text
|
|
103
24
|
.replace(/</g, '<')
|
|
@@ -235,11 +235,9 @@ const addProfileInfo = (
|
|
|
235
235
|
profile: ProfileViewModel,
|
|
236
236
|
vcardLines: string[],
|
|
237
237
|
): void => {
|
|
238
|
-
const name = sanitizeForVCard(
|
|
239
|
-
const title = sanitizeForVCard(
|
|
240
|
-
const description = sanitizeForVCard(
|
|
241
|
-
getPlainTextFromRTE(profile.description) || '',
|
|
242
|
-
);
|
|
238
|
+
const name = sanitizeForVCard(profile.name || '');
|
|
239
|
+
const title = sanitizeForVCard(profile.title || '');
|
|
240
|
+
const description = sanitizeForVCard(profile.description || '');
|
|
243
241
|
|
|
244
242
|
if (name) {
|
|
245
243
|
const nameParts = name.trim().split(/\s+/);
|
|
@@ -318,7 +316,7 @@ export const saveContact = (brandPageData: {
|
|
|
318
316
|
}): void => {
|
|
319
317
|
const vcard = generateVCard(brandPageData);
|
|
320
318
|
const profile = findProfileContent(brandPageData);
|
|
321
|
-
const filename = generateVCardFilename(
|
|
319
|
+
const filename = generateVCardFilename(profile?.name);
|
|
322
320
|
|
|
323
321
|
downloadVCard(vcard, filename);
|
|
324
322
|
};
|
|
@@ -119,7 +119,7 @@ import {
|
|
|
119
119
|
getRouteNameByPageIndex,
|
|
120
120
|
getRouteParamsByPage,
|
|
121
121
|
} from './route.util';
|
|
122
|
-
import { hasRTEContent
|
|
122
|
+
import { hasRTEContent } from '../../brand-page-maker/utils';
|
|
123
123
|
import {
|
|
124
124
|
getPageIdFromHref,
|
|
125
125
|
isHrefPageId,
|
|
@@ -1519,86 +1519,6 @@ const mapPageIdReferencesToRelativePaths = (
|
|
|
1519
1519
|
});
|
|
1520
1520
|
};
|
|
1521
1521
|
|
|
1522
|
-
// Resolve a `pageid:<id>` internal-link href to the published relative path.
|
|
1523
|
-
// Returns the resolved href, or null when `href` is not an internal pageid link
|
|
1524
|
-
// (external/absolute links are left untouched). Falls back to the site root when
|
|
1525
|
-
// the pageid can't be matched to a nav route.
|
|
1526
|
-
const resolvePageIdHref = (
|
|
1527
|
-
href: string,
|
|
1528
|
-
nav: NavViewModel,
|
|
1529
|
-
options: ViewModelMappingOptions,
|
|
1530
|
-
): string | null => {
|
|
1531
|
-
// Only process internal links (no protocol, doesn't start with /)
|
|
1532
|
-
if (!href || href.match(/^(https?:\/\/|\/)/) || !isHrefPageId(href)) {
|
|
1533
|
-
return null;
|
|
1534
|
-
}
|
|
1535
|
-
|
|
1536
|
-
const pageId = getPageIdFromHref(href);
|
|
1537
|
-
const route = findNavItemByPageId(nav.navItems, pageId);
|
|
1538
|
-
const brandPageSlugRoute = options.brandPageSlug
|
|
1539
|
-
? `/${options.brandPageSlug}`
|
|
1540
|
-
: '/';
|
|
1541
|
-
|
|
1542
|
-
if (route) {
|
|
1543
|
-
if (route.isRoot) {
|
|
1544
|
-
return brandPageSlugRoute;
|
|
1545
|
-
}
|
|
1546
|
-
|
|
1547
|
-
return `${brandPageSlugRoute}${brandPageSlugRoute === '/' ? '' : '/'}${route.relativePath}`;
|
|
1548
|
-
}
|
|
1549
|
-
|
|
1550
|
-
// fallback to root if pageid cannot be resolved.
|
|
1551
|
-
return brandPageSlugRoute;
|
|
1552
|
-
};
|
|
1553
|
-
|
|
1554
|
-
// BP-5847: walk a Tiptap JSON doc and rewrite every link mark whose href is a
|
|
1555
|
-
// `pageid:` reference into its published relative path. Returns a NEW doc when
|
|
1556
|
-
// something changed (so the shared model doc is never mutated), else the same
|
|
1557
|
-
// reference.
|
|
1558
|
-
export const resolvePageIdsInRteJson = (
|
|
1559
|
-
doc: unknown,
|
|
1560
|
-
nav: NavViewModel,
|
|
1561
|
-
options: ViewModelMappingOptions,
|
|
1562
|
-
): unknown => {
|
|
1563
|
-
let changed = false;
|
|
1564
|
-
|
|
1565
|
-
const walk = (node: any): any => {
|
|
1566
|
-
if (!node || typeof node !== 'object') {
|
|
1567
|
-
return node;
|
|
1568
|
-
}
|
|
1569
|
-
|
|
1570
|
-
let next = node;
|
|
1571
|
-
|
|
1572
|
-
if (Array.isArray(node.marks)) {
|
|
1573
|
-
const marks = node.marks.map((mark: any) => {
|
|
1574
|
-
if (mark?.type === 'link' && typeof mark.attrs?.href === 'string') {
|
|
1575
|
-
const resolved = resolvePageIdHref(mark.attrs.href, nav, options);
|
|
1576
|
-
|
|
1577
|
-
if (resolved !== null) {
|
|
1578
|
-
changed = true;
|
|
1579
|
-
|
|
1580
|
-
return { ...mark, attrs: { ...mark.attrs, href: resolved } };
|
|
1581
|
-
}
|
|
1582
|
-
}
|
|
1583
|
-
|
|
1584
|
-
return mark;
|
|
1585
|
-
});
|
|
1586
|
-
|
|
1587
|
-
next = { ...next, marks };
|
|
1588
|
-
}
|
|
1589
|
-
|
|
1590
|
-
if (Array.isArray(node.content)) {
|
|
1591
|
-
next = { ...next, content: node.content.map(walk) };
|
|
1592
|
-
}
|
|
1593
|
-
|
|
1594
|
-
return next;
|
|
1595
|
-
};
|
|
1596
|
-
|
|
1597
|
-
const result = walk(doc);
|
|
1598
|
-
|
|
1599
|
-
return changed ? result : doc;
|
|
1600
|
-
};
|
|
1601
|
-
|
|
1602
1522
|
const dynamicallyMapPageIdInHyperlinksToRelativePaths = (
|
|
1603
1523
|
content: any,
|
|
1604
1524
|
nav: NavViewModel,
|
|
@@ -1611,15 +1531,7 @@ const dynamicallyMapPageIdInHyperlinksToRelativePaths = (
|
|
|
1611
1531
|
return;
|
|
1612
1532
|
}
|
|
1613
1533
|
|
|
1614
|
-
if (
|
|
1615
|
-
// BP-5847: RTE fields may now be Tiptap JSON. Resolve pageid link marks
|
|
1616
|
-
// here too, or internal links render as raw `pageid:` and don't navigate.
|
|
1617
|
-
(content as unknown as Record<string, unknown>)[key] =
|
|
1618
|
-
resolvePageIdsInRteJson(value, nav, options);
|
|
1619
|
-
} else if (
|
|
1620
|
-
typeof value === 'object' &&
|
|
1621
|
-
(key === 'items' || key === 'textItems')
|
|
1622
|
-
) {
|
|
1534
|
+
if (typeof value === 'object' && (key === 'items' || key === 'textItems')) {
|
|
1623
1535
|
if (Array.isArray(value)) {
|
|
1624
1536
|
value.forEach((item) => {
|
|
1625
1537
|
dynamicallyMapPageIdInHyperlinksToRelativePaths(item, nav, options);
|
|
@@ -1631,11 +1543,31 @@ const dynamicallyMapPageIdInHyperlinksToRelativePaths = (
|
|
|
1631
1543
|
(content as unknown as Record<string, unknown>)[key] = value.replace(
|
|
1632
1544
|
/<a\s+([^>]*?)href="([^"]*?)"([^>]*?)>/gi,
|
|
1633
1545
|
(match, existingAttrsStart, href, existingAttrsEnd) => {
|
|
1634
|
-
|
|
1546
|
+
// Only process internal links (no protocol, doesn't start with /)
|
|
1547
|
+
if (href && !href.match(/^(https?:\/\/|\/)/) && isHrefPageId(href)) {
|
|
1548
|
+
const pageId = getPageIdFromHref(href);
|
|
1549
|
+
|
|
1550
|
+
const route = findNavItemByPageId(nav.navItems, pageId);
|
|
1551
|
+
|
|
1552
|
+
const brandPageSlugRoute = options.brandPageSlug
|
|
1553
|
+
? `/${options.brandPageSlug}`
|
|
1554
|
+
: '/';
|
|
1555
|
+
|
|
1556
|
+
if (route) {
|
|
1557
|
+
let href = `${brandPageSlugRoute}`;
|
|
1558
|
+
|
|
1559
|
+
if (!route.isRoot) {
|
|
1560
|
+
href = `${brandPageSlugRoute}${brandPageSlugRoute === '/' ? '' : '/'}${route.relativePath}`;
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
return `<a ${existingAttrsStart}href="${href}"${existingAttrsEnd}>`;
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
// fallback to root if pageid cannot be resolved.
|
|
1567
|
+
return `<a ${existingAttrsStart}href="${brandPageSlugRoute}"${existingAttrsEnd}>`;
|
|
1568
|
+
}
|
|
1635
1569
|
|
|
1636
|
-
return
|
|
1637
|
-
? match
|
|
1638
|
-
: `<a ${existingAttrsStart}href="${resolved}"${existingAttrsEnd}>`;
|
|
1570
|
+
return match;
|
|
1639
1571
|
},
|
|
1640
1572
|
);
|
|
1641
1573
|
}
|
|
@@ -41,7 +41,6 @@ import type {
|
|
|
41
41
|
BackgroundShape,
|
|
42
42
|
PersonalisationRole,
|
|
43
43
|
SaveContactOptions,
|
|
44
|
-
RichTextValue,
|
|
45
44
|
} from './models';
|
|
46
45
|
import type { ImagesSectionContentDisplayStyle } from './constants';
|
|
47
46
|
|
|
@@ -197,8 +196,8 @@ export interface TextImageSectionViewModel {
|
|
|
197
196
|
id: string;
|
|
198
197
|
type: 'images-section';
|
|
199
198
|
role: ContentType;
|
|
200
|
-
title:
|
|
201
|
-
description:
|
|
199
|
+
title: string;
|
|
200
|
+
description: string;
|
|
202
201
|
displayStyle: ImagesSectionContentDisplayStyle;
|
|
203
202
|
aspectRatio: AspectRatio;
|
|
204
203
|
areImagesFullWidth: boolean;
|
|
@@ -386,10 +386,10 @@
|
|
|
386
386
|
<div class="tw-relative" :class="{ 'editor-compact': isCompact }">
|
|
387
387
|
<BubbleMenu
|
|
388
388
|
:editor="editor"
|
|
389
|
-
:options="
|
|
389
|
+
:tippy-options="tippyOptions"
|
|
390
390
|
:update-delay="0"
|
|
391
391
|
:should-show="shouldShow"
|
|
392
|
-
class="tw-w-fit
|
|
392
|
+
class="tw-w-fit"
|
|
393
393
|
>
|
|
394
394
|
<div class="bubble-menu">
|
|
395
395
|
<div
|
|
@@ -455,8 +455,7 @@ import {
|
|
|
455
455
|
ButtonGroup,
|
|
456
456
|
TextInput,
|
|
457
457
|
} from '@designcrowd/fe-shared-lib';
|
|
458
|
-
import { EditorContent } from '@tiptap/vue-3';
|
|
459
|
-
import { BubbleMenu } from '@tiptap/vue-3/menus';
|
|
458
|
+
import { EditorContent, BubbleMenu } from '@tiptap/vue-3';
|
|
460
459
|
import { useRichTextEditor } from '../../composables/rich-text-editor/useRichTextEditor';
|
|
461
460
|
import FontPicker from '@shared/font-family-selector/FontPicker.vue';
|
|
462
461
|
import ColorField from '@shared/content-settings/components/color-field/ColorField.vue';
|
|
@@ -954,7 +953,7 @@ const {
|
|
|
954
953
|
hyperlinkValue,
|
|
955
954
|
setHyperlinkUrlValue,
|
|
956
955
|
shouldShow,
|
|
957
|
-
|
|
956
|
+
tippyOptions,
|
|
958
957
|
handleAdminColorChange,
|
|
959
958
|
} = useRichTextEditor(
|
|
960
959
|
model,
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import mixin from './TemplatePreview.mixin';
|
|
3
|
+
|
|
4
|
+
// Covers the `previewImageUrl` computed's content-key precedence. The getter is pure over
|
|
5
|
+
// `this` (template/isPersonalisable/userToken/logoImage), so it's invoked directly with a
|
|
6
|
+
// mocked `this` rather than mounting the component.
|
|
7
|
+
const previewImageUrl = (mixin as any).computed.previewImageUrl as (
|
|
8
|
+
this: unknown,
|
|
9
|
+
) => string;
|
|
10
|
+
|
|
11
|
+
const BASE =
|
|
12
|
+
'https://cdn/preview?templateToken=t&logoDraftToken=l&deviceType=Desktop';
|
|
13
|
+
|
|
14
|
+
function run(context: {
|
|
15
|
+
template: {
|
|
16
|
+
previewImageUrl?: string;
|
|
17
|
+
contentKey?: string;
|
|
18
|
+
usesUserPromptContentKey?: boolean;
|
|
19
|
+
};
|
|
20
|
+
isPersonalisable?: boolean;
|
|
21
|
+
userToken?: string | null;
|
|
22
|
+
logoImage?: { logoVariation?: number };
|
|
23
|
+
}): string {
|
|
24
|
+
return previewImageUrl.call({
|
|
25
|
+
isPersonalisable: true,
|
|
26
|
+
userToken: null,
|
|
27
|
+
logoImage: undefined,
|
|
28
|
+
...context,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe('TemplatePreview.mixin previewImageUrl', () => {
|
|
33
|
+
it('overrides a backend-baked logo-draft contentKey with the editor userPrompt key (bp-5941)', () => {
|
|
34
|
+
// Empty-search post-purchase: backend baked the per-template logo-draft contentKey.
|
|
35
|
+
const result = run({
|
|
36
|
+
template: {
|
|
37
|
+
previewImageUrl: `${BASE}&contentKey=5267ff6d&userToken=abc`,
|
|
38
|
+
contentKey: '2c077d2f',
|
|
39
|
+
usesUserPromptContentKey: true,
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
expect(result).toContain('userPromptContentKey=2c077d2f');
|
|
44
|
+
expect(result).not.toContain('contentKey=5267ff6d');
|
|
45
|
+
// Left in place — the renderer ignores it once userPromptContentKey is set.
|
|
46
|
+
expect(result).toContain('userToken=abc');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('does not duplicate the userPrompt key when the backend already baked it', () => {
|
|
50
|
+
const result = run({
|
|
51
|
+
template: {
|
|
52
|
+
previewImageUrl: `${BASE}&userPromptContentKey=2c077d2f`,
|
|
53
|
+
contentKey: '2c077d2f',
|
|
54
|
+
usesUserPromptContentKey: true,
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
expect(result.match(/userPromptContentKey=2c077d2f/g)).toHaveLength(1);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('uses the regular bucket (contentKey + userToken) when there is no userPrompt key', () => {
|
|
62
|
+
const result = run({
|
|
63
|
+
template: {
|
|
64
|
+
previewImageUrl: BASE,
|
|
65
|
+
contentKey: 'abc123',
|
|
66
|
+
usesUserPromptContentKey: false,
|
|
67
|
+
},
|
|
68
|
+
userToken: 'user-1',
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
expect(result).toContain('contentKey=abc123');
|
|
72
|
+
expect(result).toContain('userToken=user-1');
|
|
73
|
+
expect(result).not.toContain('userPromptContentKey=');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('leaves an already-personalised regular-bucket url untouched', () => {
|
|
77
|
+
const result = run({
|
|
78
|
+
template: {
|
|
79
|
+
previewImageUrl: `${BASE}&contentKey=already&userToken=abc`,
|
|
80
|
+
contentKey: 'ignored',
|
|
81
|
+
usesUserPromptContentKey: false,
|
|
82
|
+
},
|
|
83
|
+
userToken: 'user-1',
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
expect(result).toContain('contentKey=already');
|
|
87
|
+
expect(result).not.toContain('contentKey=ignored');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('appends the logo variation regardless of the content-key branch', () => {
|
|
91
|
+
const result = run({
|
|
92
|
+
template: {
|
|
93
|
+
previewImageUrl: `${BASE}&contentKey=5267ff6d`,
|
|
94
|
+
contentKey: '2c077d2f',
|
|
95
|
+
usesUserPromptContentKey: true,
|
|
96
|
+
},
|
|
97
|
+
logoImage: { logoVariation: 3 },
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
expect(result).toContain('userPromptContentKey=2c077d2f');
|
|
101
|
+
expect(result).toContain('logoVariation=3');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// The old regex only matched params preceded by `&`, so a leading `?contentKey=`
|
|
105
|
+
// survived and the URL carried BOTH keys — the exact divergence this override exists
|
|
106
|
+
// to prevent. Param order must not matter.
|
|
107
|
+
it('strips a baked contentKey even when it is the first query param', () => {
|
|
108
|
+
const result = run({
|
|
109
|
+
template: {
|
|
110
|
+
previewImageUrl:
|
|
111
|
+
'https://cdn/preview?contentKey=5267ff6d&templateToken=t&userToken=abc',
|
|
112
|
+
contentKey: '2c077d2f',
|
|
113
|
+
usesUserPromptContentKey: true,
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
expect(result).not.toContain('contentKey=5267ff6d');
|
|
118
|
+
expect(result).toContain('userToken=abc');
|
|
119
|
+
expect(result).toContain('userPromptContentKey=2c077d2f');
|
|
120
|
+
expect(result).toContain('templateToken=t');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('leaves a single userPromptContentKey when one was already baked', () => {
|
|
124
|
+
const result = run({
|
|
125
|
+
template: {
|
|
126
|
+
previewImageUrl: 'https://cdn/preview?userPromptContentKey=STALE&x=1',
|
|
127
|
+
contentKey: 'EDITOR',
|
|
128
|
+
usesUserPromptContentKey: true,
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
expect(result.match(/userPromptContentKey=/g)).toHaveLength(1);
|
|
133
|
+
expect(result).toContain('userPromptContentKey=EDITOR');
|
|
134
|
+
});
|
|
135
|
+
});
|
|
@@ -32,6 +32,21 @@ import {
|
|
|
32
32
|
} from '@brand-page/utils/logo.util';
|
|
33
33
|
import { useVideoStore } from '@src/lib';
|
|
34
34
|
|
|
35
|
+
// Content params the backend may bake into a preview URL. When the editor's key wins,
|
|
36
|
+
// none of these may survive — a stale one alongside the new key is the bp-5941 mismatch.
|
|
37
|
+
// contentKey MUST stay listed: preview-renderer prefers contentKey+userToken over
|
|
38
|
+
// userPromptContentKey, so a surviving contentKey would silently win. userToken alone
|
|
39
|
+
// can't trigger that branch, so it's left as the backend sent it.
|
|
40
|
+
const BAKED_CONTENT_PARAMS = ['contentKey', 'userPromptContentKey'];
|
|
41
|
+
|
|
42
|
+
const toUrl = (value: string): URL | null => {
|
|
43
|
+
try {
|
|
44
|
+
return new URL(value);
|
|
45
|
+
} catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
35
50
|
const DEBOUNCE_IN_MS = 250;
|
|
36
51
|
|
|
37
52
|
interface TemplatePreviewData {
|
|
@@ -237,29 +252,55 @@ export default defineComponent({
|
|
|
237
252
|
return '';
|
|
238
253
|
}
|
|
239
254
|
|
|
240
|
-
const
|
|
241
|
-
previewImageUrl.includes('contentKey=') ||
|
|
242
|
-
previewImageUrl.includes('userPromptContentKey=');
|
|
243
|
-
|
|
244
|
-
if (
|
|
255
|
+
const hasAuthoritativeUserPromptKey =
|
|
245
256
|
this.isPersonalisable &&
|
|
246
|
-
this.template.
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
+
this.template.usesUserPromptContentKey &&
|
|
258
|
+
!!this.template.contentKey;
|
|
259
|
+
|
|
260
|
+
if (hasAuthoritativeUserPromptKey) {
|
|
261
|
+
// The editor resolves post-purchase content from the current industry, so its
|
|
262
|
+
// userPrompt key is authoritative. On empty search the backend bakes a per-template
|
|
263
|
+
// logo-draft contentKey (DY-1305) that diverges from the editor (bp-5941); strip any
|
|
264
|
+
// baked content params and resolve from the userPrompt S3 bucket via the editor's key.
|
|
265
|
+
const url = toUrl(previewImageUrl);
|
|
266
|
+
|
|
267
|
+
// Not a parseable absolute URL — nothing to personalise, leave it alone.
|
|
268
|
+
if (!url) {
|
|
269
|
+
return previewImageUrl;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
BAKED_CONTENT_PARAMS.forEach((param) => url.searchParams.delete(param));
|
|
273
|
+
url.searchParams.set(
|
|
274
|
+
'userPromptContentKey',
|
|
275
|
+
this.template.contentKey as string,
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
previewImageUrl = url.toString();
|
|
279
|
+
} else {
|
|
280
|
+
const isAlreadyPersonalised =
|
|
281
|
+
previewImageUrl.includes('contentKey=') ||
|
|
282
|
+
previewImageUrl.includes('userPromptContentKey=');
|
|
283
|
+
|
|
284
|
+
if (
|
|
285
|
+
this.isPersonalisable &&
|
|
286
|
+
this.template.contentKey &&
|
|
287
|
+
!isAlreadyPersonalised
|
|
288
|
+
) {
|
|
289
|
+
if (this.userToken) {
|
|
290
|
+
previewImageUrl += `&contentKey=${this.template.contentKey}`;
|
|
291
|
+
|
|
292
|
+
// The backend already bakes userToken into brand-site preview URLs. Only add
|
|
293
|
+
// it when missing - two userToken params parse as an array and the preview
|
|
294
|
+
// renderer rejects it ("userToken must be a string type") (bp-5765).
|
|
295
|
+
if (!previewImageUrl.includes('userToken=')) {
|
|
296
|
+
previewImageUrl += `&userToken=${this.userToken}`;
|
|
297
|
+
}
|
|
298
|
+
} else {
|
|
299
|
+
// userPromptContentKey resolves from the userPrompt S3 bucket (real AI images).
|
|
300
|
+
// Post-purchase designs with a userPrompt key route here too, NOT through the
|
|
301
|
+
// userToken/regular-bucket branch above which holds placeholders (bp-5454).
|
|
302
|
+
previewImageUrl += `&userPromptContentKey=${this.template.contentKey}`;
|
|
257
303
|
}
|
|
258
|
-
} else {
|
|
259
|
-
// userPromptContentKey resolves from the userPrompt S3 bucket (real AI images).
|
|
260
|
-
// Post-purchase designs with a userPrompt key route here too, NOT through the
|
|
261
|
-
// userToken/regular-bucket branch above which holds placeholders (bp-5454).
|
|
262
|
-
previewImageUrl += `&userPromptContentKey=${this.template.contentKey}`;
|
|
263
304
|
}
|
|
264
305
|
}
|
|
265
306
|
|
|
@@ -33,6 +33,7 @@ export function useAiMetadataBuilder(logger: Logger) {
|
|
|
33
33
|
model: BrandPageModel,
|
|
34
34
|
aiAssistantToolCall: AiAssistantToolCall[],
|
|
35
35
|
options: {
|
|
36
|
+
maxCharacters?: number;
|
|
36
37
|
/**
|
|
37
38
|
* Invoked by the UndoBuilder to resolve the pre-prompt snapshot.
|
|
38
39
|
* Required when the response includes an `undo` tool call.
|