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