@designcrowd/library.brand-page 6.2.11-builder-v2-1 → 6.2.12
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-Dg2pOwbr.js → CreatedOnBrandCrowd-de-de-BtVKllNQ.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-en-us-BILs_hsB.js → CreatedOnBrandCrowd-en-us-CX5V0v8u.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-es-es-VpxQ0N5F.js → CreatedOnBrandCrowd-es-es-CbA02PCL.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-fr-CFHWRHzi.js → CreatedOnBrandCrowd-fr-ca-BQAKruBF.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-ca-CFHWRHzi.js → CreatedOnBrandCrowd-fr-fr-BQAKruBF.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-pt-CZfs45s9.js → CreatedOnBrandCrowd-pt-br-Yk_yzB3s.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-br-CZfs45s9.js → CreatedOnBrandCrowd-pt-pt-Yk_yzB3s.js} +1 -1
- package/lib/src/{CreatedOnDcom-de-de-DYMQ6sIF.js → CreatedOnDcom-de-de-DJZQbPSD.js} +1 -1
- package/lib/src/{CreatedOnDcom-en-us-Dj5sdPid.js → CreatedOnDcom-en-us-BJvNAa4l.js} +1 -1
- package/lib/src/{CreatedOnDcom-es-es-Cx9yNTro.js → CreatedOnDcom-es-es-CUwB1FRY.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-ca-Bzr0SjGq.js → CreatedOnDcom-fr-ca-CK54U4oh.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-fr-BgqKY35F.js → CreatedOnDcom-fr-fr-DuUNBGrM.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-br-CXK6qfvL.js → CreatedOnDcom-pt-br-BfODpFNV.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-pt-B8SggNTQ.js → CreatedOnDcom-pt-pt-Cf6u2GCg.js} +1 -1
- package/lib/src/brand-page/__fixtures__/production-text-style-resolver.fixture.d.ts +16 -0
- package/lib/src/brand-page/__fixtures__/production-text-style-resolver.fixture.js +35 -0
- package/lib/src/brand-page/components/contact-form/ContactForm.mixin.d.ts +3 -0
- package/lib/src/brand-page/components/contact-form/ContactForm.mixin.js +19 -13
- package/lib/src/brand-page/components/mailing-list/MailingList.mixin.d.ts +3 -1
- package/lib/src/brand-page/components/mailing-list/MailingList.mixin.js +17 -2
- package/lib/src/brand-page/components/navigation/button-carousel/ButtonCarouselNavigation.mixin.d.ts +11 -3
- package/lib/src/brand-page/components/navigation/button-carousel/ButtonCarouselNavigation.mixin.js +35 -1
- package/lib/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.mixin.d.ts +8 -2
- package/lib/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.mixin.js +27 -3
- package/lib/src/brand-page/components/page/Page.mixin.d.ts +5 -2
- package/lib/src/brand-page/components/page/Page.mixin.js +33 -27
- package/lib/src/brand-page/components/page-footer/PageFooter.mixin.d.ts +7 -2
- package/lib/src/brand-page/components/page-footer/PageFooter.mixin.js +21 -1
- package/lib/src/brand-page/components/payment/Payment.mixin.d.ts +5 -2
- package/lib/src/brand-page/components/payment/Payment.mixin.js +22 -2
- package/lib/src/brand-page/components/payment/support-me/SupportMe.mixin.d.ts +6 -2
- package/lib/src/brand-page/components/payment/support-me/SupportMe.mixin.js +32 -1
- package/lib/src/brand-page/components/social-links/SocialLinks.stories.js +0 -3
- package/lib/src/brand-page/components/testimonial-section/__fixtures__/data.js +0 -3
- package/lib/src/brand-page/components/text/Text.stories.js +0 -17
- package/lib/src/brand-page/constants/font.constants.d.ts +62 -0
- package/lib/src/brand-page/constants/font.constants.js +35 -0
- package/lib/src/brand-page/models/brand-page-context.model.d.ts +2 -0
- package/lib/src/brand-page/models/brand-page-context.model.js +7 -0
- package/lib/src/brand-page/models/index.d.ts +23 -24
- package/lib/src/brand-page/models/index.js +20 -7
- package/lib/src/brand-page/models/navigation-styles.model.d.ts +1 -1
- package/lib/src/brand-page/models/navigation-styles.model.js +17 -8
- package/lib/src/brand-page/models/payment.model.d.ts +11 -6
- package/lib/src/brand-page/models/styles.model.d.ts +53 -7
- package/lib/src/brand-page/models/styles.model.js +115 -27
- package/lib/src/brand-page/utils/color-palette.util.d.ts +43 -23
- package/lib/src/brand-page/utils/color-palette.util.js +65 -27
- package/lib/src/brand-page/utils/color.util.d.ts +13 -9
- package/lib/src/brand-page/utils/color.util.js +56 -15
- package/lib/src/brand-page/utils/container-text-color.util.d.ts +46 -13
- package/lib/src/brand-page/utils/container-text-color.util.js +321 -41
- package/lib/src/brand-page/utils/index.d.ts +1 -0
- package/lib/src/brand-page/utils/index.js +1 -0
- package/lib/src/brand-page/utils/migration/migration-20.js +6 -2
- package/lib/src/brand-page/utils/migration/migration-21.js +34 -6
- package/lib/src/brand-page/utils/migration/migration-22.d.ts +99 -0
- package/lib/src/brand-page/utils/migration/migration-22.js +909 -0
- package/lib/src/brand-page/utils/migration.util.js +17 -0
- package/lib/src/brand-page/utils/model.util.d.ts +4 -0
- package/lib/src/brand-page/utils/model.util.js +4 -0
- package/lib/src/brand-page/utils/personalisation.util.js +9 -1
- package/lib/src/brand-page/utils/rte-role-lift.util.d.ts +72 -0
- package/lib/src/brand-page/utils/rte-role-lift.util.js +275 -0
- package/lib/src/brand-page/utils/style.util.d.ts +5 -1
- package/lib/src/brand-page/utils/style.util.js +34 -11
- package/lib/src/brand-page/utils/text-role-css.util.d.ts +19 -0
- package/lib/src/brand-page/utils/text-role-css.util.js +186 -0
- package/lib/src/brand-page/utils/text-role-reconnect.util.d.ts +90 -0
- package/lib/src/brand-page/utils/text-role-reconnect.util.js +434 -0
- package/lib/src/brand-page/utils/text-style.util.d.ts +53 -23
- package/lib/src/brand-page/utils/text-style.util.js +122 -22
- package/lib/src/brand-page/utils/view-model.util.d.ts +9 -1
- package/lib/src/brand-page/utils/view-model.util.js +124 -111
- package/lib/src/brand-page/view-models.d.ts +1 -0
- package/lib/src/brand-page-maker/components/action-bar/ActionBar.mixin.d.ts +4 -1
- package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +34 -2
- package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.js +4 -4
- package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMakerTemplateSettings.mixin.d.ts +4 -1
- package/lib/src/brand-page-maker/components/brand-page-maker/__fixtures__/brand-page-examples.js +0 -10
- package/lib/src/brand-page-maker/components/brand-page-maker/__fixtures__/content-settings-items.fixture.js +0 -1
- package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.d.ts +44 -138
- package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.js +0 -28
- package/lib/src/brand-page-maker/components/brandpage-settings/ReplaceImageSettingsForBrandPageSettings.mixin.d.ts +4 -1
- package/lib/src/brand-page-maker/components/navigation/Navigation.mixin.d.ts +1 -1
- package/lib/src/brand-page-maker/components/preview/Preview.mixin.d.ts +7 -0
- package/lib/src/brand-page-maker/components/quick-link-bar/QuickLinkBar.mixin.d.ts +21 -0
- package/lib/src/brand-page-maker/components/seo-settings/SeoPageSettings.mixin.d.ts +25 -1
- package/lib/src/brand-page-maker/components/seo-settings/UrlSettings.mixin.d.ts +25 -1
- package/lib/src/brand-page-maker/components/seo-settings/UseExistingDomainModal.mixin.d.ts +21 -0
- package/lib/src/brand-page-maker/components/social-share/SocialShare.mixin.d.ts +4 -1
- package/lib/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.d.ts +25 -1
- package/lib/src/brand-page-maker/components/upgrade-to-premium-button/UpgradeToPremiumButton.mixin.d.ts +21 -0
- package/lib/src/brand-page-maker/composables/ai-chat/ai-builder-experiment.d.ts +10 -0
- package/lib/src/brand-page-maker/composables/ai-chat/ai-builder-experiment.js +11 -0
- package/lib/src/brand-page-maker/composables/ai-chat/useAiMetadataBuilder.d.ts +1 -0
- package/lib/src/brand-page-maker/composables/ai-chat/useAiMetadataBuilder.js +1 -0
- package/lib/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.js +14 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/RoleTypographyFill.d.ts +16 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/RoleTypographyFill.js +61 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/RteClipboard.d.ts +11 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/RteClipboard.js +236 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/font.d.ts +2 -1
- package/lib/src/brand-page-maker/composables/rich-text-editor/font.js +25 -6
- package/lib/src/brand-page-maker/composables/rich-text-editor/formatting.d.ts +17 -4
- package/lib/src/brand-page-maker/composables/rich-text-editor/formatting.js +204 -17
- package/lib/src/brand-page-maker/composables/rich-text-editor/hyperlink.d.ts +1 -1
- package/lib/src/brand-page-maker/composables/rich-text-editor/hyperlink.js +29 -31
- package/lib/src/brand-page-maker/composables/rich-text-editor/keepMarksOnEmpty.d.ts +20 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/keepMarksOnEmpty.js +60 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/spacing.d.ts +2 -1
- package/lib/src/brand-page-maker/composables/rich-text-editor/spacing.js +22 -6
- package/lib/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.d.ts +10 -4
- package/lib/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.js +319 -275
- package/lib/src/brand-page-maker/constants/config-settings-constant.js +1 -5
- package/lib/src/brand-page-maker/convertors/text-css-style-options.convertor.js +6 -2
- package/lib/src/brand-page-maker/convertors/text-size-value.convertor.d.ts +1 -1
- package/lib/src/brand-page-maker/convertors/text-size-value.convertor.js +3 -1
- package/lib/src/brand-page-maker/default-getters/payment-text-and-border-color.default-getter.js +7 -14
- package/lib/src/brand-page-maker/default-getters/text-color.default-getter.js +6 -13
- package/lib/src/brand-page-maker/models/content-settings.d.ts +8 -0
- package/lib/src/brand-page-maker/models/content-settings.js +112 -81
- package/lib/src/brand-page-maker/stores/admin-logo-settings/admin-logo-settings-store-utils.d.ts +2 -2
- package/lib/src/brand-page-maker/stores/admin-logo-settings/admin-logo-settings-store-utils.js +5 -2
- package/lib/src/brand-page-maker/stores/admin-logo-settings/admin-logo-settings-store.js +4 -3
- package/lib/src/brand-page-maker/stores/ai-checkpoint/ai-checkpoint-store.d.ts +1257 -117
- package/lib/src/brand-page-maker/stores/ai-thread/ai-website-builder-thread-store.d.ts +9 -2
- package/lib/src/brand-page-maker/stores/ai-thread/ai-website-builder-thread-store.js +20 -0
- package/lib/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.d.ts +21 -0
- package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.d.ts +6 -1
- package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.js +17 -9
- package/lib/src/brand-page-maker/stores/history/history-store.d.ts +59 -5
- package/lib/src/brand-page-maker/stores/history/history-store.js +22 -2
- package/lib/src/brand-page-maker/utils/apply-color-strategy.js +26 -8
- package/lib/src/brand-page-maker/utils/index.d.ts +2 -1
- package/lib/src/brand-page-maker/utils/index.js +2 -1
- package/lib/src/brand-page-maker/utils/rich-text-editor.util.d.ts +117 -15
- package/lib/src/brand-page-maker/utils/rich-text-editor.util.js +435 -281
- package/lib/src/brand-page-maker/utils/rte-authoring.util.d.ts +4 -0
- package/lib/src/brand-page-maker/utils/rte-authoring.util.js +101 -0
- package/lib/src/brand-page-maker/utils/rte-clipboard.util.d.ts +126 -0
- package/lib/src/brand-page-maker/utils/rte-clipboard.util.js +307 -0
- package/lib/src/brand-page-maker/utils/rte-sanitize.util.js +13 -3
- package/lib/src/{bundled-translations-zfiA5Tsw.js → bundled-translations-yGrvPE5i.js} +17 -17
- package/lib/src/{bundled-translations.de-DE-ChsdSyAP.js → bundled-translations.de-DE-L4Znf8Hs.js} +17 -17
- package/lib/src/{bundled-translations.es-ES-DflsWQK0.js → bundled-translations.es-ES-D6iticrK.js} +15 -15
- package/lib/src/{bundled-translations.fr-CA-fpDRuZ52.js → bundled-translations.fr-CA-BgAVE-9k.js} +25 -25
- package/lib/src/{bundled-translations.fr-FR-Bub73Zt-.js → bundled-translations.fr-FR-B8GbyqxQ.js} +17 -17
- package/lib/src/{bundled-translations.pt-BR-BlnWE9TB.js → bundled-translations.pt-BR-DjPU3Db7.js} +25 -25
- package/lib/src/{bundled-translations.pt-PT-DmzXlnaS.js → bundled-translations.pt-PT-BaepaDpr.js} +23 -23
- package/lib/src/bundles/bundled-translations.de-DE.json +21 -2
- package/lib/src/bundles/bundled-translations.es-ES.json +21 -2
- package/lib/src/bundles/bundled-translations.fr-CA.json +21 -2
- package/lib/src/bundles/bundled-translations.fr-FR.json +21 -2
- package/lib/src/bundles/bundled-translations.json +21 -2
- package/lib/src/bundles/bundled-translations.pt-BR.json +21 -2
- package/lib/src/bundles/bundled-translations.pt-PT.json +21 -2
- package/lib/src/clients/ai-assistant-client.d.ts +7 -1
- package/lib/src/clients/ai-assistant-client.js +7 -15
- package/lib/src/composables/useTranslate.d.ts +1 -1
- package/lib/src/composables/useTranslate.js +1 -1
- package/lib/src/css/library.brand-page-brandCrowd.min.css +284 -188
- package/lib/src/css/library.brand-page-brandPage.min.css +284 -188
- package/lib/src/css/library.brand-page-designCom.min.css +284 -188
- package/lib/src/css/library.brand-page-preview.min.css +284 -188
- package/lib/src/index.d.ts +2 -1
- package/lib/src/index.mjs +195 -191
- package/lib/src/{lib-7gtW85nE.js → lib-kGRlD3_P.js} +39082 -36131
- package/lib/src/lib.js +2 -1
- package/lib/src/metadata-builder/builders/footer-builder.js +4 -5
- package/lib/src/metadata-builder/builders/global-role-propagation.d.ts +58 -0
- package/lib/src/metadata-builder/builders/global-role-propagation.js +246 -0
- package/lib/src/metadata-builder/builders/global-styles-builder.d.ts +1 -0
- package/lib/src/metadata-builder/builders/global-styles-builder.js +116 -45
- package/lib/src/metadata-builder/builders/header-builder.js +13 -4
- package/lib/src/metadata-builder/builders/section-builders/about-section-builder.js +3 -3
- package/lib/src/metadata-builder/builders/section-builders/banner-section-builder.js +3 -3
- package/lib/src/metadata-builder/builders/section-builders/booking-section-builder.js +3 -3
- package/lib/src/metadata-builder/builders/section-builders/contact-form-builder.js +4 -4
- package/lib/src/metadata-builder/builders/section-builders/images-section-builder.js +3 -3
- package/lib/src/metadata-builder/builders/section-builders/mailing-list-builder.js +2 -2
- package/lib/src/metadata-builder/builders/section-builders/mailing-list-section-builder.js +2 -2
- package/lib/src/metadata-builder/builders/section-builders/profile-section-builder.js +4 -4
- package/lib/src/metadata-builder/builders/section-builders/shapes-section-builder.js +3 -3
- package/lib/src/metadata-builder/builders/section-builders/social-links-builder.js +8 -2
- package/lib/src/metadata-builder/builders/section-builders/testimonial-section-builder.js +2 -2
- package/lib/src/metadata-builder/builders/section-builders/text-section-builder.js +60 -29
- package/lib/src/metadata-builder/builders/section-field-maps.js +1 -0
- package/lib/src/metadata-builder/builders/section-field-utils.d.ts +48 -19
- package/lib/src/metadata-builder/builders/section-field-utils.js +128 -52
- package/lib/src/metadata-builder/builders/section-rules/global-styles.rules.d.ts +4 -2
- package/lib/src/metadata-builder/builders/section-rules/global-styles.rules.js +28 -12
- package/lib/src/metadata-builder/builders/section-rules/social-links.rules.js +5 -1
- package/lib/src/metadata-builder/helpers/rte.helpers.d.ts +1 -1
- package/lib/src/metadata-builder/helpers/rte.helpers.js +97 -40
- package/lib/src/metadata-builder/metadata-executor.js +23 -1
- package/lib/src/metadata-builder/models.d.ts +2 -0
- package/lib/src/metadata-builder/parsers/ai-toolcall-parser.d.ts +18 -19
- package/lib/src/metadata-builder/parsers/section-tool-arguments.d.ts +18 -1
- package/lib/src/metadata-builder/sanitizers/rte-schema-check.js +1 -1
- package/lib/src/metadata-builder/sanitizers/sanitizer-rules.d.ts +0 -1
- package/lib/src/metadata-builder/sanitizers/sanitizer-rules.js +5 -8
- package/lib/src/metadata-builder/sanitizers/section-args-validator.d.ts +7 -16
- package/lib/src/metadata-builder/sanitizers/section-args-validator.js +95 -22
- package/lib/src/shared/color-palette/section-color-matches.js +9 -25
- package/lib/src/shared/color-strategies/color-strategies.d.ts +15 -0
- package/lib/src/shared/color-strategies/color-strategies.js +31 -0
- package/lib/src/shared/color-strategies/rte-color.d.ts +8 -0
- package/lib/src/shared/color-strategies/rte-color.js +16 -0
- package/lib/src/shared/color-strategies/rte-field-pairs.d.ts +23 -0
- package/lib/src/shared/color-strategies/rte-field-pairs.js +28 -0
- package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.d.ts +9 -135
- package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.js +1 -27
- package/lib/src/shared/content-settings/components/content-settings/ReplaceImageSettingsForContent.mixin.d.ts +12 -2
- package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItem.mixin.d.ts +7 -0
- package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.d.ts +75 -6
- package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.js +18 -7
- package/lib/src/shared/content-settings/components/focused-content-item-settings/FocusedContentItemSettings.mixin.d.ts +6 -1
- package/lib/src/shared/content-settings/components/focused-content-item-settings/FocusedContentItemSettings.mixin.js +0 -7
- package/lib/src/shared/content-settings/components/payment-config-field/PaymentConfigField.mixin.d.ts +22 -0
- package/lib/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.js +8 -4
- package/lib/src/shared/content-settings/components/text-style-field/TextStyleField.mixin.d.ts +1255 -26
- package/lib/src/shared/content-settings/components/text-style-field/TextStyleField.mixin.js +203 -59
- package/lib/src/shared/content-settings/store/content-settings-store.d.ts +6 -0
- package/lib/src/shared/content-settings/store/content-settings-store.js +105 -8
- package/lib/src/shared/logo-settings/components/LogoSettings.mixin.d.ts +26 -1
- package/lib/src/shared/navigation-style-settings/store/index.d.ts +7 -3
- package/lib/src/shared/navigation-style-settings/store/index.js +40 -15
- package/lib/src/shared/page-settings/components/new-page-modal/NewPageModal.mixin.d.ts +1 -0
- package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +38 -5
- package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +91 -13
- package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.js +10 -2
- package/lib/src/shared/page-settings/store/page-settings-store.d.ts +2 -1
- package/lib/src/shared/page-settings/store/page-settings-store.js +17 -0
- package/lib/src/shared/publish-brand-page-with-state/PublishBrandPageModalWithState.mixin.d.ts +32 -1
- package/lib/src/shared/rte-extensions/AutoLink.d.ts +17 -0
- package/lib/src/{brand-page-maker/composables/rich-text-editor/extensions → shared/rte-extensions}/AutoLink.js +67 -51
- package/lib/src/{brand-page-maker/composables/rich-text-editor/extensions → shared/rte-extensions}/MaxLength.js +4 -1
- package/lib/src/shared/rte-extensions/TextEffects.d.ts +28 -0
- package/lib/src/shared/rte-extensions/TextEffects.js +96 -0
- package/lib/src/shared/rte-extensions/ViewDomParser.d.ts +2 -0
- package/lib/src/shared/rte-extensions/ViewDomParser.js +21 -0
- package/lib/src/shared/rte-extensions.util.d.ts +25 -0
- package/lib/src/{brand-page-maker/composables/rich-text-editor/getRteExtensions.js → shared/rte-extensions.util.js} +62 -7
- package/lib/src/shared/rte-link.util.d.ts +1 -0
- package/lib/src/shared/rte-link.util.js +1 -0
- package/lib/src/shared/rte-render.util.d.ts +112 -0
- package/lib/src/shared/rte-render.util.js +255 -0
- package/lib/src/shared/rte-shape.util.d.ts +6 -0
- package/lib/src/shared/rte-shape.util.js +18 -0
- package/lib/src/shared/side-bar/components/SideBar.mixin.d.ts +23 -1
- package/lib/src/shared/side-bar/components/SideBarTabButton.mixin.d.ts +1 -0
- package/lib/src/shared/side-bar/store/side-bar-store.d.ts +2 -0
- package/lib/src/shared/side-bar/store/side-bar-store.js +9 -0
- package/lib/src/shared/style-settings/components/StyleSettings.mixin.d.ts +176 -15
- package/lib/src/shared/style-settings/components/StyleSettings.mixin.js +7 -1
- package/lib/src/shared/style-settings/store/style-settings-store.d.ts +1 -9
- package/lib/src/shared/style-settings/store/style-settings-store.js +3 -33
- package/lib/src/shared/style-settings/text-style-size-order.d.ts +2 -0
- package/lib/src/shared/style-settings/text-style-size-order.js +13 -0
- package/lib/src/shared/text-role-typography.util.d.ts +64 -0
- package/lib/src/shared/text-role-typography.util.js +183 -0
- package/lib/src/utils/minify-brand-page-metadata.d.ts +16 -1
- package/lib/src/utils/minify-brand-page-metadata.js +31 -9
- package/package.json +1 -1
- package/src/brand-page/__fixtures__/migration-22-v21/edited-brandsite-sections.v21.json +982 -0
- package/src/brand-page/__fixtures__/migration-22-v21/new-brandcontact-every-section.v21.json +1231 -0
- package/src/brand-page/__fixtures__/migration-22-v21/new-brandpage-every-section.v21.json +1231 -0
- package/src/brand-page/__fixtures__/migration-22-v21/new-brandsite-every-section.v21.json +1243 -0
- package/src/brand-page/__fixtures__/migration-22-v21/real-brandcontact.v21.json +755 -0
- package/src/brand-page/__fixtures__/migration-22-v21/real-brandpage-general-link.v21.json +527 -0
- package/src/brand-page/__fixtures__/migration-22-v21/real-brandpage-links.v21.json +755 -0
- package/src/brand-page/__fixtures__/migration-22-v21/real-brandpage-social-links.v21.json +514 -0
- package/src/brand-page/__fixtures__/migration-22-v21/real-brandsite-about.v21.json +459 -0
- package/src/brand-page/__fixtures__/migration-22-v21/real-brandsite-footer.v21.json +469 -0
- package/src/brand-page/__fixtures__/migration-22-v21/real-brandsite-header-banner.v21.json +481 -0
- package/src/brand-page/__fixtures__/migration-22-v21/real-brandsite-sections.v21.json +800 -0
- package/src/brand-page/__fixtures__/production-text-style-resolver.fixture.ts +52 -0
- package/src/brand-page/components/about-section/AboutSection.vue.test.ts +0 -10
- package/src/brand-page/components/contact-form/ContactForm.mixin.test.ts +127 -0
- package/src/brand-page/components/contact-form/ContactForm.mixin.ts +31 -14
- package/src/brand-page/components/contact-form/ContactForm.vue +26 -12
- package/src/brand-page/components/contact-form/ContactForm.vue.test.ts +60 -0
- package/src/brand-page/components/mailing-list/MailingList.mixin.test.ts +92 -0
- package/src/brand-page/components/mailing-list/MailingList.mixin.ts +28 -3
- package/src/brand-page/components/mailing-list/MailingList.vue +9 -4
- package/src/brand-page/components/mailing-list/MailingList.vue.test.ts +63 -0
- package/src/brand-page/components/navigation/button-carousel/ButtonCarouselNavigation.mixin.ts +51 -1
- package/src/brand-page/components/navigation/button-carousel/ButtonCarouselNavigation.test.ts +114 -0
- package/src/brand-page/components/navigation/button-carousel/ButtonCarouselNavigation.vue +8 -2
- package/src/brand-page/components/navigation/navigation-carousel/NavigationCarousel.test.ts +182 -0
- package/src/brand-page/components/navigation/navigation-carousel/NavigationCarousel.vue +58 -12
- package/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.mixin.ts +40 -3
- package/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.vue +15 -7
- package/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.vue.test.ts +133 -3
- package/src/brand-page/components/page/Page.mixin.ts +41 -34
- package/src/brand-page/components/page/Page.vue +17 -6
- package/src/brand-page/components/page/Page.vue.test.ts +91 -1
- package/src/brand-page/components/page-footer/PageFooter.mixin.ts +32 -2
- package/src/brand-page/components/page-footer/PageFooter.test.ts +79 -0
- package/src/brand-page/components/page-footer/PageFooter.vue +12 -4
- package/src/brand-page/components/payment/Payment.mixin.ts +34 -2
- package/src/brand-page/components/payment/Payment.test.ts +188 -0
- package/src/brand-page/components/payment/Payment.vue +12 -5
- package/src/brand-page/components/payment/RequestPaymentStripe.vue +1 -0
- package/src/brand-page/components/payment/support-me/SupportMe.mixin.ts +49 -2
- package/src/brand-page/components/payment/support-me/SupportMe.test.ts +100 -0
- package/src/brand-page/components/payment/support-me/SupportMe.vue +7 -1
- package/src/brand-page/components/payment/support-me/SupportMe.vue.test.ts +66 -0
- package/src/brand-page/components/save-contact/SaveContactWidget.test.ts +100 -0
- package/src/brand-page/components/save-contact/SaveContactWidget.vue +6 -3
- package/src/brand-page/components/shop/PaymentModal.vue +2 -6
- package/src/brand-page/components/shop/PaymentShippingAddress.vue +6 -0
- package/src/brand-page/components/shop/Shop.vue +15 -2
- package/src/brand-page/components/shop/Shop.vue.test.ts +74 -2
- package/src/brand-page/components/social-links/SocialLinks.stories.ts +0 -3
- package/src/brand-page/components/social-links/SocialLinks.vue.test.ts +0 -3
- package/src/brand-page/components/testimonial-section/__fixtures__/data.ts +0 -3
- package/src/brand-page/components/text/Text.stories.ts +0 -17
- package/src/brand-page/components/text-wrapper/TextWrapper.parity.test.ts +136 -0
- package/src/brand-page/components/text-wrapper/TextWrapper.test.ts +358 -0
- package/src/brand-page/components/text-wrapper/TextWrapper.vue +82 -22
- package/src/brand-page/constants/font.constants.ts +37 -0
- package/src/brand-page/models/brand-page-context.model.ts +13 -0
- package/src/brand-page/models/footer-icon-color.test.ts +22 -0
- package/src/brand-page/models/index.ts +50 -28
- package/src/brand-page/models/navigation-styles.model.test.ts +77 -1
- package/src/brand-page/models/navigation-styles.model.ts +26 -9
- package/src/brand-page/models/payment.model.ts +15 -6
- package/src/brand-page/models/styles.model.test.ts +104 -0
- package/src/brand-page/models/styles.model.ts +181 -47
- package/src/brand-page/utils/__snapshots__/migration.util.test.ts.snap +619 -115
- package/src/brand-page/utils/__snapshots__/migration2.util.test.ts.snap +150 -9
- package/src/brand-page/utils/__snapshots__/model.util.test.ts.snap +300 -18
- package/src/brand-page/utils/color-palette.util.test.ts +126 -1
- package/src/brand-page/utils/color-palette.util.ts +92 -26
- package/src/brand-page/utils/color.util.test.ts +62 -29
- package/src/brand-page/utils/color.util.ts +77 -14
- package/src/brand-page/utils/container-text-color.util.test.ts +672 -26
- package/src/brand-page/utils/container-text-color.util.ts +472 -46
- package/src/brand-page/utils/index.ts +1 -0
- package/src/brand-page/utils/migration/__snapshots__/migration-22-output/edited-brandsite-sections.v22.json +1191 -0
- package/src/brand-page/utils/migration/__snapshots__/migration-22-output/new-brandcontact-every-section.v22.json +2026 -0
- package/src/brand-page/utils/migration/__snapshots__/migration-22-output/new-brandpage-every-section.v22.json +2017 -0
- package/src/brand-page/utils/migration/__snapshots__/migration-22-output/new-brandsite-every-section.v22.json +2031 -0
- package/src/brand-page/utils/migration/__snapshots__/migration-22-output/real-brandcontact.v22.json +1145 -0
- package/src/brand-page/utils/migration/__snapshots__/migration-22-output/real-brandpage-general-link.v22.json +645 -0
- package/src/brand-page/utils/migration/__snapshots__/migration-22-output/real-brandpage-links.v22.json +1136 -0
- package/src/brand-page/utils/migration/__snapshots__/migration-22-output/real-brandpage-social-links.v22.json +662 -0
- package/src/brand-page/utils/migration/__snapshots__/migration-22-output/real-brandsite-about.v22.json +638 -0
- package/src/brand-page/utils/migration/__snapshots__/migration-22-output/real-brandsite-footer.v22.json +610 -0
- package/src/brand-page/utils/migration/__snapshots__/migration-22-output/real-brandsite-header-banner.v22.json +660 -0
- package/src/brand-page/utils/migration/__snapshots__/migration-22-output/real-brandsite-sections.v22.json +1132 -0
- package/src/brand-page/utils/migration/migration-01.ts +293 -237
- package/src/brand-page/utils/migration/migration-02.ts +8 -1
- package/src/brand-page/utils/migration/migration-20.ts +6 -3
- package/src/brand-page/utils/migration/migration-20_5.test.ts +5 -2
- package/src/brand-page/utils/migration/migration-21.test.ts +34 -0
- package/src/brand-page/utils/migration/migration-21.ts +46 -10
- package/src/brand-page/utils/migration/migration-22.fixed-input.test.ts +43 -0
- package/src/brand-page/utils/migration/migration-22.test.ts +4103 -0
- package/src/brand-page/utils/migration/migration-22.text-font-family.test.ts +72 -0
- package/src/brand-page/utils/migration/migration-22.ts +1336 -0
- package/src/brand-page/utils/migration.util.test.ts +146 -32
- package/src/brand-page/utils/migration.util.ts +18 -0
- package/src/brand-page/utils/model.util.ts +4 -0
- package/src/brand-page/utils/personalisation.util.test.ts +148 -3
- package/src/brand-page/utils/personalisation.util.ts +9 -1
- package/src/brand-page/utils/rte-role-lift.util.ts +409 -0
- package/src/brand-page/utils/style.util.test.ts +35 -2
- package/src/brand-page/utils/style.util.ts +45 -11
- package/src/brand-page/utils/text-role-css.util.test.ts +525 -0
- package/src/brand-page/utils/text-role-css.util.ts +321 -0
- package/src/brand-page/utils/text-role-reconnect.util.test.ts +604 -0
- package/src/brand-page/utils/text-role-reconnect.util.ts +727 -0
- package/src/brand-page/utils/text-style.util.test.ts +305 -0
- package/src/brand-page/utils/text-style.util.ts +198 -24
- package/src/brand-page/utils/view-model.page-data.test.ts +232 -0
- package/src/brand-page/utils/view-model.util.test.ts +954 -133
- package/src/brand-page/utils/view-model.util.ts +187 -139
- package/src/brand-page/view-models.ts +4 -0
- package/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.blank-site.test.ts +57 -0
- package/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.ts +9 -5
- package/src/brand-page-maker/components/brand-page-maker/__fixtures__/brand-page-examples.ts +0 -10
- package/src/brand-page-maker/components/brand-page-maker/__fixtures__/content-settings-items.fixture.ts +0 -1
- package/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.ts +0 -44
- package/src/brand-page-maker/components/rich-text-editor/RichTextEditor.vue +253 -200
- package/src/brand-page-maker/composables/ai-chat/ai-builder-experiment.test.ts +82 -0
- package/src/brand-page-maker/composables/ai-chat/ai-builder-experiment.ts +19 -0
- package/src/brand-page-maker/composables/ai-chat/useAiMetadataBuilder.ts +2 -0
- package/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.ts +19 -0
- package/src/brand-page-maker/composables/rich-text-editor/block-typography.test.ts +519 -0
- package/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.test.ts +1 -1
- package/src/brand-page-maker/composables/rich-text-editor/extensions/MaxLength.test.ts +26 -1
- package/src/brand-page-maker/composables/rich-text-editor/extensions/RoleTypographyFill.test.ts +110 -0
- package/src/brand-page-maker/composables/rich-text-editor/extensions/RoleTypographyFill.ts +99 -0
- package/src/brand-page-maker/composables/rich-text-editor/extensions/RteClipboard.ts +397 -0
- package/src/brand-page-maker/composables/rich-text-editor/extensions/ViewDomParser.test.ts +139 -0
- package/src/brand-page-maker/composables/rich-text-editor/font.ts +36 -5
- package/src/brand-page-maker/composables/rich-text-editor/formatting.test.ts +375 -1
- package/src/brand-page-maker/composables/rich-text-editor/formatting.ts +301 -17
- package/src/brand-page-maker/composables/rich-text-editor/hyperlink.test.ts +82 -1
- package/src/brand-page-maker/composables/rich-text-editor/hyperlink.ts +28 -34
- package/src/brand-page-maker/composables/rich-text-editor/keepMarksOnEmpty.test.ts +80 -0
- package/src/brand-page-maker/composables/rich-text-editor/keepMarksOnEmpty.ts +75 -0
- package/src/brand-page-maker/composables/rich-text-editor/spacing.ts +31 -6
- package/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.test.ts +2485 -0
- package/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.ts +435 -337
- package/src/brand-page-maker/constants/config-settings-constant.ts +0 -6
- package/src/brand-page-maker/convertors/text-css-style-options.convertor.test.ts +43 -0
- package/src/brand-page-maker/convertors/text-css-style-options.convertor.ts +6 -2
- package/src/brand-page-maker/convertors/text-size-value.convertor.test.ts +24 -0
- package/src/brand-page-maker/convertors/text-size-value.convertor.ts +17 -13
- package/src/brand-page-maker/default-getters/payment-text-and-border-color.default-getter.ts +7 -22
- package/src/brand-page-maker/default-getters/text-color.default-getter.ts +5 -21
- package/src/brand-page-maker/models/content-settings.test.ts +708 -0
- package/src/brand-page-maker/models/content-settings.ts +133 -82
- package/src/brand-page-maker/stores/admin-logo-settings/admin-logo-settings-store-utils.test.ts +63 -1
- package/src/brand-page-maker/stores/admin-logo-settings/admin-logo-settings-store-utils.ts +16 -3
- package/src/brand-page-maker/stores/admin-logo-settings/admin-logo-settings-store.test.ts +34 -0
- package/src/brand-page-maker/stores/admin-logo-settings/admin-logo-settings-store.ts +4 -2
- package/src/brand-page-maker/stores/ai-thread/ai-website-builder-thread-store.ts +30 -0
- package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.test.ts +185 -1
- package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.ts +22 -34
- package/src/brand-page-maker/stores/history/history-store.ts +35 -2
- package/src/brand-page-maker/utils/__snapshots__/apply-color-strategy.golden.test.ts.snap +528 -0
- package/src/brand-page-maker/utils/__snapshots__/rte-render-parity.test.ts.snap +12 -0
- package/src/brand-page-maker/utils/apply-color-strategy.golden.test.ts +2 -0
- package/src/brand-page-maker/utils/apply-color-strategy.test.ts +181 -3
- package/src/brand-page-maker/utils/apply-color-strategy.ts +36 -4
- package/src/brand-page-maker/utils/index.ts +2 -1
- package/src/brand-page-maker/utils/rich-text-editor.util.test.ts +447 -410
- package/src/brand-page-maker/utils/rich-text-editor.util.ts +610 -506
- package/src/brand-page-maker/utils/rte-authoring.util.test.ts +244 -0
- package/src/brand-page-maker/utils/rte-authoring.util.ts +175 -0
- package/src/brand-page-maker/utils/rte-clipboard.util.test.ts +558 -0
- package/src/brand-page-maker/utils/rte-clipboard.util.ts +473 -0
- package/src/brand-page-maker/utils/rte-features.test.ts +99 -3
- package/src/brand-page-maker/utils/rte-render-parity.test.ts +1 -1
- package/src/brand-page-maker/utils/rte-render.util.test.ts +230 -0
- package/src/brand-page-maker/utils/rte-sanitize-json.test.ts +1 -1
- package/src/brand-page-maker/utils/rte-sanitize.util.ts +14 -3
- package/src/brand-page-maker/utils/rte-schema-version.test.ts +102 -0
- package/src/bundles/bundled-translations.de-DE.json +21 -2
- package/src/bundles/bundled-translations.es-ES.json +21 -2
- package/src/bundles/bundled-translations.fr-CA.json +21 -2
- package/src/bundles/bundled-translations.fr-FR.json +21 -2
- package/src/bundles/bundled-translations.json +21 -2
- package/src/bundles/bundled-translations.pt-BR.json +21 -2
- package/src/bundles/bundled-translations.pt-PT.json +21 -2
- package/src/clients/ai-assistant-client.ts +13 -18
- package/src/composables/useTranslate.ts +2 -1
- package/src/lib.ts +2 -2
- package/src/metadata-builder/builders/footer-builder.test.ts +70 -45
- package/src/metadata-builder/builders/footer-builder.ts +4 -8
- package/src/metadata-builder/builders/global-role-propagation.test.ts +907 -0
- package/src/metadata-builder/builders/global-role-propagation.ts +430 -0
- package/src/metadata-builder/builders/global-styles-builder.test.ts +1020 -0
- package/src/metadata-builder/builders/global-styles-builder.ts +194 -48
- package/src/metadata-builder/builders/header-builder.test.ts +112 -40
- package/src/metadata-builder/builders/header-builder.ts +18 -8
- package/src/metadata-builder/builders/section-builders/about-section-builder.ts +5 -3
- package/src/metadata-builder/builders/section-builders/banner-section-builder.ts +5 -3
- package/src/metadata-builder/builders/section-builders/booking-section-builder.ts +5 -3
- package/src/metadata-builder/builders/section-builders/contact-form-builder.ts +7 -4
- package/src/metadata-builder/builders/section-builders/images-section-builder.ts +5 -3
- package/src/metadata-builder/builders/section-builders/mailing-list-builder.ts +3 -2
- package/src/metadata-builder/builders/section-builders/mailing-list-section-builder.ts +3 -2
- package/src/metadata-builder/builders/section-builders/profile-section-builder.ts +7 -7
- package/src/metadata-builder/builders/section-builders/shapes-section-builder.ts +5 -3
- package/src/metadata-builder/builders/section-builders/social-links-builder.test.ts +2 -2
- package/src/metadata-builder/builders/section-builders/social-links-builder.ts +21 -10
- package/src/metadata-builder/builders/section-builders/support-me-builder.test.ts +10 -0
- package/src/metadata-builder/builders/section-builders/testimonial-section-builder.ts +3 -2
- package/src/metadata-builder/builders/section-builders/text-section-builder.ts +111 -61
- package/src/metadata-builder/builders/section-field-maps.ts +1 -0
- package/src/metadata-builder/builders/section-field-utils.test.ts +545 -2
- package/src/metadata-builder/builders/section-field-utils.ts +202 -69
- package/src/metadata-builder/builders/section-rules/global-styles.rules.ts +38 -13
- package/src/metadata-builder/builders/section-rules/social-links.rules.ts +5 -0
- package/src/metadata-builder/helpers/rte.helpers.test.ts +101 -0
- package/src/metadata-builder/helpers/rte.helpers.ts +129 -43
- package/src/metadata-builder/metadata-executor.test.ts +167 -0
- package/src/metadata-builder/metadata-executor.ts +34 -1
- package/src/metadata-builder/models.ts +2 -0
- package/src/metadata-builder/parsers/ai-toolcall-parser.ts +23 -7
- package/src/metadata-builder/parsers/section-tool-arguments.ts +21 -1
- package/src/metadata-builder/sanitizers/__tests__/section-args-validator.test.ts +215 -0
- package/src/metadata-builder/sanitizers/rte-schema-check.ts +1 -1
- package/src/metadata-builder/sanitizers/sanitizer-rules.ts +4 -8
- package/src/metadata-builder/sanitizers/section-args-validator.ts +136 -53
- package/src/shared/color-palette/section-color-matches.test.ts +15 -11
- package/src/shared/color-palette/section-color-matches.ts +9 -28
- package/src/shared/color-strategies/color-strategies.ts +35 -0
- package/src/shared/color-strategies/rte-color.test.ts +1 -1
- package/src/shared/color-strategies/rte-color.ts +19 -0
- package/src/shared/color-strategies/rte-field-pairs.test.ts +26 -5
- package/src/shared/color-strategies/rte-field-pairs.ts +33 -0
- package/src/shared/content-settings/components/color-matches-panel/ColorMatchesPanel.test.ts +43 -66
- package/src/shared/content-settings/components/color-matches-panel/ColorMatchesPanel.vue +7 -1
- package/src/shared/content-settings/components/content-settings/ContentSettings.mixin.ts +1 -48
- package/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.ts +33 -7
- package/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.test.ts +128 -1
- package/src/shared/content-settings/components/focused-content-item-settings/FocusedContentItemSettings.mixin.ts +0 -6
- package/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.test.ts +56 -3
- package/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.ts +11 -4
- package/src/shared/content-settings/components/text-style-field/TextStyleField.mixin.test.ts +335 -25
- package/src/shared/content-settings/components/text-style-field/TextStyleField.mixin.ts +248 -70
- package/src/shared/content-settings/components/text-style-field/TextStyleField.vue +113 -25
- package/src/shared/content-settings/components/text-style-field/i18n/text-style-field.de-DE.json +17 -10
- package/src/shared/content-settings/components/text-style-field/i18n/text-style-field.es-ES.json +17 -10
- package/src/shared/content-settings/components/text-style-field/i18n/text-style-field.fr-CA.json +17 -10
- package/src/shared/content-settings/components/text-style-field/i18n/text-style-field.fr-FR.json +17 -10
- package/src/shared/content-settings/components/text-style-field/i18n/text-style-field.json +8 -1
- package/src/shared/content-settings/components/text-style-field/i18n/text-style-field.pt-BR.json +17 -10
- package/src/shared/content-settings/components/text-style-field/i18n/text-style-field.pt-PT.json +17 -10
- package/src/shared/content-settings/components/text-style-role-dropdown/TextStyleRoleDropdown.test.ts +141 -0
- package/src/shared/content-settings/components/text-style-role-dropdown/TextStyleRoleDropdown.vue +136 -0
- package/src/shared/content-settings/store/content-settings-store.test.ts +491 -6
- package/src/shared/content-settings/store/content-settings-store.ts +159 -17
- package/src/shared/navigation-style-settings/store/index.test.ts +295 -0
- package/src/shared/navigation-style-settings/store/index.ts +65 -15
- package/src/shared/page-settings/components/page-settings-section-styles/HeaderButtonSettings.test.ts +44 -4
- package/src/shared/page-settings/components/page-settings-section-styles/HeaderButtonSettings.vue +13 -4
- package/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.navigation.test.ts +89 -0
- package/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.text-style.test.ts +91 -0
- package/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.ts +14 -2
- package/src/shared/page-settings/store/page-settings-store.text-style.test.ts +84 -0
- package/src/shared/page-settings/store/page-settings-store.ts +30 -1
- package/src/shared/rte-extensions/AutoLink.test.ts +98 -0
- package/src/{brand-page-maker/composables/rich-text-editor/extensions → shared/rte-extensions}/AutoLink.ts +87 -58
- package/src/{brand-page-maker/composables/rich-text-editor/extensions → shared/rte-extensions}/MaxLength.ts +4 -1
- package/src/shared/rte-extensions/TextEffects.ts +151 -0
- package/src/shared/rte-extensions/ViewDomParser.ts +27 -0
- package/src/{brand-page-maker/composables/rich-text-editor/getRteExtensions.ts → shared/rte-extensions.util.ts} +66 -9
- package/src/shared/rte-link.util.ts +2 -0
- package/src/shared/rte-render.util.ts +385 -0
- package/src/shared/rte-shape.util.ts +29 -0
- package/src/shared/side-bar/store/side-bar-store.test.ts +27 -1
- package/src/shared/side-bar/store/side-bar-store.ts +11 -0
- package/src/shared/style-settings/components/StyleSettings.mixin.ts +7 -3
- package/src/shared/style-settings/components/StyleSettings.test.ts +116 -0
- package/src/shared/style-settings/components/StyleSettings.vue +74 -25
- package/src/shared/style-settings/components/i18n/style-settings.de-DE.json +32 -20
- package/src/shared/style-settings/components/i18n/style-settings.es-ES.json +32 -20
- package/src/shared/style-settings/components/i18n/style-settings.fr-CA.json +32 -20
- package/src/shared/style-settings/components/i18n/style-settings.fr-FR.json +32 -20
- package/src/shared/style-settings/components/i18n/style-settings.json +13 -3
- package/src/shared/style-settings/components/i18n/style-settings.pt-BR.json +32 -20
- package/src/shared/style-settings/components/i18n/style-settings.pt-PT.json +32 -20
- package/src/shared/style-settings/components/text-style-options-settings-with-state/TextStyleOptionsSettingsWithState.vue +14 -34
- package/src/shared/style-settings/store/style-settings-store.ts +3 -49
- package/src/shared/style-settings/text-style-size-order.test.ts +76 -0
- package/src/shared/style-settings/text-style-size-order.ts +28 -0
- package/src/shared/text-role-typography.util.ts +277 -0
- package/src/utils/minify-brand-page-metadata.test.ts +102 -7
- package/src/utils/minify-brand-page-metadata.ts +62 -8
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/AutoLink.d.ts +0 -2
- package/lib/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.d.ts +0 -28
- 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 -106
- package/lib/src/brand-page-maker/utils/rte-shape.d.ts +0 -18
- package/lib/src/brand-page-maker/utils/rte-shape.js +0 -21
- package/lib/src/metadata-builder/builders/global-font-propagation.d.ts +0 -41
- package/lib/src/metadata-builder/builders/global-font-propagation.js +0 -146
- package/src/brand-page/models/brand-page-context.model.test.ts +0 -38
- package/src/brand-page-maker/utils/rte-render.util.ts +0 -179
- package/src/brand-page-maker/utils/rte-shape.ts +0 -29
- package/src/clients/ai-assistant-client.test.ts +0 -135
- package/src/metadata-builder/builders/global-font-propagation.test.ts +0 -401
- package/src/metadata-builder/builders/global-font-propagation.ts +0 -238
- package/src/shared/style-settings/components/StyleSettingsSection.vue +0 -8
- /package/lib/src/{brand-page-maker/composables/rich-text-editor/extensions → shared/rte-extensions}/FontFamily.d.ts +0 -0
- /package/lib/src/{brand-page-maker/composables/rich-text-editor/extensions → shared/rte-extensions}/FontFamily.js +0 -0
- /package/lib/src/{brand-page-maker/composables/rich-text-editor/extensions → shared/rte-extensions}/LetterSpacing.d.ts +0 -0
- /package/lib/src/{brand-page-maker/composables/rich-text-editor/extensions → shared/rte-extensions}/LetterSpacing.js +0 -0
- /package/lib/src/{brand-page-maker/composables/rich-text-editor/extensions → shared/rte-extensions}/MaxLength.d.ts +0 -0
- /package/src/{brand-page-maker/composables/rich-text-editor/extensions → shared/rte-extensions}/FontFamily.ts +0 -0
- /package/src/{brand-page-maker/composables/rich-text-editor/extensions → shared/rte-extensions}/LetterSpacing.ts +0 -0
|
@@ -0,0 +1,1336 @@
|
|
|
1
|
+
import type { MigrationData, MigrationOptions } from '../migration.util';
|
|
2
|
+
import {
|
|
3
|
+
BRAND_PAGE_TYPES,
|
|
4
|
+
type BrandPageModel,
|
|
5
|
+
type HeaderButton,
|
|
6
|
+
type NavOrder,
|
|
7
|
+
type PageModel,
|
|
8
|
+
type TextStyleDeclaration,
|
|
9
|
+
} from '../../models';
|
|
10
|
+
import {
|
|
11
|
+
TEXT_STYLE_TYPES,
|
|
12
|
+
isCanonicalTextStyleType,
|
|
13
|
+
isLegacyTextStyleType,
|
|
14
|
+
type TextStyleOptions,
|
|
15
|
+
type TextStyleType,
|
|
16
|
+
type LegacyTextStyleType,
|
|
17
|
+
} from '../../models/styles.model';
|
|
18
|
+
import {
|
|
19
|
+
CONTENT_TEXT_STYLE_PROPERTY_KEYS,
|
|
20
|
+
type ContentTextStylePropertyKey,
|
|
21
|
+
FONT_FAMILY_CONFIGS,
|
|
22
|
+
DEFAULT_FONT_FAMILY_KEY,
|
|
23
|
+
FONT_SIZE_BODY,
|
|
24
|
+
LINE_HEIGHT_BODY,
|
|
25
|
+
ALIGN_LEFT_ALIGNED,
|
|
26
|
+
ALIGN_RIGHT_ALIGNED,
|
|
27
|
+
MAX_IMAGES_SECTION_ALIGNMENT_ITEMS_LENGTH,
|
|
28
|
+
type FontFamilyKey,
|
|
29
|
+
} from '../../constants';
|
|
30
|
+
import {
|
|
31
|
+
computeLegacyTargets,
|
|
32
|
+
reconnectTextStyleRoles,
|
|
33
|
+
contentFieldReconnectSpec,
|
|
34
|
+
drawnFontFamily,
|
|
35
|
+
siteTextFontName,
|
|
36
|
+
FAMILY_TIERS,
|
|
37
|
+
type LegacyTargetMap,
|
|
38
|
+
type TextRoleFamily,
|
|
39
|
+
type RenderedDefaultsForContent,
|
|
40
|
+
} from '../text-role-reconnect.util';
|
|
41
|
+
import {
|
|
42
|
+
pairedRteTextField,
|
|
43
|
+
rteStyleFieldsFor,
|
|
44
|
+
} from '@shared/color-strategies/rte-field-pairs';
|
|
45
|
+
import {
|
|
46
|
+
containerTextColorDefaults,
|
|
47
|
+
titleSurfaceContrastFill,
|
|
48
|
+
} from '../container-text-color.util';
|
|
49
|
+
import {
|
|
50
|
+
stripLiftContentRte,
|
|
51
|
+
textRunFontFamilies,
|
|
52
|
+
} from '../rte-role-lift.util';
|
|
53
|
+
import {
|
|
54
|
+
collectRteTextColors,
|
|
55
|
+
fillUncolouredRteText,
|
|
56
|
+
} from '@shared/color-strategies/rte-color';
|
|
57
|
+
import { isBlankColorValue } from '../color-palette.util';
|
|
58
|
+
import {
|
|
59
|
+
plainTextToRteJson,
|
|
60
|
+
resolveHtmlParser,
|
|
61
|
+
isTiptapDocument,
|
|
62
|
+
type HtmlParser,
|
|
63
|
+
} from '@shared/rte-render.util';
|
|
64
|
+
import type { JSONContent } from '@tiptap/vue-3';
|
|
65
|
+
|
|
66
|
+
// SNAPSHOT DEPENDENCY NOTICE: B/C/D import live shared helpers above. Their source modules carry
|
|
67
|
+
// `MIGRATION-22 SNAPSHOT DEPENDENCY` notices; once v22 ships, copy the v22 semantics here before
|
|
68
|
+
// changing any of them. B1 already owns an inline snapshot of its typography fill.
|
|
69
|
+
// `migration-22.fixed-input.test.ts` fails when a helper change moves this migration's output.
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* BP-5969 migration-22 — the single-source text-style migration. Version-gated: runs
|
|
73
|
+
* once when `v < 22`. The durable decisions are `0019-retire-rte-object-style-sync.md`,
|
|
74
|
+
* `0020-role-is-typography-only-colour-is-contrast-derived.md`, and
|
|
75
|
+
* `0021-migrated-content-reconnects-to-nearest-role-with-override.md`.
|
|
76
|
+
*
|
|
77
|
+
* A/B/C are appearance-preserving. D/E/G finish the ADR-0020 colour decouple in the same
|
|
78
|
+
* (still-unreleased) migration — they too preserve appearance, by freezing each element's
|
|
79
|
+
* currently-resolved colour before the role colour and base coat that fed it are removed.
|
|
80
|
+
* One decided exception (ADR-0020, Update 2026-09-29): a social-links section with no base
|
|
81
|
+
* coat to freeze bakes its icon fill to the title's contrast colour instead — a new colour,
|
|
82
|
+
* not a literal freeze of whatever `currentColor` happened to paint under `:root`.
|
|
83
|
+
* A. Seed each old role's colour onto its target tier (D3 base coat) — `seedRoleColours`.
|
|
84
|
+
* Roles keep their §4 size, weight and line height; the old size is preserved as a per-block
|
|
85
|
+
* override by B. The legacy → tier map is computed per-site by `computeLegacyTargets`.
|
|
86
|
+
* A2. (R16) Count the font the site's title/heading, description/paragraph and button text shows,
|
|
87
|
+
* and give each family the winner — each family's font comes from the site's text, not §4 —
|
|
88
|
+
* before B and C link equal text to it — `seedRoleFonts`.
|
|
89
|
+
* B. Seed the absent header-button / brand.contact Save Contact styles (they rendered as
|
|
90
|
+
* `buttons`), then reconnect every text field outside the ten — content, text items, footer
|
|
91
|
+
* copyright, nav, header button, Save Contact — to its nearest role, keeping its look as
|
|
92
|
+
* overrides — `reconnectContentRoles` (the shared `text-role-reconnect.util`). The three old
|
|
93
|
+
* no-role renderer defaults (mailing-list title, Support Me title, text block) are materialised
|
|
94
|
+
* here only, before reconnect persists their effective values; no runtime reader keeps them. A colourless nav
|
|
95
|
+
* link's colour is baked here, not in D: it comes from the legacy role, which B replaces. A
|
|
96
|
+
* payment or shop success message with no role of its own gets the look it drew
|
|
97
|
+
* (`seedSuccessMessageStyle`); D then bakes its colour, like any non-RTE style.
|
|
98
|
+
* C. Baked→override strip/lift on RTE fields (D4) — `stripLiftContentRte`; per field it also folds
|
|
99
|
+
* legacy bold/italic/underline/strike marks into the run's textStyle attrs (`foldSemanticFormattingMarks`)
|
|
100
|
+
* and pins a not-bold legacy block's weight so a bold heading tier can't bold it (`pinLegacyRunWeight`).
|
|
101
|
+
* B1. Convert plain-text RTE-eligible fields to Tiptap docs — `convertPlainRteFields`.
|
|
102
|
+
* D. Bake each element's resolved colour explicitly, social-links icon fills included —
|
|
103
|
+
* `bakeElementColours`.
|
|
104
|
+
* E. Clear the retired role colour — `clearRoleColours`.
|
|
105
|
+
* G. Colour-decouple RTE-paired style objects: remove `color` only — `stripRteStyleColours`.
|
|
106
|
+
* H. Delete the retired site text font and its footer copies — `deleteTextFontFamily`
|
|
107
|
+
* (`0036-retire-text-font-family.md`). A2 and B read it through `siteTextFontName`.
|
|
108
|
+
*
|
|
109
|
+
* Ordering is load-bearing: D reads the base coat off `paragraph1` and the role colours (which A
|
|
110
|
+
* seeds from the old roles), so it must run after A and before E; B must run before C and D, which
|
|
111
|
+
* read each field's reconnected role and pinned typography; G removes the RTE style object's
|
|
112
|
+
* colour, so it must run after D has baked that colour into the mark. B1 runs after C (so C's
|
|
113
|
+
* per-run typography bake sees real editor-baked docs, not a freshly-converted placeholder) and
|
|
114
|
+
* before D (so D colours the converted runs like any other doc). A2 runs before B, C, B1, which
|
|
115
|
+
* read the role's font: on the untouched model, so it counts what a legacy-role or no-role field
|
|
116
|
+
* showed before B relinks it, and links B and C to bake against the winner it seeds. H runs last,
|
|
117
|
+
* after every reader of the site text font.
|
|
118
|
+
*
|
|
119
|
+
* Version-gated (v only moves forward), so this runs exactly once. A hypothetical re-run is a
|
|
120
|
+
* no-op throughout: B (seed + reconnect), C (strip + weight pin) and B1 (convert) find every button
|
|
121
|
+
* style present and every field on one of the ten, no baked role-equal typography, every bare run
|
|
122
|
+
* already weight-pinned and no plain strings left; D/E/G find every element already coloured, role
|
|
123
|
+
* colours already cleared and RTE style objects already colourless; H finds nothing to delete; and
|
|
124
|
+
* A (`seedRoleColours`) finds nothing to copy — E already cleared the legacy roles' colour
|
|
125
|
+
* (persisted), so the tiers keep their §4 typography untouched. A2 re-counts what the migrated
|
|
126
|
+
* site shows, the same fonts, so it picks the same winners — also a no-op. Unreachable behind the
|
|
127
|
+
* gate; documented here per the migration rules.
|
|
128
|
+
*/
|
|
129
|
+
|
|
130
|
+
export { computeLegacyTargets };
|
|
131
|
+
export {
|
|
132
|
+
stripLiftRteDoc,
|
|
133
|
+
foldSemanticFormattingMarks,
|
|
134
|
+
pinLegacyRunWeight,
|
|
135
|
+
stripLiftContentRte,
|
|
136
|
+
} from '../rte-role-lift.util';
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Seed each old legacy role's colour onto its target tier — COLOUR ONLY; the tier keeps its
|
|
140
|
+
* §4 default typography. `bakeElementColours` (D) freezes each element's colour by reading its
|
|
141
|
+
* tier, and the base coat off `paragraph1`, so a legacy block's colour is preserved exactly as
|
|
142
|
+
* long as the tier it links to carries the old role's colour. `clearRoleColours` (E) then wipes
|
|
143
|
+
* the seed once D has read it — the tier ends typography-only, per ADR-0020.
|
|
144
|
+
*/
|
|
145
|
+
export const seedRoleColours = (
|
|
146
|
+
options: TextStyleOptions,
|
|
147
|
+
targets: LegacyTargetMap,
|
|
148
|
+
): void => {
|
|
149
|
+
for (const legacy of Object.keys(targets) as LegacyTextStyleType[]) {
|
|
150
|
+
const source = options[legacy]?.style as unknown as
|
|
151
|
+
| Record<string, unknown>
|
|
152
|
+
| undefined;
|
|
153
|
+
const colour = source?.color;
|
|
154
|
+
|
|
155
|
+
if (colour === undefined || colour === null || colour === '') continue;
|
|
156
|
+
|
|
157
|
+
const target = options[targets[legacy]]?.style as unknown as
|
|
158
|
+
| Record<string, unknown>
|
|
159
|
+
| undefined;
|
|
160
|
+
|
|
161
|
+
// The canonical target normally pre-exists (the model constructor seeds all 10); guard
|
|
162
|
+
// defensively for pre-10-role metadata rather than assume it.
|
|
163
|
+
if (target) target.color = colour;
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// An absent header-button or brand.contact Save Contact style rendered as `buttons`. Seeding
|
|
168
|
+
// it lets the reconnect move it to `button` with that look kept as overrides.
|
|
169
|
+
const seedAbsentButtonStyles = (brandPageModel: BrandPageModel): void => {
|
|
170
|
+
for (const header of [
|
|
171
|
+
brandPageModel.pageHeaderDesktop,
|
|
172
|
+
brandPageModel.pageHeaderMobile,
|
|
173
|
+
]) {
|
|
174
|
+
const headerButton = header?.headerButton;
|
|
175
|
+
|
|
176
|
+
if (headerButton) {
|
|
177
|
+
headerButton.buttonTextStyle ??= {
|
|
178
|
+
textStyleType: TEXT_STYLE_TYPES.buttons,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (brandPageModel.brandPageType !== BRAND_PAGE_TYPES.brandContact) return;
|
|
184
|
+
|
|
185
|
+
// The same defaults the view model and the maker store apply when none are saved.
|
|
186
|
+
brandPageModel.saveContactSettings ??= {
|
|
187
|
+
isSavingEnabled: true,
|
|
188
|
+
hideBranding: false,
|
|
189
|
+
};
|
|
190
|
+
brandPageModel.saveContactSettings.buttonTextStyle ??= {
|
|
191
|
+
textStyleType: TEXT_STYLE_TYPES.buttons,
|
|
192
|
+
};
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// ── R16 — job A2: role fonts on upgrade ───────────────────────────────────
|
|
196
|
+
|
|
197
|
+
const LATO = FONT_FAMILY_CONFIGS[DEFAULT_FONT_FAMILY_KEY].name;
|
|
198
|
+
|
|
199
|
+
// Every display NAME a stored font can resolve to — built once, not re-scanned per vote.
|
|
200
|
+
const FONT_FAMILY_NAMES = new Set(
|
|
201
|
+
Object.values(FONT_FAMILY_CONFIGS).map((config) => config.name),
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
// A stored font counts by its display NAME: a key spelling ('nunito') counts as its name
|
|
205
|
+
// ('Nunito'); a font outside the list casts no vote (Q11) and the stored override survives
|
|
206
|
+
// (B/C compare strictly, so a key-spelled override never drops).
|
|
207
|
+
const voteFontName = (fontFamily: string | undefined): string | undefined => {
|
|
208
|
+
if (!fontFamily) return undefined;
|
|
209
|
+
|
|
210
|
+
const byKey = FONT_FAMILY_CONFIGS[fontFamily as FontFamilyKey]?.name;
|
|
211
|
+
|
|
212
|
+
if (byKey) return byKey;
|
|
213
|
+
|
|
214
|
+
return FONT_FAMILY_NAMES.has(fontFamily) ? fontFamily : undefined;
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// One tally per family. A `Map` keeps first-insertion order, and a later count never overtakes an
|
|
218
|
+
// EQUAL earlier one, so the winner scan gives the tie to whichever font was first voted for
|
|
219
|
+
// (migration-21's first-seen rule, D11).
|
|
220
|
+
class FontTally {
|
|
221
|
+
private readonly votes = new Map<string, number>();
|
|
222
|
+
|
|
223
|
+
add(fontFamily: string | undefined): void {
|
|
224
|
+
const name = voteFontName(fontFamily);
|
|
225
|
+
|
|
226
|
+
if (!name) return;
|
|
227
|
+
|
|
228
|
+
this.votes.set(name, (this.votes.get(name) ?? 0) + 1);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
winner(defaultFont: string): string {
|
|
232
|
+
let winner = defaultFont;
|
|
233
|
+
let max = 0;
|
|
234
|
+
|
|
235
|
+
for (const [font, count] of this.votes) {
|
|
236
|
+
if (count > max) {
|
|
237
|
+
max = count;
|
|
238
|
+
winner = font;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return winner;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Depth-first through the nav from its home page (`orders[0]`), then any page with no route, in
|
|
247
|
+
// its stored order — `page-settings-store.ts` `syncPagesWithOrders`'s algorithm, read-only here.
|
|
248
|
+
const sitePageOrder = (brandPageModel: BrandPageModel): PageModel[] => {
|
|
249
|
+
const routes = brandPageModel.nav?.routes ?? [];
|
|
250
|
+
const pageIds: string[] = [];
|
|
251
|
+
|
|
252
|
+
const collect = (orders: NavOrder[] | undefined): void => {
|
|
253
|
+
for (const order of orders ?? []) {
|
|
254
|
+
const route = routes.find((r) => r.id === order.routeId);
|
|
255
|
+
|
|
256
|
+
if (route?.pageId) pageIds.push(route.pageId);
|
|
257
|
+
|
|
258
|
+
collect(order.subRoutes);
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
collect(brandPageModel.nav?.orders);
|
|
263
|
+
|
|
264
|
+
const pages = brandPageModel.pages ?? [];
|
|
265
|
+
const byId = new Map(pages.map((page) => [page.id, page]));
|
|
266
|
+
const ordered: PageModel[] = [];
|
|
267
|
+
|
|
268
|
+
for (const id of pageIds) {
|
|
269
|
+
const page = byId.get(id);
|
|
270
|
+
|
|
271
|
+
if (page) {
|
|
272
|
+
ordered.push(page);
|
|
273
|
+
byId.delete(id);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
ordered.push(...byId.values());
|
|
278
|
+
|
|
279
|
+
return ordered;
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// Same predicate as `vcard.util`'s `hasProfileContent`, checked directly against the raw model:
|
|
283
|
+
// that one is typed for the rendered view model, which migration runs before building.
|
|
284
|
+
const hasProfileContent = (brandPageModel: BrandPageModel): boolean =>
|
|
285
|
+
(brandPageModel.pages ?? []).some((page) =>
|
|
286
|
+
page.contents?.some((content) => content.type === 'profile'),
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
// A Tiptap doc gives the distinct fonts its non-empty runs show; a non-blank plain string gives
|
|
290
|
+
// the block font; anything else (blank, `false`, absent) casts no vote — read the same way the
|
|
291
|
+
// reconnect will (`drawnFontFamily`), so the count can't disagree with what B and C keep.
|
|
292
|
+
const voteText = (
|
|
293
|
+
tally: FontTally,
|
|
294
|
+
style: TextStyleDeclaration | undefined,
|
|
295
|
+
text: unknown,
|
|
296
|
+
options: TextStyleOptions,
|
|
297
|
+
siteFont: string,
|
|
298
|
+
): void => {
|
|
299
|
+
if (!style) return;
|
|
300
|
+
|
|
301
|
+
if (isTiptapDocument(text)) {
|
|
302
|
+
const blockFont = drawnFontFamily(style, options, siteFont);
|
|
303
|
+
|
|
304
|
+
for (const font of textRunFontFamilies(text, blockFont)) tally.add(font);
|
|
305
|
+
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (typeof text !== 'string' || text.trim() === '') return;
|
|
310
|
+
|
|
311
|
+
tally.add(drawnFontFamily(style, options, siteFont));
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
// The content-level style key → its plain text field, for the sections whose title/description
|
|
315
|
+
// isn't RTE-paired (`pairedRteTextField` returns nothing for them) — a link button's `title`
|
|
316
|
+
// (`ButtonContent`) and a direct section button's `buttonText` (banner, booking, mailing-list…).
|
|
317
|
+
const CONTENT_LEVEL_TEXT_FIELD: Partial<Record<string, string>> = {
|
|
318
|
+
titleStyle: 'title',
|
|
319
|
+
descriptionStyle: 'description',
|
|
320
|
+
paragraphStyle: 'paragraph',
|
|
321
|
+
buttonTextStyle: 'buttonText',
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
// Payment (Continue/Done) and Support Me (the same widget, `Payment.vue`) always show their
|
|
325
|
+
// fixed-label button whenever the section renders — no persisted field holds the label, so
|
|
326
|
+
// there's nothing to check for blank. Shop's fixed-label "View cart" (`Shop.vue:59`) shows only
|
|
327
|
+
// once it has a product to sell; with none, its button (like an empty title) casts no vote.
|
|
328
|
+
const showsFixedLabelButton = (
|
|
329
|
+
contentType: string | undefined,
|
|
330
|
+
fields: Record<string, unknown>,
|
|
331
|
+
): boolean =>
|
|
332
|
+
contentType === 'request-payment' ||
|
|
333
|
+
contentType === 'support-me' ||
|
|
334
|
+
(contentType === 'shop-section' &&
|
|
335
|
+
Array.isArray(fields.products) &&
|
|
336
|
+
fields.products.length > 0);
|
|
337
|
+
|
|
338
|
+
// One content: its own title/description/paragraph/button-text field, then (images/shapes only)
|
|
339
|
+
// each item's title/paragraph/buttonText against the section's single heading/paragraph/button
|
|
340
|
+
// style. `headingStyle` never has content-level text of its own, so it only votes per item.
|
|
341
|
+
const voteContentFields = (
|
|
342
|
+
tallies: Record<TextRoleFamily, FontTally>,
|
|
343
|
+
fields: Record<string, unknown>,
|
|
344
|
+
options: TextStyleOptions,
|
|
345
|
+
siteFont: string,
|
|
346
|
+
): void => {
|
|
347
|
+
const contentType = fields.type as string | undefined;
|
|
348
|
+
|
|
349
|
+
for (const key of [
|
|
350
|
+
'titleStyle',
|
|
351
|
+
'descriptionStyle',
|
|
352
|
+
'paragraphStyle',
|
|
353
|
+
'buttonTextStyle',
|
|
354
|
+
] as const) {
|
|
355
|
+
const style = fields[key];
|
|
356
|
+
|
|
357
|
+
if (!style || typeof style !== 'object') continue;
|
|
358
|
+
|
|
359
|
+
if (
|
|
360
|
+
key === 'buttonTextStyle' &&
|
|
361
|
+
showsFixedLabelButton(contentType, fields)
|
|
362
|
+
) {
|
|
363
|
+
// A fixed-label button's text is an i18n string, never model data — there's nothing to
|
|
364
|
+
// check for blank, so vote its font directly rather than through `voteText`'s text gate.
|
|
365
|
+
tallies.button.add(
|
|
366
|
+
drawnFontFamily(style as TextStyleDeclaration, options, siteFont),
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// `pairedRteTextField` returns nothing for `buttonTextStyle` (never RTE-paired) and for a
|
|
373
|
+
// button content's own `titleStyle` (a link's plain-text label) — `CONTENT_LEVEL_TEXT_FIELD`
|
|
374
|
+
// is exactly the fallback those two need.
|
|
375
|
+
const textField =
|
|
376
|
+
pairedRteTextField(contentType ?? '', key) ??
|
|
377
|
+
CONTENT_LEVEL_TEXT_FIELD[key];
|
|
378
|
+
// The reconnect's own family (`contentFieldReconnectSpec`), so the vote and the reconnect
|
|
379
|
+
// share one classification: Button text for a link button's `titleStyle` or any
|
|
380
|
+
// `buttonTextStyle`, otherwise fixed by the key.
|
|
381
|
+
const family = contentFieldReconnectSpec(contentType, key).family;
|
|
382
|
+
|
|
383
|
+
if (!textField || !family) continue;
|
|
384
|
+
|
|
385
|
+
voteText(
|
|
386
|
+
tallies[family],
|
|
387
|
+
style as TextStyleDeclaration,
|
|
388
|
+
fields[textField],
|
|
389
|
+
options,
|
|
390
|
+
siteFont,
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const items = fields.items;
|
|
395
|
+
|
|
396
|
+
if (!Array.isArray(items)) return;
|
|
397
|
+
|
|
398
|
+
// Items belong only to `MediaSection`s (images/shapes) — the section's own `paragraphStyle`
|
|
399
|
+
// never also pairs an RTE field at content level there (only `about-section` does, and it has
|
|
400
|
+
// no items), so an item's paragraph vote never double-counts the section's own.
|
|
401
|
+
const perItemVotes: [
|
|
402
|
+
TextRoleFamily,
|
|
403
|
+
TextStyleDeclaration | undefined,
|
|
404
|
+
string,
|
|
405
|
+
][] = [
|
|
406
|
+
[
|
|
407
|
+
'headings',
|
|
408
|
+
fields.headingStyle as TextStyleDeclaration | undefined,
|
|
409
|
+
'title',
|
|
410
|
+
],
|
|
411
|
+
[
|
|
412
|
+
'paragraphs',
|
|
413
|
+
fields.paragraphStyle as TextStyleDeclaration | undefined,
|
|
414
|
+
'paragraph',
|
|
415
|
+
],
|
|
416
|
+
[
|
|
417
|
+
'button',
|
|
418
|
+
fields.buttonTextStyle as TextStyleDeclaration | undefined,
|
|
419
|
+
'buttonText',
|
|
420
|
+
],
|
|
421
|
+
];
|
|
422
|
+
|
|
423
|
+
// A left/right-aligned images-section renders only its first few items
|
|
424
|
+
// (`view-model.util.ts` `mapToImagesSectionViewModel`) — an item past that cutoff never shows,
|
|
425
|
+
// so it casts no vote either.
|
|
426
|
+
const displayStyle = fields.displayStyle;
|
|
427
|
+
const renderedItems =
|
|
428
|
+
contentType === 'images-section' &&
|
|
429
|
+
(displayStyle === ALIGN_LEFT_ALIGNED ||
|
|
430
|
+
displayStyle === ALIGN_RIGHT_ALIGNED)
|
|
431
|
+
? (items as Record<string, unknown>[]).slice(
|
|
432
|
+
0,
|
|
433
|
+
MAX_IMAGES_SECTION_ALIGNMENT_ITEMS_LENGTH,
|
|
434
|
+
)
|
|
435
|
+
: (items as Record<string, unknown>[]);
|
|
436
|
+
|
|
437
|
+
for (const item of renderedItems) {
|
|
438
|
+
if (!item || typeof item !== 'object') continue;
|
|
439
|
+
|
|
440
|
+
for (const [family, itemStyle, textField] of perItemVotes) {
|
|
441
|
+
if (itemStyle) {
|
|
442
|
+
voteText(
|
|
443
|
+
tallies[family],
|
|
444
|
+
itemStyle,
|
|
445
|
+
item[textField],
|
|
446
|
+
options,
|
|
447
|
+
siteFont,
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
// The header button renders only with a non-blank label (`view-model.util.ts:1215`) — `isVisible`
|
|
455
|
+
// alone isn't enough, or a visible-but-blank desktop button would wrongly stop the mobile one
|
|
456
|
+
// (which might otherwise render) from getting a turn.
|
|
457
|
+
const headerButtonRenders = (
|
|
458
|
+
headerButton: HeaderButton | undefined,
|
|
459
|
+
): headerButton is HeaderButton =>
|
|
460
|
+
!!headerButton?.isVisible && !!headerButton.text?.trim();
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* A2 — count the font the site's title/heading, description/paragraph and button text shows
|
|
464
|
+
* (`business-rules.md` *Text style roles*), and give each family the winner, before B and C link
|
|
465
|
+
* equal text to it. Runs on the untouched v21 model, after A and before B: a field's font is read
|
|
466
|
+
* the same way B will read its look (`drawnFontFamily`), so the count and the reconnect can't
|
|
467
|
+
* disagree about what a field showed. Only `fontFamily` changes on the ten canonical roles — size,
|
|
468
|
+
* weight and line height stay at their §4 defaults. A family with no vote keeps Lato.
|
|
469
|
+
*/
|
|
470
|
+
export const seedRoleFonts = (
|
|
471
|
+
brandPageModel: BrandPageModel,
|
|
472
|
+
options: TextStyleOptions,
|
|
473
|
+
): void => {
|
|
474
|
+
const siteFont = siteTextFontName(brandPageModel) ?? LATO;
|
|
475
|
+
const tallies: Record<TextRoleFamily, FontTally> = {
|
|
476
|
+
headings: new FontTally(),
|
|
477
|
+
paragraphs: new FontTally(),
|
|
478
|
+
button: new FontTally(),
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
// The header button votes once — the desktop header's, else the mobile one (Q12c).
|
|
482
|
+
for (const header of [
|
|
483
|
+
brandPageModel.pageHeaderDesktop,
|
|
484
|
+
brandPageModel.pageHeaderMobile,
|
|
485
|
+
]) {
|
|
486
|
+
const headerButton = header?.headerButton;
|
|
487
|
+
|
|
488
|
+
if (!headerButtonRenders(headerButton)) continue;
|
|
489
|
+
|
|
490
|
+
voteText(
|
|
491
|
+
tallies.button,
|
|
492
|
+
headerButton.buttonTextStyle ?? {
|
|
493
|
+
textStyleType: TEXT_STYLE_TYPES.buttons,
|
|
494
|
+
},
|
|
495
|
+
headerButton.text,
|
|
496
|
+
options,
|
|
497
|
+
siteFont,
|
|
498
|
+
);
|
|
499
|
+
break;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
for (const page of sitePageOrder(brandPageModel)) {
|
|
503
|
+
for (const content of page.contents ?? []) {
|
|
504
|
+
voteContentFields(
|
|
505
|
+
tallies,
|
|
506
|
+
content as unknown as Record<string, unknown>,
|
|
507
|
+
options,
|
|
508
|
+
siteFont,
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// brand.contact only, and only once the widget is actually turned on (`Page.mixin.ts:535`) and the
|
|
514
|
+
// site has a profile section for it to attach to (`Page.mixin.ts` `shouldShowSaveContactWidget`,
|
|
515
|
+
// `checkHasProfileContent`) — otherwise it never renders, the same "nothing to show" rule as an
|
|
516
|
+
// empty title.
|
|
517
|
+
if (
|
|
518
|
+
brandPageModel.brandPageType === BRAND_PAGE_TYPES.brandContact &&
|
|
519
|
+
brandPageModel.saveContactSettings?.isSavingEnabled !== false &&
|
|
520
|
+
hasProfileContent(brandPageModel)
|
|
521
|
+
) {
|
|
522
|
+
tallies.button.add(
|
|
523
|
+
drawnFontFamily(
|
|
524
|
+
brandPageModel.saveContactSettings?.buttonTextStyle ?? {
|
|
525
|
+
textStyleType: TEXT_STYLE_TYPES.buttons,
|
|
526
|
+
},
|
|
527
|
+
options,
|
|
528
|
+
siteFont,
|
|
529
|
+
),
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// The canonical roles normally pre-exist (the model constructor seeds all 10); guard
|
|
534
|
+
// defensively for pre-10-role metadata rather than assume it (as `seedRoleColours` does). A
|
|
535
|
+
// family with no vote keeps Lato — never the site font, which is only what an UNCOUNTED field
|
|
536
|
+
// inherits (`drawnFontFamily`), not a family-wide default.
|
|
537
|
+
for (const family of Object.keys(tallies) as TextRoleFamily[]) {
|
|
538
|
+
const winner = tallies[family].winner(LATO);
|
|
539
|
+
|
|
540
|
+
for (const role of FAMILY_TIERS[family]) {
|
|
541
|
+
if (options[role]) options[role].style.fontFamily = winner;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* B — seed the absent button styles, then reconnect every text field outside the ten to its
|
|
548
|
+
* nearest role, keeping the look production drew: a field on a role drew only that role's
|
|
549
|
+
* typography (`drawnBeforeCascade`). After this every text field links one of the ten (R14).
|
|
550
|
+
*/
|
|
551
|
+
const isUnset = (value: unknown): boolean =>
|
|
552
|
+
value === undefined || value === null || value === '';
|
|
553
|
+
|
|
554
|
+
const isUnlinkedLegacyStyle = (
|
|
555
|
+
style: unknown,
|
|
556
|
+
): style is Record<string, unknown> => {
|
|
557
|
+
if (!style || typeof style !== 'object') return false;
|
|
558
|
+
|
|
559
|
+
const type = (style as Record<string, unknown>).textStyleType;
|
|
560
|
+
|
|
561
|
+
return (
|
|
562
|
+
!isCanonicalTextStyleType(type as string | undefined) &&
|
|
563
|
+
!isLegacyTextStyleType(type as string | undefined)
|
|
564
|
+
);
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
const setIfUnset = (
|
|
568
|
+
style: Record<string, unknown>,
|
|
569
|
+
key: string,
|
|
570
|
+
value: unknown,
|
|
571
|
+
): void => {
|
|
572
|
+
if (isUnset(style[key]) && !isUnset(value)) style[key] = value;
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
const styleRecord = (value: unknown): Record<string, unknown> =>
|
|
576
|
+
value && typeof value === 'object' && !Array.isArray(value)
|
|
577
|
+
? (value as Record<string, unknown>)
|
|
578
|
+
: {};
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* B0 — production composed three no-role text styles in its view-model layer. Recreate those
|
|
582
|
+
* values once on the persisted field before B picks the nearest canonical role. A valid legacy
|
|
583
|
+
* role was resolved instead of these component defaults, so it must remain untouched.
|
|
584
|
+
*/
|
|
585
|
+
const materialiseNoRoleRendererDefaults = (
|
|
586
|
+
brandPageModel: BrandPageModel,
|
|
587
|
+
): void => {
|
|
588
|
+
for (const page of brandPageModel.pages ?? []) {
|
|
589
|
+
for (const content of page.contents ?? []) {
|
|
590
|
+
const fields = content as unknown as Record<string, unknown>;
|
|
591
|
+
|
|
592
|
+
if (content.type === 'mailing-list') {
|
|
593
|
+
const style = fields.titleStyle;
|
|
594
|
+
|
|
595
|
+
if (isUnlinkedLegacyStyle(style)) {
|
|
596
|
+
setIfUnset(style, 'fontSize', FONT_SIZE_BODY);
|
|
597
|
+
setIfUnset(style, 'fontWeight', 'bold');
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
if (content.type === 'support-me') {
|
|
602
|
+
const style = fields.titleStyle;
|
|
603
|
+
|
|
604
|
+
if (isUnlinkedLegacyStyle(style)) {
|
|
605
|
+
setIfUnset(style, 'fontWeight', 'bold');
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
if (content.type === 'text') {
|
|
610
|
+
const original = styleRecord(fields.textStyle);
|
|
611
|
+
const isUnlinked = isUnlinkedLegacyStyle(original);
|
|
612
|
+
|
|
613
|
+
// Production's text mapper merged these deprecated sibling objects before the style
|
|
614
|
+
// object. Alignment and colour mattered even on an old role; textSize and the 1.5
|
|
615
|
+
// default only mattered without one, because a role replaced all typography.
|
|
616
|
+
fields.textStyle = isUnlinked
|
|
617
|
+
? {
|
|
618
|
+
lineHeight: LINE_HEIGHT_BODY,
|
|
619
|
+
...styleRecord(fields.textSize),
|
|
620
|
+
...styleRecord(fields.textAlignment),
|
|
621
|
+
...styleRecord(fields.textColor),
|
|
622
|
+
...styleRecord(original),
|
|
623
|
+
}
|
|
624
|
+
: {
|
|
625
|
+
...styleRecord(fields.textAlignment),
|
|
626
|
+
...styleRecord(fields.textColor),
|
|
627
|
+
...styleRecord(original),
|
|
628
|
+
};
|
|
629
|
+
|
|
630
|
+
delete fields.textSize;
|
|
631
|
+
delete fields.textAlignment;
|
|
632
|
+
delete fields.textColor;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
|
|
638
|
+
const legacyRenderedDefaults: RenderedDefaultsForContent = (content, key) => {
|
|
639
|
+
const style = content[key];
|
|
640
|
+
|
|
641
|
+
if (!isUnlinkedLegacyStyle(style)) return undefined;
|
|
642
|
+
|
|
643
|
+
if (content.type === 'mailing-list' && key === 'titleStyle') {
|
|
644
|
+
return { fontSize: FONT_SIZE_BODY, fontWeight: 'bold' };
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
if (content.type === 'support-me' && key === 'titleStyle') {
|
|
648
|
+
return { fontWeight: 'bold' };
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
if (content.type === 'text' && key === 'textStyle') {
|
|
652
|
+
return { lineHeight: LINE_HEIGHT_BODY };
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
return undefined;
|
|
656
|
+
};
|
|
657
|
+
|
|
658
|
+
export const reconnectContentRoles = (
|
|
659
|
+
brandPageModel: BrandPageModel,
|
|
660
|
+
targets: LegacyTargetMap,
|
|
661
|
+
options: TextStyleOptions,
|
|
662
|
+
): void => {
|
|
663
|
+
seedAbsentButtonStyles(brandPageModel);
|
|
664
|
+
materialiseNoRoleRendererDefaults(brandPageModel);
|
|
665
|
+
reconnectTextStyleRoles(brandPageModel, targets, options, {
|
|
666
|
+
drawnBeforeCascade: true,
|
|
667
|
+
renderedDefaultsForContent: legacyRenderedDefaults,
|
|
668
|
+
});
|
|
669
|
+
};
|
|
670
|
+
|
|
671
|
+
// ── BP-5969 ADR-0020: colour decouple (jobs D, E, G) ──────────────────────────
|
|
672
|
+
// Appended to the appearance-preserving A/B/C above. Colour is read via bracket access
|
|
673
|
+
// because `GlobalTextStyleDeclaration.color` is retired from the typed model.
|
|
674
|
+
|
|
675
|
+
type RawStyleObject = {
|
|
676
|
+
color?: string;
|
|
677
|
+
textStyleType?: string;
|
|
678
|
+
applyAutoScale?: boolean;
|
|
679
|
+
textAlign?: string;
|
|
680
|
+
fontWeight?: string;
|
|
681
|
+
};
|
|
682
|
+
|
|
683
|
+
type NoRoleTextStyles = WeakMap<object, Set<string>>;
|
|
684
|
+
|
|
685
|
+
const hasNoRole = (style: unknown): boolean => {
|
|
686
|
+
if (!style || typeof style !== 'object') return false;
|
|
687
|
+
|
|
688
|
+
const textStyleType = (style as RawStyleObject).textStyleType;
|
|
689
|
+
|
|
690
|
+
return (
|
|
691
|
+
!isCanonicalTextStyleType(textStyleType) &&
|
|
692
|
+
!isLegacyTextStyleType(textStyleType)
|
|
693
|
+
);
|
|
694
|
+
};
|
|
695
|
+
|
|
696
|
+
/**
|
|
697
|
+
* Job B assigns a canonical role to every unlinked field. Job D must still use the colour the
|
|
698
|
+
* field rendered with before that assignment: its own colour, container default, then the base coat
|
|
699
|
+
* (never the role B selected merely for typography). Keep the location rather than the style object,
|
|
700
|
+
* because reconnect replaces each style object.
|
|
701
|
+
*/
|
|
702
|
+
const captureNoRoleTextStyles = (
|
|
703
|
+
brandPageModel: BrandPageModel,
|
|
704
|
+
): NoRoleTextStyles => {
|
|
705
|
+
const locations: NoRoleTextStyles = new WeakMap();
|
|
706
|
+
const record = (holder: Record<string, unknown>, key: string): void => {
|
|
707
|
+
if (!hasNoRole(holder[key])) return;
|
|
708
|
+
|
|
709
|
+
const keys = locations.get(holder) ?? new Set<string>();
|
|
710
|
+
|
|
711
|
+
keys.add(key);
|
|
712
|
+
locations.set(holder, keys);
|
|
713
|
+
};
|
|
714
|
+
|
|
715
|
+
for (const page of brandPageModel.pages ?? []) {
|
|
716
|
+
for (const content of page.contents ?? []) {
|
|
717
|
+
const fields = content as unknown as Record<string, unknown>;
|
|
718
|
+
|
|
719
|
+
for (const key of Object.keys(CONTENT_TEXT_STYLE_PROPERTY_KEYS)) {
|
|
720
|
+
record(fields, key);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
const textItems = fields.textItems;
|
|
724
|
+
|
|
725
|
+
if (Array.isArray(textItems)) {
|
|
726
|
+
for (const item of textItems) {
|
|
727
|
+
if (item && typeof item === 'object') {
|
|
728
|
+
record(item as Record<string, unknown>, 'textStyle');
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
for (const footer of [
|
|
736
|
+
brandPageModel.pageFooterDesktop,
|
|
737
|
+
brandPageModel.pageFooterMobile,
|
|
738
|
+
]) {
|
|
739
|
+
if (footer && typeof footer === 'object') {
|
|
740
|
+
record(footer as unknown as Record<string, unknown>, 'copyrightStyle');
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
return locations;
|
|
745
|
+
};
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* An RTE-eligible field that is a plain string, not a Tiptap doc. On an old site nothing
|
|
749
|
+
* converts plain text → doc (migration-20 only converts HTML), so such a field renders via
|
|
750
|
+
* `TextWrapper`'s PLAIN path from `style.color` — it has no run mark to hold colour. So its
|
|
751
|
+
* colour must be baked onto (D) and never stripped from (G) the style object; only a genuine
|
|
752
|
+
* doc's colour moves into a mark. A non-string field is NOT plain text: a real doc holds its
|
|
753
|
+
* colour in the mark, and an absent (`undefined`/`null`) field renders nothing — either way G
|
|
754
|
+
* safely strips the (now inert) style-object colour. An empty string `''` DOES count as plain
|
|
755
|
+
* text and is kept: a field later filled with plain text must not have lost its colour.
|
|
756
|
+
* `convertPlainRteFields` completes that conversion in this migration; regular runtime paths do not rewrap.
|
|
757
|
+
*/
|
|
758
|
+
const isPlainTextValue = (value: unknown): value is string =>
|
|
759
|
+
typeof value === 'string';
|
|
760
|
+
|
|
761
|
+
/** A linked role's colour, read defensively (the typed property is retired). */
|
|
762
|
+
const roleColour = (
|
|
763
|
+
options: TextStyleOptions,
|
|
764
|
+
textStyleType: string | undefined,
|
|
765
|
+
): string | undefined =>
|
|
766
|
+
textStyleType
|
|
767
|
+
? (((
|
|
768
|
+
options[textStyleType as TextStyleType]?.style as unknown as
|
|
769
|
+
| Record<string, unknown>
|
|
770
|
+
| undefined
|
|
771
|
+
)?.color as string | undefined) ?? undefined)
|
|
772
|
+
: undefined;
|
|
773
|
+
|
|
774
|
+
/**
|
|
775
|
+
* D — freeze each text element's currently-resolved colour so removing the base coat (F)
|
|
776
|
+
* and the role colour (E) changes no pixels. migration-21 already baked element→frame→role
|
|
777
|
+
* colour into RTE runs, so the only uncoloured runs reaching here inherit the base coat;
|
|
778
|
+
* non-RTE fields (21 left them alone) resolve element→container-default→role→base coat.
|
|
779
|
+
* `buttonTextStyle` is excluded — the button label takes its colour from `buttonStyles`,
|
|
780
|
+
* never the base coat. A social-links section's unset icon fill is frozen here too.
|
|
781
|
+
*/
|
|
782
|
+
export const bakeElementColours = (
|
|
783
|
+
brandPageModel: BrandPageModel,
|
|
784
|
+
options: TextStyleOptions,
|
|
785
|
+
noRoleTextStyles: NoRoleTextStyles = new WeakMap(),
|
|
786
|
+
): void => {
|
|
787
|
+
const baseCoat = roleColour(options, 'paragraph1');
|
|
788
|
+
|
|
789
|
+
for (const page of brandPageModel.pages ?? []) {
|
|
790
|
+
for (const content of page.contents ?? []) {
|
|
791
|
+
const fields = content as unknown as Record<string, unknown>;
|
|
792
|
+
const containerDefaults = containerTextColorDefaults(
|
|
793
|
+
content,
|
|
794
|
+
brandPageModel.styles,
|
|
795
|
+
brandPageModel.brandPageType,
|
|
796
|
+
);
|
|
797
|
+
const rteStyleFields = new Set(rteStyleFieldsFor(content.type));
|
|
798
|
+
|
|
799
|
+
const resolvedColour = (
|
|
800
|
+
style: RawStyleObject | undefined,
|
|
801
|
+
holder: object,
|
|
802
|
+
key: string,
|
|
803
|
+
): string | undefined =>
|
|
804
|
+
style?.color ||
|
|
805
|
+
containerDefaults[key as ContentTextStylePropertyKey] ||
|
|
806
|
+
(noRoleTextStyles.get(holder)?.has(key)
|
|
807
|
+
? undefined
|
|
808
|
+
: roleColour(options, style?.textStyleType)) ||
|
|
809
|
+
baseCoat;
|
|
810
|
+
|
|
811
|
+
// Non-RTE style objects → bake onto `style.color`.
|
|
812
|
+
for (const key of Object.keys(CONTENT_TEXT_STYLE_PROPERTY_KEYS)) {
|
|
813
|
+
if (key === 'buttonTextStyle' || rteStyleFields.has(key)) continue;
|
|
814
|
+
|
|
815
|
+
const style = fields[key] as RawStyleObject | undefined;
|
|
816
|
+
|
|
817
|
+
if (!style || !isBlankColorValue(style.color)) continue;
|
|
818
|
+
|
|
819
|
+
const resolved = resolvedColour(style, fields, key);
|
|
820
|
+
|
|
821
|
+
if (!isBlankColorValue(resolved)) style.color = resolved;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// RTE-eligible fields. A real doc holds colour in its run marks → fill the runs the mark
|
|
825
|
+
// left uncoloured (the base-coat stragglers). A plain string (an old site never converted
|
|
826
|
+
// to a doc) has NO mark, so its colour lives on the style object → bake it there, like a
|
|
827
|
+
// non-RTE field; G then leaves that colour alone (it only strips when a doc holds it).
|
|
828
|
+
for (const styleField of rteStyleFields) {
|
|
829
|
+
const textField = pairedRteTextField(content.type, styleField);
|
|
830
|
+
|
|
831
|
+
if (!textField) continue;
|
|
832
|
+
|
|
833
|
+
const doc = fields[textField];
|
|
834
|
+
|
|
835
|
+
if (isPlainTextValue(doc)) {
|
|
836
|
+
const style = fields[styleField] as RawStyleObject | undefined;
|
|
837
|
+
|
|
838
|
+
if (style && isBlankColorValue(style.color)) {
|
|
839
|
+
const resolved = resolvedColour(style, fields, styleField);
|
|
840
|
+
|
|
841
|
+
if (!isBlankColorValue(resolved)) style.color = resolved;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
continue;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
if (!collectRteTextColors(doc)?.hasUncolouredRuns) continue;
|
|
848
|
+
|
|
849
|
+
const resolved = resolvedColour(
|
|
850
|
+
fields[styleField] as RawStyleObject | undefined,
|
|
851
|
+
fields,
|
|
852
|
+
styleField,
|
|
853
|
+
);
|
|
854
|
+
|
|
855
|
+
if (!isBlankColorValue(resolved)) {
|
|
856
|
+
fields[textField] =
|
|
857
|
+
fillUncolouredRteText(doc, resolved as string) ?? doc;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// Social icons had no colour of their own: they painted the base coat (the paragraphs
|
|
862
|
+
// seed, or `currentColor` under `:root`). Freeze it; with no base coat, the title's
|
|
863
|
+
// contrast colour, so every icon ends with a stored fill.
|
|
864
|
+
if (content.type === 'social-links') {
|
|
865
|
+
const iconFillStyles = (fields.iconFillStyles ?? {}) as Record<
|
|
866
|
+
string,
|
|
867
|
+
unknown
|
|
868
|
+
>;
|
|
869
|
+
|
|
870
|
+
if (isBlankColorValue(iconFillStyles.fill as string | undefined)) {
|
|
871
|
+
const fill = !isBlankColorValue(baseCoat)
|
|
872
|
+
? baseCoat
|
|
873
|
+
: titleSurfaceContrastFill(
|
|
874
|
+
content,
|
|
875
|
+
brandPageModel.styles,
|
|
876
|
+
brandPageModel.brandPageType,
|
|
877
|
+
);
|
|
878
|
+
|
|
879
|
+
if (!isBlankColorValue(fill)) {
|
|
880
|
+
fields.iconFillStyles = { ...iconFillStyles, fill };
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
// brandSite text `textItems` — same rule per item (RTE doc, else the style object).
|
|
886
|
+
const textItems = fields.textItems as
|
|
887
|
+
| { text?: unknown; textStyle?: RawStyleObject }[]
|
|
888
|
+
| undefined;
|
|
889
|
+
|
|
890
|
+
if (Array.isArray(textItems)) {
|
|
891
|
+
for (const item of textItems) {
|
|
892
|
+
if (!item || typeof item !== 'object') continue;
|
|
893
|
+
|
|
894
|
+
const resolved = resolvedColour(item.textStyle, item, 'textStyle');
|
|
895
|
+
|
|
896
|
+
if (isBlankColorValue(resolved)) continue;
|
|
897
|
+
|
|
898
|
+
if (collectRteTextColors(item.text)?.hasUncolouredRuns) {
|
|
899
|
+
item.text =
|
|
900
|
+
fillUncolouredRteText(item.text, resolved as string) ?? item.text;
|
|
901
|
+
} else if (
|
|
902
|
+
item.textStyle &&
|
|
903
|
+
isBlankColorValue(item.textStyle.color)
|
|
904
|
+
) {
|
|
905
|
+
item.textStyle.color = resolved;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// Footer copyright is non-RTE text that resolved through role → base coat, but lives
|
|
913
|
+
// OUTSIDE `pages[].contents`, so freeze it here too — else it loses its colour when E
|
|
914
|
+
// clears the role and F removes the base coat (the copyright-on-dark-footer regression).
|
|
915
|
+
for (const footer of [
|
|
916
|
+
brandPageModel.pageFooterDesktop,
|
|
917
|
+
brandPageModel.pageFooterMobile,
|
|
918
|
+
]) {
|
|
919
|
+
const style = footer?.copyrightStyle as RawStyleObject | undefined;
|
|
920
|
+
|
|
921
|
+
if (!style || !isBlankColorValue(style.color)) continue;
|
|
922
|
+
|
|
923
|
+
const resolved =
|
|
924
|
+
(noRoleTextStyles.get(footer)?.has('copyrightStyle')
|
|
925
|
+
? undefined
|
|
926
|
+
: roleColour(options, style.textStyleType)) || baseCoat;
|
|
927
|
+
|
|
928
|
+
if (!isBlankColorValue(resolved)) style.color = resolved;
|
|
929
|
+
}
|
|
930
|
+
};
|
|
931
|
+
|
|
932
|
+
/** E — clear the retired role colour from every role definition (run after D reads it). */
|
|
933
|
+
export const clearRoleColours = (options: TextStyleOptions): void => {
|
|
934
|
+
for (const key of Object.keys(options)) {
|
|
935
|
+
const style = options[key as TextStyleType]?.style as unknown as
|
|
936
|
+
| Record<string, unknown>
|
|
937
|
+
| undefined;
|
|
938
|
+
|
|
939
|
+
if (style && 'color' in style) delete style.color;
|
|
940
|
+
}
|
|
941
|
+
};
|
|
942
|
+
|
|
943
|
+
/**
|
|
944
|
+
* G — colour-decouple the RTE-paired style objects: remove ONLY `color` (D baked it into the
|
|
945
|
+
* mark). Everything else stays — a block-level typography override (a title set to a non-role
|
|
946
|
+
* font/size, `textAlign`, `applyAutoScale`, the role link) is genuine state the renderer
|
|
947
|
+
* applies via `overrideTypographyStyle`; dropping it would lose the override through migration.
|
|
948
|
+
* So the RTE style object becomes `Omit<TextStyleDeclaration, 'color'>`, not a bare role link.
|
|
949
|
+
* Non-RTE per-item styles (`headingStyle`, shapes/images `paragraphStyle`, `testimonialStyle`,
|
|
950
|
+
* …) are not RTE and are untouched.
|
|
951
|
+
*/
|
|
952
|
+
const stripRteStyleColour = (style: RawStyleObject | undefined): void => {
|
|
953
|
+
if (style && 'color' in style) delete style.color;
|
|
954
|
+
};
|
|
955
|
+
|
|
956
|
+
export const stripRteStyleColours = (brandPageModel: BrandPageModel): void => {
|
|
957
|
+
for (const page of brandPageModel.pages ?? []) {
|
|
958
|
+
for (const content of page.contents ?? []) {
|
|
959
|
+
const fields = content as unknown as Record<string, unknown>;
|
|
960
|
+
|
|
961
|
+
for (const styleField of rteStyleFieldsFor(content.type)) {
|
|
962
|
+
const textField = pairedRteTextField(content.type, styleField);
|
|
963
|
+
|
|
964
|
+
// A plain-string field keeps its style-object colour — that's its only home (D baked it
|
|
965
|
+
// there, there's no mark). A real doc holds colour in its mark, so strip the object.
|
|
966
|
+
if (textField && isPlainTextValue(fields[textField])) continue;
|
|
967
|
+
|
|
968
|
+
stripRteStyleColour(fields[styleField] as RawStyleObject | undefined);
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
const textItems = fields.textItems as
|
|
972
|
+
| { text?: unknown; textStyle?: RawStyleObject }[]
|
|
973
|
+
| undefined;
|
|
974
|
+
|
|
975
|
+
if (Array.isArray(textItems)) {
|
|
976
|
+
for (const item of textItems) {
|
|
977
|
+
if (!item || typeof item !== 'object') continue;
|
|
978
|
+
|
|
979
|
+
if (isPlainTextValue(item.text)) continue; // plain-text item keeps its baked colour
|
|
980
|
+
|
|
981
|
+
stripRteStyleColour(item.textStyle);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
};
|
|
987
|
+
|
|
988
|
+
/** H — delete the retired site text font and the footers' stray copies. */
|
|
989
|
+
export const deleteTextFontFamily = (brandPageModel: BrandPageModel): void => {
|
|
990
|
+
const holders = [
|
|
991
|
+
brandPageModel.styles?.textStyles,
|
|
992
|
+
brandPageModel.pageFooterDesktop,
|
|
993
|
+
brandPageModel.pageFooterMobile,
|
|
994
|
+
] as unknown as (Record<string, unknown> | undefined)[];
|
|
995
|
+
|
|
996
|
+
for (const holder of holders) {
|
|
997
|
+
if (holder) delete holder['textFontFamily'];
|
|
998
|
+
}
|
|
999
|
+
};
|
|
1000
|
+
|
|
1001
|
+
/**
|
|
1002
|
+
* B1 — convert plain-text RTE-eligible fields to Tiptap docs, so every such field is a doc at
|
|
1003
|
+
* load and the editor never runs its plain→RTE "rewrap" (the runtime anti-pattern this retires).
|
|
1004
|
+
*
|
|
1005
|
+
* Only PLAIN strings are converted: migration-20 already did HTML→doc, and the plain-by-design
|
|
1006
|
+
* sections (`shop-section`/`request-payment`/`support-me`) are excluded because `rteStyleFieldsFor`
|
|
1007
|
+
* returns `[]` for them — they render via `TextWrapper`'s plain path and keep their `style.color`.
|
|
1008
|
+
* A converted field's colour is filled by D (into the mark) and its style-object colour stripped
|
|
1009
|
+
* by G, exactly like any other doc.
|
|
1010
|
+
*
|
|
1011
|
+
* Runs AFTER C (see orchestrator) so C only ever sees editor-baked docs and its per-run typography
|
|
1012
|
+
* bake operates on the real runs, not a freshly-converted placeholder.
|
|
1013
|
+
*
|
|
1014
|
+
* Error-tolerant like migration-20: a parse failure (or no DOM parser in this runtime) leaves the
|
|
1015
|
+
* plain string in place, and Path A's D/G plain-text branches keep its colour resolvable — so the
|
|
1016
|
+
* two together are total. `textAlign` is stamped onto the block node from the style object, as the
|
|
1017
|
+
* runtime conversion (`getUpdatedHTMLFromEditor`) does via `setTextAlign`.
|
|
1018
|
+
*
|
|
1019
|
+
* The converted run is then baked with this migration's frozen typography resolver, so it carries all
|
|
1020
|
+
* 7 typography attrs as a role var (follows role edits) or a literal override, not colour alone. Job C
|
|
1021
|
+
* never saw this field (it was still a plain string), so without this bake the run would carry only
|
|
1022
|
+
* colour and the maker toolbar (which reads the run, not the style object) would fall back to the role,
|
|
1023
|
+
* ignoring a block-level override (e.g. a banner title set to a non-role font/size). It stays
|
|
1024
|
+
* appearance-preserving: the fill is idempotent over an already-baked run, so the persisted doc renders
|
|
1025
|
+
* identically — it is now just self-describing.
|
|
1026
|
+
*/
|
|
1027
|
+
const setBlockTextAlign = (
|
|
1028
|
+
doc: JSONContent,
|
|
1029
|
+
textAlign: string | undefined,
|
|
1030
|
+
): void => {
|
|
1031
|
+
if (!textAlign) return;
|
|
1032
|
+
|
|
1033
|
+
for (const block of doc.content ?? []) {
|
|
1034
|
+
block.attrs = { ...(block.attrs ?? {}), textAlign };
|
|
1035
|
+
}
|
|
1036
|
+
};
|
|
1037
|
+
|
|
1038
|
+
// Snapshot the B1 resolver here rather than calling the live render helper. A versioned migration
|
|
1039
|
+
// defines one permanent v21 -> v22 transformation; later renderer fixes must not change its output.
|
|
1040
|
+
const MIGRATION_RTE_TYPOGRAPHY_ATTRS = [
|
|
1041
|
+
['fontFamily', '--role-font-family'],
|
|
1042
|
+
['fontSize', '--role-font-size'],
|
|
1043
|
+
['lineHeight', '--role-line-height'],
|
|
1044
|
+
['letterSpacing', '--role-letter-spacing'],
|
|
1045
|
+
['fontWeight', '--role-font-weight'],
|
|
1046
|
+
['fontStyle', '--role-font-style'],
|
|
1047
|
+
['textDecoration', '--role-text-decoration'],
|
|
1048
|
+
] as const;
|
|
1049
|
+
|
|
1050
|
+
type MigrationRteTypographyAttr =
|
|
1051
|
+
(typeof MIGRATION_RTE_TYPOGRAPHY_ATTRS)[number][0];
|
|
1052
|
+
|
|
1053
|
+
type MigrationRteTypography = Partial<
|
|
1054
|
+
Record<MigrationRteTypographyAttr, string>
|
|
1055
|
+
>;
|
|
1056
|
+
|
|
1057
|
+
const MIGRATION_RTE_NEUTRAL_TYPOGRAPHY: Partial<
|
|
1058
|
+
Record<MigrationRteTypographyAttr, string>
|
|
1059
|
+
> = {
|
|
1060
|
+
fontWeight: 'normal',
|
|
1061
|
+
fontStyle: 'normal',
|
|
1062
|
+
textDecoration: 'none',
|
|
1063
|
+
letterSpacing: 'normal',
|
|
1064
|
+
};
|
|
1065
|
+
|
|
1066
|
+
const UNQUOTED_FONT_FAMILY_TOKEN = /^-?[A-Za-z_][A-Za-z0-9_-]*$/;
|
|
1067
|
+
|
|
1068
|
+
const migrationCssFontFamilyValue = (value: string): string =>
|
|
1069
|
+
value
|
|
1070
|
+
.split(',')
|
|
1071
|
+
.map((raw) => {
|
|
1072
|
+
const family = raw.trim();
|
|
1073
|
+
|
|
1074
|
+
if (
|
|
1075
|
+
!family ||
|
|
1076
|
+
family.startsWith('"') ||
|
|
1077
|
+
family.startsWith("'") ||
|
|
1078
|
+
family.startsWith('var(')
|
|
1079
|
+
) {
|
|
1080
|
+
return family;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
return family
|
|
1084
|
+
.split(/\s+/)
|
|
1085
|
+
.every((token) => UNQUOTED_FONT_FAMILY_TOKEN.test(token))
|
|
1086
|
+
? family
|
|
1087
|
+
: `"${family}"`;
|
|
1088
|
+
})
|
|
1089
|
+
.filter(Boolean)
|
|
1090
|
+
.join(', ');
|
|
1091
|
+
|
|
1092
|
+
type MigrationRteNode = {
|
|
1093
|
+
type?: string;
|
|
1094
|
+
marks?: { type: string; attrs?: Record<string, unknown> }[];
|
|
1095
|
+
content?: MigrationRteNode[];
|
|
1096
|
+
};
|
|
1097
|
+
|
|
1098
|
+
const migrationRoleVarRef = (attr: MigrationRteTypographyAttr): string => {
|
|
1099
|
+
const variable = MIGRATION_RTE_TYPOGRAPHY_ATTRS.find(
|
|
1100
|
+
([name]) => name === attr,
|
|
1101
|
+
)?.[1];
|
|
1102
|
+
|
|
1103
|
+
return `var(${variable})`;
|
|
1104
|
+
};
|
|
1105
|
+
|
|
1106
|
+
const migrationRunTypographyFillValue = (
|
|
1107
|
+
attr: MigrationRteTypographyAttr,
|
|
1108
|
+
roleStyle: MigrationRteTypography | undefined,
|
|
1109
|
+
style: MigrationRteTypography | undefined,
|
|
1110
|
+
): string | undefined => {
|
|
1111
|
+
const roleValue = roleStyle?.[attr];
|
|
1112
|
+
const blockValue = style?.[attr];
|
|
1113
|
+
const neutral = MIGRATION_RTE_NEUTRAL_TYPOGRAPHY[attr];
|
|
1114
|
+
const hasRole = roleValue != null && roleValue !== '';
|
|
1115
|
+
const hasBlock = blockValue != null && blockValue !== '';
|
|
1116
|
+
|
|
1117
|
+
if (roleStyle == null) return hasBlock ? blockValue : undefined;
|
|
1118
|
+
|
|
1119
|
+
const effectiveRole = hasRole ? roleValue : neutral;
|
|
1120
|
+
|
|
1121
|
+
if (hasBlock && blockValue !== effectiveRole) return blockValue;
|
|
1122
|
+
|
|
1123
|
+
return hasRole || neutral != null ? migrationRoleVarRef(attr) : undefined;
|
|
1124
|
+
};
|
|
1125
|
+
|
|
1126
|
+
const fillMigration22RunTypography = (
|
|
1127
|
+
doc: JSONContent,
|
|
1128
|
+
roleStyle: MigrationRteTypography | undefined,
|
|
1129
|
+
style: MigrationRteTypography | undefined,
|
|
1130
|
+
): JSONContent => {
|
|
1131
|
+
const clone = JSON.parse(JSON.stringify(doc)) as MigrationRteNode;
|
|
1132
|
+
const runs: MigrationRteNode[] = [];
|
|
1133
|
+
|
|
1134
|
+
const collect = (node: MigrationRteNode | undefined): void => {
|
|
1135
|
+
if (!node) return;
|
|
1136
|
+
|
|
1137
|
+
if (node.type === 'text') runs.push(node);
|
|
1138
|
+
|
|
1139
|
+
node.content?.forEach(collect);
|
|
1140
|
+
};
|
|
1141
|
+
|
|
1142
|
+
collect(clone);
|
|
1143
|
+
|
|
1144
|
+
for (const run of runs) {
|
|
1145
|
+
let mark = run.marks?.find((candidate) => candidate.type === 'textStyle');
|
|
1146
|
+
|
|
1147
|
+
for (const [attr] of MIGRATION_RTE_TYPOGRAPHY_ATTRS) {
|
|
1148
|
+
const existing = mark?.attrs?.[attr];
|
|
1149
|
+
|
|
1150
|
+
if (existing !== undefined && existing !== null && existing !== '')
|
|
1151
|
+
continue;
|
|
1152
|
+
|
|
1153
|
+
let value = migrationRunTypographyFillValue(attr, roleStyle, style);
|
|
1154
|
+
|
|
1155
|
+
if (value == null) continue;
|
|
1156
|
+
|
|
1157
|
+
if (attr === 'fontFamily') value = migrationCssFontFamilyValue(value);
|
|
1158
|
+
|
|
1159
|
+
if (!mark) {
|
|
1160
|
+
mark = { type: 'textStyle', attrs: {} };
|
|
1161
|
+
(run.marks ??= []).push(mark);
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
mark.attrs ??= {};
|
|
1165
|
+
mark.attrs[attr] = value;
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
return clone as JSONContent;
|
|
1170
|
+
};
|
|
1171
|
+
|
|
1172
|
+
// B1 converts a plain string after job C has run, so it needs C3's not-bold protection locally
|
|
1173
|
+
// too. Keep the whole B1 transform frozen: changing `rte-role-lift.util.ts` must not alter v22.
|
|
1174
|
+
const pinMigration22RunWeight = (
|
|
1175
|
+
doc: JSONContent,
|
|
1176
|
+
roleWeight: string | undefined,
|
|
1177
|
+
blockWeight: string | undefined,
|
|
1178
|
+
): void => {
|
|
1179
|
+
if (
|
|
1180
|
+
blockWeight === undefined ||
|
|
1181
|
+
blockWeight === null ||
|
|
1182
|
+
blockWeight === '' ||
|
|
1183
|
+
blockWeight === roleWeight
|
|
1184
|
+
) {
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
const runs: MigrationRteNode[] = [];
|
|
1189
|
+
const collect = (node: MigrationRteNode | undefined): void => {
|
|
1190
|
+
if (!node) return;
|
|
1191
|
+
|
|
1192
|
+
if (node.type === 'text') runs.push(node);
|
|
1193
|
+
|
|
1194
|
+
node.content?.forEach(collect);
|
|
1195
|
+
};
|
|
1196
|
+
|
|
1197
|
+
collect(doc as MigrationRteNode);
|
|
1198
|
+
|
|
1199
|
+
for (const run of runs) {
|
|
1200
|
+
let mark = run.marks?.find((candidate) => candidate.type === 'textStyle');
|
|
1201
|
+
|
|
1202
|
+
if (mark?.attrs?.fontWeight != null && mark.attrs.fontWeight !== '')
|
|
1203
|
+
continue;
|
|
1204
|
+
|
|
1205
|
+
if (!mark) {
|
|
1206
|
+
mark = { type: 'textStyle', attrs: {} };
|
|
1207
|
+
(run.marks ??= []).push(mark);
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
mark.attrs ??= {};
|
|
1211
|
+
mark.attrs.fontWeight = blockWeight;
|
|
1212
|
+
}
|
|
1213
|
+
};
|
|
1214
|
+
|
|
1215
|
+
export const convertPlainRteFields = async (
|
|
1216
|
+
brandPageModel: BrandPageModel,
|
|
1217
|
+
options: TextStyleOptions,
|
|
1218
|
+
): Promise<void> => {
|
|
1219
|
+
let parser: HtmlParser;
|
|
1220
|
+
|
|
1221
|
+
try {
|
|
1222
|
+
parser = await resolveHtmlParser();
|
|
1223
|
+
} catch (error) {
|
|
1224
|
+
// No DOM parser here (e.g. jsdom failed to load on the server). Skip conversion — plain
|
|
1225
|
+
// strings survive to D/G, whose Path A branches keep their colour. Mirrors migration-20.
|
|
1226
|
+
console.error(
|
|
1227
|
+
'migration-22: no HTML parser available, skipping plain→RTE conversion',
|
|
1228
|
+
error,
|
|
1229
|
+
);
|
|
1230
|
+
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
const convert = (
|
|
1235
|
+
value: unknown,
|
|
1236
|
+
style: RawStyleObject | undefined,
|
|
1237
|
+
): JSONContent | undefined => {
|
|
1238
|
+
if (!isPlainTextValue(value) || value.length === 0) return undefined;
|
|
1239
|
+
|
|
1240
|
+
try {
|
|
1241
|
+
// Pin the schema to v22 too: B1's structure and its attr-fill snapshot together define
|
|
1242
|
+
// the permanent v21 -> v22 result.
|
|
1243
|
+
const doc = plainTextToRteJson(value, parser, 22);
|
|
1244
|
+
|
|
1245
|
+
if (!doc) return undefined;
|
|
1246
|
+
|
|
1247
|
+
setBlockTextAlign(doc, style?.textAlign);
|
|
1248
|
+
|
|
1249
|
+
// Bake the block override + role typography onto the bare run (job C skipped this field
|
|
1250
|
+
// while it was a plain string) so the doc is self-describing for the maker toolbar.
|
|
1251
|
+
const roleStyle = style?.textStyleType
|
|
1252
|
+
? (options[style.textStyleType as TextStyleType]?.style as
|
|
1253
|
+
| MigrationRteTypography
|
|
1254
|
+
| undefined)
|
|
1255
|
+
: undefined;
|
|
1256
|
+
|
|
1257
|
+
// C3 (before the fill): pin a not-bold legacy block's weight as a literal so the fill can't
|
|
1258
|
+
// treat its `normal` as inert and let the run inherit a bold heading tier. The plain string had
|
|
1259
|
+
// no bold marks, so every run is bare — the fill then keeps this literal and completes the rest.
|
|
1260
|
+
pinMigration22RunWeight(doc, roleStyle?.fontWeight, style?.fontWeight);
|
|
1261
|
+
|
|
1262
|
+
return fillMigration22RunTypography(
|
|
1263
|
+
doc,
|
|
1264
|
+
roleStyle,
|
|
1265
|
+
style as MigrationRteTypography,
|
|
1266
|
+
);
|
|
1267
|
+
} catch (error) {
|
|
1268
|
+
// Leave the plain string; Path A keeps its colour resolvable.
|
|
1269
|
+
console.error(
|
|
1270
|
+
'migration-22: failed to convert plain RTE field, leaving as text',
|
|
1271
|
+
error,
|
|
1272
|
+
);
|
|
1273
|
+
|
|
1274
|
+
return undefined;
|
|
1275
|
+
}
|
|
1276
|
+
};
|
|
1277
|
+
|
|
1278
|
+
for (const page of brandPageModel.pages ?? []) {
|
|
1279
|
+
for (const content of page.contents ?? []) {
|
|
1280
|
+
const fields = content as unknown as Record<string, unknown>;
|
|
1281
|
+
|
|
1282
|
+
for (const styleField of rteStyleFieldsFor(content.type)) {
|
|
1283
|
+
const textField = pairedRteTextField(content.type, styleField);
|
|
1284
|
+
|
|
1285
|
+
if (!textField) continue;
|
|
1286
|
+
|
|
1287
|
+
const style = fields[styleField] as RawStyleObject | undefined;
|
|
1288
|
+
const doc = convert(fields[textField], style);
|
|
1289
|
+
|
|
1290
|
+
if (doc) fields[textField] = doc;
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
const textItems = fields.textItems as
|
|
1294
|
+
| { text?: unknown; textStyle?: RawStyleObject }[]
|
|
1295
|
+
| undefined;
|
|
1296
|
+
|
|
1297
|
+
if (Array.isArray(textItems)) {
|
|
1298
|
+
for (const item of textItems) {
|
|
1299
|
+
if (!item || typeof item !== 'object') continue;
|
|
1300
|
+
|
|
1301
|
+
const doc = convert(item.text, item.textStyle);
|
|
1302
|
+
|
|
1303
|
+
if (doc) item.text = doc;
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
};
|
|
1309
|
+
|
|
1310
|
+
export const migration22 = async (
|
|
1311
|
+
migrationResult: MigrationData,
|
|
1312
|
+
_options: MigrationOptions,
|
|
1313
|
+
): Promise<MigrationData> => {
|
|
1314
|
+
const { brandPageModel } = migrationResult;
|
|
1315
|
+
const options = brandPageModel.styles?.textStyles?.textStyleOptions;
|
|
1316
|
+
|
|
1317
|
+
if (options) {
|
|
1318
|
+
const targets = computeLegacyTargets(options); // per-site legacy → nearest tier
|
|
1319
|
+
|
|
1320
|
+
seedRoleColours(options, targets); // A — seed each old role's colour onto its tier
|
|
1321
|
+
seedRoleFonts(brandPageModel, options); // A2 — count the site's fonts onto each role family
|
|
1322
|
+
|
|
1323
|
+
const noRoleTextStyles = captureNoRoleTextStyles(brandPageModel);
|
|
1324
|
+
|
|
1325
|
+
reconnectContentRoles(brandPageModel, targets, options); // B — seed button styles, reconnect every text field
|
|
1326
|
+
stripLiftContentRte(brandPageModel, options); // C — RTE baked → override
|
|
1327
|
+
await convertPlainRteFields(brandPageModel, options); // B1 — plain text → Tiptap doc (after C)
|
|
1328
|
+
bakeElementColours(brandPageModel, options, noRoleTextStyles); // D — freeze resolved colour
|
|
1329
|
+
clearRoleColours(options); // E — retire role colour
|
|
1330
|
+
stripRteStyleColours(brandPageModel); // G — colour-decouple RTE style objects
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
deleteTextFontFamily(brandPageModel); // H — after every reader of the site text font
|
|
1334
|
+
|
|
1335
|
+
return migrationResult;
|
|
1336
|
+
};
|