@designcrowd/library.brand-page 6.0.3 → 6.0.4

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 (383) hide show
  1. package/lib/src/{CreatedOnBrandCrowd-de-de-BctSP4tp.js → CreatedOnBrandCrowd-de-de-Ruu5Az32.js} +3 -3
  2. package/lib/src/{CreatedOnBrandCrowd-en-us-BeNytcxt.js → CreatedOnBrandCrowd-en-us-DGUJtvld.js} +3 -3
  3. package/lib/src/{CreatedOnBrandCrowd-es-es-hY12f5TN.js → CreatedOnBrandCrowd-es-es-Dg-1uOfV.js} +3 -3
  4. package/lib/src/{CreatedOnBrandCrowd-fr-fr-C4SCU39P.js → CreatedOnBrandCrowd-fr-ca-CRKraZl8.js} +3 -3
  5. package/lib/src/{CreatedOnBrandCrowd-fr-ca-C4SCU39P.js → CreatedOnBrandCrowd-fr-fr-CRKraZl8.js} +3 -3
  6. package/lib/src/{CreatedOnBrandCrowd-pt-pt-C5J5kWiD.js → CreatedOnBrandCrowd-pt-br-bZ9VlEAu.js} +3 -3
  7. package/lib/src/{CreatedOnBrandCrowd-pt-br-C5J5kWiD.js → CreatedOnBrandCrowd-pt-pt-bZ9VlEAu.js} +3 -3
  8. package/lib/src/{CreatedOnDcom-de-de-DXCeZZLg.js → CreatedOnDcom-de-de-CJYfwZx8.js} +3 -3
  9. package/lib/src/{CreatedOnDcom-en-us-GeBTaofv.js → CreatedOnDcom-en-us-rzA1lVxH.js} +3 -3
  10. package/lib/src/{CreatedOnDcom-es-es-BiPmv1q-.js → CreatedOnDcom-es-es-CNwGPjik.js} +3 -3
  11. package/lib/src/{CreatedOnDcom-fr-ca-Cns_iX4-.js → CreatedOnDcom-fr-ca-DKw7OGIz.js} +3 -3
  12. package/lib/src/{CreatedOnDcom-fr-fr-CC-f_hUR.js → CreatedOnDcom-fr-fr-WzOEaAZG.js} +3 -3
  13. package/lib/src/{CreatedOnDcom-pt-br-DnhSuPKo.js → CreatedOnDcom-pt-br-DkualVhm.js} +3 -3
  14. package/lib/src/{CreatedOnDcom-pt-pt-BCgbUsqY.js → CreatedOnDcom-pt-pt-w4Nnsdzq.js} +3 -3
  15. package/lib/src/brand-page/clients/brand-page.client.d.ts +9 -9
  16. package/lib/src/brand-page/components/image-gallery/masonry/MasonryGallery.mixin.d.ts +2 -1
  17. package/lib/src/brand-page/components/navigation/hamburger/HamburgerNavigation.mixin.d.ts +2 -4
  18. package/lib/src/brand-page/components/navigation/hamburger/HamburgerNavigation.mixin.js +0 -1
  19. package/lib/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.mixin.d.ts +2 -4
  20. package/lib/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.mixin.js +4 -5
  21. package/lib/src/brand-page/components/page/Page.mixin.d.ts +1 -1
  22. package/lib/src/brand-page/components/page/PageComponent.mixin.d.ts +1 -1
  23. package/lib/src/brand-page/components/payment/Payment.mixin.d.ts +3 -3
  24. package/lib/src/brand-page/components/payment/__fixtures__/request-payment.d.ts +13 -13
  25. package/lib/src/brand-page/components/payment/__fixtures__/support-me.d.ts +13 -13
  26. package/lib/src/brand-page/constants/sections/footer-display.constants.js +1 -1
  27. package/lib/src/brand-page/models/index.d.ts +13 -2
  28. package/lib/src/brand-page/models/index.js +0 -3
  29. package/lib/src/brand-page/models/payment.model.d.ts +1 -1
  30. package/lib/src/brand-page/utils/personalisation.util.js +14 -72
  31. package/lib/src/brand-page-maker/components/action-bar/ActionBar.mixin.d.ts +19 -21
  32. package/lib/src/brand-page-maker/components/action-bar/ActionBar.mixin.js +0 -1
  33. package/lib/src/brand-page-maker/components/admin-background-settings/AdminBackgroundSettings.mixin.d.ts +10 -16
  34. package/lib/src/brand-page-maker/components/admin-background-settings/ShapePositionSlider.mixin.d.ts +1 -1
  35. package/lib/src/brand-page-maker/components/admin-logo-settings/AdminLogoSettings.mixin.d.ts +3 -3
  36. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +170 -181
  37. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.js +0 -3
  38. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.stories.js +0 -1
  39. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMakerTemplateSettings.mixin.d.ts +12 -12
  40. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMakerTemplateSettings.stories.js +2 -2
  41. package/lib/src/brand-page-maker/components/brand-page-maker/__fixtures__/brand-page-payment.js +7 -0
  42. package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.d.ts +149 -148
  43. package/lib/src/brand-page-maker/components/brandpage-settings/ReplaceImageSettingsForBrandPageSettings.mixin.d.ts +21 -21
  44. package/lib/src/brand-page-maker/components/brandpage-settings/ReplaceImageSettingsForBrandPageSettings.mixin.js +1 -1
  45. package/lib/src/brand-page-maker/components/color-picker/components/ColorModeSelect.mixin.d.ts +2 -4
  46. package/lib/src/brand-page-maker/components/color-picker/components/ColorModeSelect.mixin.js +0 -1
  47. package/lib/src/brand-page-maker/components/color-picker/components/GradientColorSlider.mixin.d.ts +2 -4
  48. package/lib/src/brand-page-maker/components/color-picker/components/GradientColorSlider.mixin.js +0 -1
  49. package/lib/src/brand-page-maker/components/color-picker/components/GradientColorSliderHandler.mixin.d.ts +2 -5
  50. package/lib/src/brand-page-maker/components/color-picker/components/GradientColorSliderHandler.mixin.js +0 -1
  51. package/lib/src/brand-page-maker/components/color-picker/components/GradientTypeSelector.mixin.d.ts +2 -4
  52. package/lib/src/brand-page-maker/components/color-picker/components/GradientTypeSelector.mixin.js +0 -1
  53. package/lib/src/brand-page-maker/components/device/Device.mixin.d.ts +2 -4
  54. package/lib/src/brand-page-maker/components/device/Device.mixin.js +0 -1
  55. package/lib/src/brand-page-maker/components/navigation/Navigation.mixin.d.ts +2 -4
  56. package/lib/src/brand-page-maker/components/navigation/Navigation.mixin.js +0 -1
  57. package/lib/src/brand-page-maker/components/preview/Preview.mixin.d.ts +3 -3
  58. package/lib/src/brand-page-maker/components/quick-link-bar/QuickLinkBar.mixin.d.ts +11 -11
  59. package/lib/src/brand-page-maker/components/seo-settings/SeoPageSettings.mixin.d.ts +25 -27
  60. package/lib/src/brand-page-maker/components/seo-settings/SeoPageSettings.mixin.js +0 -1
  61. package/lib/src/brand-page-maker/components/seo-settings/UrlSettings.mixin.d.ts +27 -27
  62. package/lib/src/brand-page-maker/components/seo-settings/UrlSettings.mixin.js +1 -1
  63. package/lib/src/brand-page-maker/components/seo-settings/UseExistingDomainModal.mixin.d.ts +12 -17
  64. package/lib/src/brand-page-maker/components/social-share/SocialShare.mixin.d.ts +12 -12
  65. package/lib/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.d.ts +32 -30
  66. package/lib/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.js +5 -16
  67. package/lib/src/brand-page-maker/components/upgrade-to-premium-button/UpgradeToPremiumButton.mixin.d.ts +11 -11
  68. package/lib/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.js +6 -3
  69. package/lib/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.d.ts +1 -1
  70. package/lib/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.js +6 -6
  71. package/lib/src/brand-page-maker/composables/useEventTracker.d.ts +2 -2
  72. package/lib/src/brand-page-maker/constants/config-settings-constant.d.ts +6 -6
  73. package/lib/src/brand-page-maker/constants/config-settings-constant.js +3 -3
  74. package/lib/src/brand-page-maker/convertors/text-image-display-style.convertor.d.ts +1 -1
  75. package/lib/src/brand-page-maker/convertors/text-image-display-style.convertor.js +1 -1
  76. package/lib/src/brand-page-maker/models/content-settings.d.ts +1 -1
  77. package/lib/src/brand-page-maker/models/navigation-style-configs.js +1 -1
  78. package/lib/src/brand-page-maker/routing/brand-page-maker-router.d.ts +2 -1
  79. package/lib/src/brand-page-maker/stores/ai-checkpoint/ai-checkpoint-store.d.ts +378 -366
  80. package/lib/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.d.ts +4 -5
  81. package/lib/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.js +5 -8
  82. package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.d.ts +1 -3
  83. package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.js +3 -7
  84. package/lib/src/brand-page-maker/stores/history/history-store.d.ts +15 -15
  85. package/lib/src/brand-page-maker/stores/ui/ui-store.d.ts +0 -6
  86. package/lib/src/brand-page-maker/stores/ui/ui-store.js +0 -8
  87. package/lib/src/brand-page-maker/utils/content-settings.util.js +1 -1
  88. package/lib/src/brand-page-maker/utils/rich-text-editor.util.d.ts +1 -1
  89. package/lib/src/brand-page-maker/utils/rich-text-editor.util.js +4 -4
  90. package/lib/src/clients/ai-assistant-client.d.ts +0 -7
  91. package/lib/src/clients/brand-page-client.d.ts +15 -16
  92. package/lib/src/clients/design-template-client.d.ts +0 -3
  93. package/lib/src/clients/media-client.d.ts +2 -2
  94. package/lib/src/clients/models.d.ts +0 -2
  95. package/lib/src/composables/useSnapshot.js +18 -18
  96. package/lib/src/css/library.brand-page-brandCrowd.min.css +324 -342
  97. package/lib/src/css/library.brand-page-brandPage.min.css +324 -620
  98. package/lib/src/css/library.brand-page-designCom.min.css +324 -342
  99. package/lib/src/css/library.brand-page-preview.min.css +324 -598
  100. package/lib/src/index.d.ts +1 -5
  101. package/lib/src/index.mjs +106 -110
  102. package/lib/src/{lib-Cw6L6CfQ.js → lib-Dy7MYyqz.js} +33240 -36457
  103. package/lib/src/lib.js +1 -11
  104. package/lib/src/metadata-builder/builders/footer-builder.js +62 -105
  105. package/lib/src/metadata-builder/builders/item-id-utils.d.ts +54 -0
  106. package/lib/src/metadata-builder/builders/item-id-utils.js +96 -0
  107. package/lib/src/metadata-builder/builders/section-builders/image-gallery-builder.js +57 -68
  108. package/lib/src/metadata-builder/builders/section-builders/images-section-builder.js +58 -93
  109. package/lib/src/metadata-builder/builders/section-builders/shapes-section-builder.js +66 -99
  110. package/lib/src/metadata-builder/builders/section-builders/social-links-builder.js +57 -17
  111. package/lib/src/metadata-builder/builders/section-builders/testimonial-section-builder.js +51 -85
  112. package/lib/src/metadata-builder/builders/section-builders/text-section-builder.js +49 -74
  113. package/lib/src/metadata-builder/builders/section-field-utils.d.ts +0 -7
  114. package/lib/src/metadata-builder/builders/section-field-utils.js +1 -1
  115. package/lib/src/metadata-builder/builders/shape-color-utils.js +1 -1
  116. package/lib/src/metadata-builder/parsers/ai-toolcall-parser.d.ts +16 -7
  117. package/lib/src/metadata-builder/parsers/section-tool-arguments.d.ts +66 -48
  118. package/lib/src/plugins/google-maps-plugin/google-maps-plugin.d.ts +1 -1
  119. package/lib/src/plugins/google-maps-plugin/google-maps-plugin.js +8 -8
  120. package/lib/src/shared/background-settings/components/background-shapes/BackgroundShapeSettings.mixin.d.ts +13 -13
  121. package/lib/src/shared/background-settings/store/background-settings-store.d.ts +3 -3
  122. package/lib/src/shared/color-picker/components/ColorPicker.mixin.d.ts +2 -4
  123. package/lib/src/shared/color-picker/components/ColorPicker.mixin.js +2 -2
  124. package/lib/src/shared/color-picker/components/ColorPickerSwatch.mixin.d.ts +2 -4
  125. package/lib/src/shared/color-picker/components/ColorPickerSwatch.mixin.js +0 -1
  126. package/lib/src/shared/confirm-modal/ConfirmModal.mixin.d.ts +2 -5
  127. package/lib/src/shared/confirm-modal/ConfirmModal.mixin.js +0 -1
  128. package/lib/src/shared/content-settings/components/GenericTextInput.mixin.d.ts +1 -0
  129. package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.d.ts +129 -131
  130. package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.js +1 -2
  131. package/lib/src/shared/content-settings/components/content-settings/ReplaceImageSettingsForContent.mixin.d.ts +110 -111
  132. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItem.mixin.d.ts +81 -84
  133. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItem.mixin.js +0 -5
  134. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.d.ts +109 -117
  135. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.js +14 -1
  136. package/lib/src/shared/content-settings/components/email-field/EmailField.mixin.d.ts +1 -0
  137. package/lib/src/shared/content-settings/components/focused-content-item-settings/FocusedContentItemSettings.mixin.d.ts +70 -72
  138. package/lib/src/shared/content-settings/components/focused-content-item-settings/FocusedContentItemSettings.mixin.js +3 -4
  139. package/lib/src/shared/content-settings/components/form-fields-field/FormFieldsField.mixin.d.ts +4 -4
  140. package/lib/src/shared/content-settings/components/icon-library/IconLibrary.mixin.d.ts +1 -3
  141. package/lib/src/shared/content-settings/components/icon-library/IconLibrary.mixin.js +0 -1
  142. package/lib/src/shared/content-settings/components/images-field/ImagesField.mixin.d.ts +2 -2
  143. package/lib/src/shared/content-settings/components/items-field/ItemsField.mixin.d.ts +3 -4
  144. package/lib/src/shared/content-settings/components/items-field/ItemsField.mixin.js +1 -1
  145. package/lib/src/shared/content-settings/components/payment-config-field/PaymentConfigField.mixin.d.ts +40 -40
  146. package/lib/src/shared/content-settings/components/payment-config-field/PaymentConfigOptionField.mixin.d.ts +6 -5
  147. package/lib/src/shared/content-settings/components/shape-field/ShapeField.mixin.d.ts +1 -0
  148. package/lib/src/shared/content-settings/components/slider-field/SliderField.mixin.d.ts +2 -2
  149. package/lib/src/shared/content-settings/components/tel-field/TelField.mixin.d.ts +1 -0
  150. package/lib/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.mixin.d.ts +2 -1
  151. package/lib/src/shared/content-settings/components/text-field/TextField.mixin.d.ts +1 -0
  152. package/lib/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.d.ts +2 -1
  153. package/lib/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.js +1 -1
  154. package/lib/src/shared/content-settings/components/text-style-field/TextStyleField.mixin.d.ts +28 -28
  155. package/lib/src/shared/content-settings/components/textarea-field/TextareaField.mixin.d.ts +1 -0
  156. package/lib/src/shared/content-settings/components/toggle-field/ToggleField.mixin.d.ts +2 -2
  157. package/lib/src/shared/content-settings/components/url-field/UrlField.mixin.d.ts +1 -0
  158. package/lib/src/shared/content-settings/components/youtube-video-field/YoutubeVideoField.mixin.d.ts +1 -0
  159. package/lib/src/shared/content-settings/store/content-settings-store.d.ts +39 -39
  160. package/lib/src/shared/content-settings/utils.js +1 -0
  161. package/lib/src/shared/font-family-selector/FontFamilySelector.mixin.d.ts +4 -6
  162. package/lib/src/shared/font-family-selector/FontFamilySelector.mixin.js +0 -1
  163. package/lib/src/shared/image-gallery-style-settings/ImageGalleryStyleSettings.mixin.d.ts +2 -4
  164. package/lib/src/shared/image-gallery-style-settings/ImageGalleryStyleSettings.mixin.js +0 -1
  165. package/lib/src/shared/logo-settings/components/EditLogoSettings.mixin.d.ts +21 -29
  166. package/lib/src/shared/logo-settings/components/EditLogoSettings.mixin.js +1 -1
  167. package/lib/src/shared/logo-settings/components/LogoSettings.mixin.d.ts +51 -50
  168. package/lib/src/shared/logo-settings/components/LogoSettingsHeader.mixin.d.ts +4 -6
  169. package/lib/src/shared/logo-settings/components/LogoSettingsHeader.mixin.js +0 -1
  170. package/lib/src/shared/logo-settings/store/editable-logo-store.js +1 -1
  171. package/lib/src/shared/logo-settings/store/logo-settings-store.d.ts +1 -1
  172. package/lib/src/shared/logo-versions/LogoVersions.mixin.d.ts +5 -13
  173. package/lib/src/shared/logo-versions/LogoVersions.mixin.js +0 -1
  174. package/lib/src/shared/models.d.ts +0 -2
  175. package/lib/src/shared/my-designs/MyDesigns.mixin.d.ts +6 -14
  176. package/lib/src/shared/my-designs/MyDesigns.mixin.js +0 -1
  177. package/lib/src/shared/my-uploads/MyMedia.mixin.d.ts +5 -14
  178. package/lib/src/shared/my-uploads/MyMedia.mixin.js +0 -1
  179. package/lib/src/shared/my-uploads/MyUploads.mixin.d.ts +5 -14
  180. package/lib/src/shared/my-uploads/MyUploads.mixin.js +0 -1
  181. package/lib/src/shared/navigation-style-settings/components/navigation-style-settings/NavigationStyleSettings.mixin.d.ts +3 -5
  182. package/lib/src/shared/navigation-style-settings/components/navigation-style-settings/NavigationStyleSettings.mixin.js +0 -1
  183. package/lib/src/shared/navigation-style-settings/store/index.d.ts +2 -2
  184. package/lib/src/shared/page-navigation/store/page-navigation-store.d.ts +2 -2
  185. package/lib/src/shared/page-settings/components/new-page-modal/NewPageModal.mixin.d.ts +28 -27
  186. package/lib/src/shared/page-settings/components/new-page-modal/NewPageModal.mixin.js +2 -0
  187. package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +179 -188
  188. package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +177 -181
  189. package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.js +1 -1
  190. package/lib/src/shared/page-settings/store/page-settings-store.d.ts +9 -9
  191. package/lib/src/shared/publish-brand-page-with-state/PublishBrandPageModalWithState.mixin.d.ts +39 -39
  192. package/lib/src/shared/publish-brand-page-with-state/PublishBrandPageModalWithState.mixin.js +1 -0
  193. package/lib/src/shared/side-bar/components/SideBar.mixin.d.ts +18 -18
  194. package/lib/src/shared/side-bar/components/SideBarTabButton.mixin.d.ts +6 -6
  195. package/lib/src/shared/style-settings/components/StyleSettings.mixin.d.ts +60 -66
  196. package/lib/src/shared/style-settings/store/style-settings-store.d.ts +1 -1
  197. package/lib/src/shared/style-settings/store/style-settings-store.js +1 -1
  198. package/lib/src/shared/template-settings/components/TemplatesSettings.mixin.d.ts +450 -20
  199. package/lib/src/shared/template-settings/components/TemplatesSettings.mixin.js +2 -2
  200. package/lib/src/shared/template-settings/models.d.ts +0 -1
  201. package/lib/src/shared/template-settings/store/template-settings-store.d.ts +0 -4
  202. package/lib/src/shared/template-settings/store/template-settings-store.js +11 -79
  203. package/lib/src/shared/unsplash-library/UnsplashLibrary.mixin.d.ts +4 -9
  204. package/lib/src/utils/build-asset-manifest.d.ts +0 -4
  205. package/lib/src/utils/build-asset-manifest.js +0 -8
  206. package/lib/src/utils/minify-brand-page-metadata.d.ts +2 -0
  207. package/lib/src/utils/minify-brand-page-metadata.js +17 -2
  208. package/package.json +56 -60
  209. package/src/brand-page/components/about-section/AboutSection.vue.test.ts +5 -7
  210. package/src/brand-page/components/brand-page-footer/BrandPageFooter.vue +1 -1
  211. package/src/brand-page/components/image/Image.mixin.ts +1 -1
  212. package/src/brand-page/components/image/Image.test.ts +1 -1
  213. package/src/brand-page/components/image-gallery/masonry/MasonryGallery.mixin.ts +2 -1
  214. package/src/brand-page/components/images-section/ImagesSection.vue +1 -0
  215. package/src/brand-page/components/logo/LogoRender.vue +188 -188
  216. package/src/brand-page/components/navigation/hamburger/HamburgerNavigation.mixin.ts +0 -1
  217. package/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.mixin.ts +6 -15
  218. package/src/brand-page/components/page-footer/PageFooter.vue +0 -1
  219. package/src/brand-page/components/page-header/HeaderLayout.vue +6 -12
  220. package/src/brand-page/components/page-header/PageHeader.vue +0 -1
  221. package/src/brand-page/components/shapes-section/ShapesSection.vue +1 -1
  222. package/src/brand-page/components/shop/CartModal.vue +3 -1
  223. package/src/brand-page/components/shop/FeaturedDisplay.vue +2 -2
  224. package/src/brand-page/components/shop/PaymentModal.vue +5 -1
  225. package/src/brand-page/components/shop/ProductDetailModal.vue +1 -1
  226. package/src/brand-page/components/shop/Shop.vue +1 -1
  227. package/src/brand-page/components/shop/TextDisplay.vue +1 -1
  228. package/src/brand-page/components/shop/ThumbnailDisplay.vue +2 -2
  229. package/src/brand-page/components/testimonial-section/TestimonialSection.vue +3 -3
  230. package/src/brand-page/components/text-wrapper/TextWrapper.vue +1 -3
  231. package/src/brand-page/constants/sections/footer-display.constants.ts +1 -1
  232. package/src/brand-page/models/index.ts +12 -4
  233. package/src/brand-page/models/payment.model.ts +1 -2
  234. package/src/brand-page/models/shop.model.ts +2 -0
  235. package/src/brand-page/models/user-personalisation.model.ts +2 -1
  236. package/src/brand-page/utils/__snapshots__/migration.util.test.ts.snap +0 -3
  237. package/src/brand-page/utils/__snapshots__/migration2.util.test.ts.snap +0 -1
  238. package/src/brand-page/utils/__snapshots__/model.util.test.ts.snap +0 -2
  239. package/src/brand-page/utils/migration/migration-01_1.ts +1 -1
  240. package/src/brand-page/utils/migration/migration-02.ts +1 -0
  241. package/src/brand-page/utils/migration/migration-03.ts +1 -0
  242. package/src/brand-page/utils/migration/migration-04.ts +1 -0
  243. package/src/brand-page/utils/migration/migration-05.ts +5 -1
  244. package/src/brand-page/utils/migration/migration-05_2.ts +1 -1
  245. package/src/brand-page/utils/migration/migration-08.test.ts +1 -1
  246. package/src/brand-page/utils/personalisation.util.test.ts +0 -35
  247. package/src/brand-page/utils/personalisation.util.ts +23 -119
  248. package/src/brand-page/view-models.ts +56 -38
  249. package/src/brand-page-maker/assessors/banner-section-description-visibility.assessor.ts +6 -6
  250. package/src/brand-page-maker/assessors/color-replacement-icon-visibility.assessor.ts +2 -2
  251. package/src/brand-page-maker/assessors/color-replacement-profile-visibility.assessor.ts +2 -2
  252. package/src/brand-page-maker/assessors/color-replacement-visibility.assessor.ts +2 -2
  253. package/src/brand-page-maker/assessors/contact-form-visibility.assessor.ts +14 -14
  254. package/src/brand-page-maker/components/action-bar/ActionBar.mixin.ts +0 -1
  255. package/src/brand-page-maker/components/ai-text-edit/TextBubble.vue +0 -1
  256. package/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.ts +1 -5
  257. package/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.stories.ts +0 -1
  258. package/src/brand-page-maker/components/brand-page-maker/BrandPageMakerTemplateSettings.stories.ts +5 -2
  259. package/src/brand-page-maker/components/brand-page-maker/__fixtures__/brand-page-examples.ts +5 -0
  260. package/src/brand-page-maker/components/brand-page-maker/__fixtures__/brand-page-payment.ts +8 -0
  261. package/src/brand-page-maker/components/brand-page-maker/__fixtures__/brand-site-template.examples.ts +2 -0
  262. package/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.vue +0 -1
  263. package/src/brand-page-maker/components/brandpage-settings/ReplaceImageSettingsForBrandPageSettings.mixin.ts +1 -1
  264. package/src/brand-page-maker/components/color-palette/BpColorPalette.vue +2 -1
  265. package/src/brand-page-maker/components/color-picker/components/ColorModeSelect.mixin.ts +0 -1
  266. package/src/brand-page-maker/components/color-picker/components/GradientColorSlider.mixin.ts +0 -1
  267. package/src/brand-page-maker/components/color-picker/components/GradientColorSliderHandler.mixin.ts +0 -1
  268. package/src/brand-page-maker/components/color-picker/components/GradientTypeSelector.mixin.ts +0 -1
  269. package/src/brand-page-maker/components/device/Device.mixin.ts +0 -1
  270. package/src/brand-page-maker/components/navigation/Navigation.mixin.ts +0 -1
  271. package/src/brand-page-maker/components/rich-text-editor/RichTextEditor.vue +1 -3
  272. package/src/brand-page-maker/components/sensitive-content/SensitiveContent.vue +4 -1
  273. package/src/brand-page-maker/components/seo-settings/DeleteDomainModal.vue +1 -1
  274. package/src/brand-page-maker/components/seo-settings/SeoPageSettings.mixin.ts +0 -1
  275. package/src/brand-page-maker/components/seo-settings/SeoPageSettings.vue +1 -1
  276. package/src/brand-page-maker/components/seo-settings/UrlSettings.mixin.ts +1 -0
  277. package/src/brand-page-maker/components/seo-settings/UrlSettings.vue +1 -0
  278. package/src/brand-page-maker/components/seo-settings/UseExistingDomainModal.vue +5 -1
  279. package/src/brand-page-maker/components/shop/AddOrEditProductContainer.vue +1 -1
  280. package/src/brand-page-maker/components/shop/ReplaceImageSettingsForShop.vue +1 -1
  281. package/src/brand-page-maker/components/social-share/SocialShare.vue +1 -1
  282. package/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.ts +8 -22
  283. package/src/brand-page-maker/components/template-preview/TemplatePreview.vue +10 -5
  284. package/src/brand-page-maker/components/video-play/VideoPlayButton.vue +1 -1
  285. package/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.ts +7 -3
  286. package/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.ts +6 -6
  287. package/src/brand-page-maker/constants/config-settings-constant.ts +5 -5
  288. package/src/brand-page-maker/convertors/text-image-display-style.convertor.ts +1 -1
  289. package/src/brand-page-maker/models/button-styles-config.ts +2 -4
  290. package/src/brand-page-maker/models/content-settings-configs.ts +2 -4
  291. package/src/brand-page-maker/models/navigation-style-configs.ts +6 -5
  292. package/src/brand-page-maker/stores/admin-logo-settings/admin-logo-settings-store.ts +1 -0
  293. package/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.ts +5 -8
  294. package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.test.ts +3 -1
  295. package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.ts +4 -12
  296. package/src/brand-page-maker/stores/ui/ui-store.ts +0 -16
  297. package/src/brand-page-maker/utils/content-settings.util.ts +1 -1
  298. package/src/brand-page-maker/utils/rich-text-editor.util.ts +4 -4
  299. package/src/clients/ai-assistant-client.ts +0 -7
  300. package/src/clients/design-template-client.ts +0 -3
  301. package/src/clients/models.ts +0 -2
  302. package/src/composables/useSnapshot.ts +22 -19
  303. package/src/lib.ts +0 -13
  304. package/src/metadata-builder/builders/footer-builder.test.ts +229 -130
  305. package/src/metadata-builder/builders/footer-builder.ts +90 -155
  306. package/src/metadata-builder/builders/item-id-utils.test.ts +184 -0
  307. package/src/metadata-builder/builders/item-id-utils.ts +117 -0
  308. package/src/metadata-builder/builders/section-builders/image-gallery-builder.ts +62 -88
  309. package/src/metadata-builder/builders/section-builders/images-section-builder.ts +66 -129
  310. package/src/metadata-builder/builders/section-builders/shapes-section-builder.ts +105 -160
  311. package/src/metadata-builder/builders/section-builders/social-links-builder.ts +65 -20
  312. package/src/metadata-builder/builders/section-builders/testimonial-section-builder.ts +83 -146
  313. package/src/metadata-builder/builders/section-builders/text-section-builder.ts +70 -122
  314. package/src/metadata-builder/builders/section-field-utils.ts +1 -1
  315. package/src/metadata-builder/builders/shape-color-utils.ts +2 -2
  316. package/src/metadata-builder/parsers/ai-toolcall-parser.ts +16 -7
  317. package/src/metadata-builder/parsers/section-tool-arguments.ts +66 -48
  318. package/src/plugins/google-maps-plugin/google-maps-plugin.ts +8 -8
  319. package/src/shared/background-settings/components/BackgroundSettings.vue +1 -1
  320. package/src/shared/background-settings/components/background-color/BackgroundColorSettings.vue +1 -1
  321. package/src/shared/color-picker/components/ColorPicker.mixin.ts +7 -2
  322. package/src/shared/color-picker/components/ColorPickerSwatch.mixin.ts +0 -1
  323. package/src/shared/common/NumberInput.vue +0 -1
  324. package/src/shared/common/SideBarSettingsHeader.vue +2 -2
  325. package/src/shared/confirm-modal/ConfirmModal.mixin.ts +0 -1
  326. package/src/shared/content-settings/components/booking-url-field/BookingUrlField.vue +0 -1
  327. package/src/shared/content-settings/components/button-configuration-field/ButtonConfigurationField.vue +0 -1
  328. package/src/shared/content-settings/components/color-field/ColorField.vue +10 -3
  329. package/src/shared/content-settings/components/content-settings/ContentSettings.mixin.ts +1 -1
  330. package/src/shared/content-settings/components/content-settings/ContentSettingsLockedButton.vue +0 -1
  331. package/src/shared/content-settings/components/content-settings-item/ContentSettingsItem.mixin.ts +0 -5
  332. package/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.ts +14 -1
  333. package/src/shared/content-settings/components/focused-content-item-settings/FocusedContentItemSettings.mixin.ts +6 -4
  334. package/src/shared/content-settings/components/icon-library/IconLibrary.mixin.ts +0 -1
  335. package/src/shared/content-settings/components/items-field/ItemsField.mixin.ts +1 -1
  336. package/src/shared/content-settings/components/logo-selection-field/LogoSelectionField.vue +1 -1
  337. package/src/shared/content-settings/components/logo-size-field/LogoSizeSliderField.vue +1 -1
  338. package/src/shared/content-settings/components/page-dropdown-field/PageDropdownField.vue +1 -1
  339. package/src/shared/content-settings/components/personalisation-role-field/PersonalisationRoleField.vue +4 -4
  340. package/src/shared/content-settings/components/text-image-field/TextImageField.vue +8 -2
  341. package/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.ts +1 -1
  342. package/src/shared/content-settings/models.ts +2 -3
  343. package/src/shared/content-settings/utils.ts +1 -0
  344. package/src/shared/dropzone-image-uploader/components/DropzoneImageUploader.vue +0 -1
  345. package/src/shared/ecommerce-settings/store/cart-store.test.ts +1 -1
  346. package/src/shared/font-family-selector/FontFamilySelector.mixin.ts +0 -1
  347. package/src/shared/image-gallery-style-settings/ImageGalleryStyleSettings.mixin.ts +0 -1
  348. package/src/shared/logo-settings/components/EditLogoSettings.mixin.ts +1 -1
  349. package/src/shared/logo-settings/components/LogoSettings.mixin.ts +1 -0
  350. package/src/shared/logo-settings/components/LogoSettingsHeader.mixin.ts +0 -1
  351. package/src/shared/logo-settings/components/LogoStyleSettingsWithState.vue +2 -2
  352. package/src/shared/logo-settings/components/LogoTemplateSearch.vue +1 -1
  353. package/src/shared/logo-settings/store/editable-logo-store.ts +1 -1
  354. package/src/shared/logo-settings/store/logo-settings-store.test.ts +4 -4
  355. package/src/shared/logo-versions/LogoVersions.mixin.ts +0 -1
  356. package/src/shared/models.ts +0 -6
  357. package/src/shared/my-designs/MyDesigns.mixin.ts +0 -1
  358. package/src/shared/my-uploads/MyMedia.mixin.ts +0 -1
  359. package/src/shared/my-uploads/MyMediaSelectionItem.vue +1 -2
  360. package/src/shared/my-uploads/MyUploads.mixin.ts +0 -1
  361. package/src/shared/my-uploads/models.ts +2 -1
  362. package/src/shared/navigation-style-settings/components/navigation-style-settings/NavigationStyleSettings.mixin.ts +0 -1
  363. package/src/shared/navigation-style-settings/components/navigation-style-settings/NavigationStyleSettings.vue +1 -1
  364. package/src/shared/page-settings/components/new-page-modal/NewPageModal.mixin.ts +3 -0
  365. package/src/shared/page-settings/components/page-settings/NestedDraggable.vue +1 -1
  366. package/src/shared/page-settings/components/page-settings/PageSettings.vue +2 -1
  367. package/src/shared/page-settings/components/page-settings-section-styles/HeaderDisplayStyle.vue +1 -1
  368. package/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.ts +1 -1
  369. package/src/shared/page-settings/store/page-settings-store.test.ts +8 -4
  370. package/src/shared/publish-brand-page-with-state/PublishBrandPageModalWithState.mixin.ts +1 -0
  371. package/src/shared/shape-library/ShapeLibrary.vue +2 -2
  372. package/src/shared/style-settings/store/style-settings-store.ts +1 -1
  373. package/src/shared/template-settings/components/TemplatesSettings.mixin.ts +2 -2
  374. package/src/shared/template-settings/models.ts +0 -1
  375. package/src/shared/template-settings/store/template-settings-store.ts +18 -105
  376. package/src/shared/unsplash-library/UnsplashLibrary.vue +6 -2
  377. package/src/shared/video-library/VideoLibrary.vue +1 -1
  378. package/src/utils/build-asset-manifest.ts +0 -12
  379. package/src/utils/minify-brand-page-metadata.ts +27 -7
  380. package/lib/src/brand-page/components/banner-section/BannerSection.mixin.d.ts +0 -103
  381. package/lib/src/brand-page/components/banner-section/BannerSection.mixin.js +0 -178
  382. package/src/brand-page/components/banner-section/BannerSection.mixin.ts +0 -210
  383. package/src/brand-page-maker/components/template-preview/TemplatePreviewAiBadge.vue +0 -12
@@ -10,22 +10,17 @@ import {
10
10
  import type {
11
11
  UpdateFooterArguments,
12
12
  ViewportFooterOverrides,
13
- FooterSocialLinkItem,
14
13
  } from '../parsers/ai-toolcall-parser';
15
14
  import type { Logger } from '../logger';
16
- import type {
17
- TextStyleDeclaration,
18
- TextStyleOptions,
19
- } from '../../brand-page/models/styles.model';
15
+ import type { TextStyleOptions } from '../../brand-page/models/styles.model';
20
16
  import { FOOTER_CHECKS } from './section-rules/footer.rules';
17
+ import { applyDetachedStyleOverride } from './section-field-utils';
21
18
  import {
22
- applyDetachedStyleOverride,
23
- normalizeStyleValue,
24
- } from './section-field-utils';
25
-
26
- type FooterSocialLink = NonNullable<
27
- AbstractPageFooterStyles['socialLinks']
28
- >[number];
19
+ ensureItemIds,
20
+ generateItemId,
21
+ removeItemsByIds,
22
+ repositionItemByAnchor,
23
+ } from './item-id-utils';
29
24
 
30
25
  const normaliseSocialUrl = (url: string): string => {
31
26
  for (const prefix of ['mailto:', 'tel:']) {
@@ -35,113 +30,6 @@ const normaliseSocialUrl = (url: string): string => {
35
30
  return url;
36
31
  };
37
32
 
38
- const makeSocialLink = (
39
- icon: string | undefined,
40
- url: string | undefined,
41
- ): FooterSocialLink =>
42
- ({
43
- imageUrl: icon || '',
44
- url: normaliseSocialUrl(url || ''),
45
- imageTitle: '',
46
- width: 0,
47
- height: 0,
48
- imageLink: '',
49
- }) as FooterSocialLink;
50
-
51
- const hasSocialLinkMutation = (args: UpdateFooterArguments): boolean =>
52
- args._replace_social_links === true ||
53
- (args._remove_social_link_indices?.length ?? 0) > 0 ||
54
- (args._reorder_social_links?.length ?? 0) > 0 ||
55
- (args.social_links?.length ?? 0) > 0;
56
-
57
- /**
58
- * Social links are a GLOBAL footer concern — one logical list that both
59
- * viewports render identically. Derive the new list ONCE from the current one
60
- * WITHOUT mutating the input, so the caller can assign an independent copy to
61
- * each viewport.
62
- *
63
- * Why this can't mutate per-viewport in place: footers migrated from the legacy
64
- * single `footerStyles` model (migration-01) share ONE `socialLinks` array
65
- * reference across pageFooterDesktop and pageFooterMobile, and `cloneDeep`
66
- * preserves that shared reference. Running an in-place `splice` once per
67
- * viewport therefore applies it TWICE to the same array — e.g. one requested
68
- * removal index drops two links. Compute-once + assign-fresh is correct for
69
- * both shared (legacy) and independent (new-model) footers.
70
- */
71
- const computeSocialLinks = (
72
- current: FooterSocialLink[] | undefined,
73
- args: UpdateFooterArguments,
74
- ): FooterSocialLink[] => {
75
- // _replace_social_links: replace entire list (empty/absent social_links = clear all).
76
- // A replacement supersedes every other social link mutation.
77
- if (args._replace_social_links) {
78
- return (args.social_links ?? []).map((l) =>
79
- makeSocialLink(l.social_link_icon, l.url),
80
- );
81
- }
82
-
83
- const links: FooterSocialLink[] = [...(current ?? [])];
84
-
85
- // item_index-based updates for existing links — replace the item reference
86
- // (never mutate the original object, which may be shared across viewports).
87
- for (const link of args.social_links ?? []) {
88
- if (link.item_index === undefined) continue;
89
-
90
- const existing = links[link.item_index];
91
-
92
- if (!existing) continue;
93
-
94
- links[link.item_index] = {
95
- ...existing,
96
- ...(link.social_link_icon !== undefined
97
- ? { imageUrl: link.social_link_icon }
98
- : {}),
99
- ...(link.url !== undefined ? { url: normaliseSocialUrl(link.url) } : {}),
100
- };
101
- }
102
-
103
- // Remove by index (descending order to preserve indices during splice).
104
- if (args._remove_social_link_indices?.length) {
105
- const sorted = [...args._remove_social_link_indices].sort((a, b) => b - a);
106
-
107
- for (const idx of sorted) {
108
- if (idx >= 0 && idx < links.length) links.splice(idx, 1);
109
- }
110
- }
111
-
112
- // Insert new links (descending insert_at order to preserve positions).
113
- const insertLinks = (args.social_links ?? []).filter(
114
- (l): l is FooterSocialLinkItem & { insert_at: number } =>
115
- l.insert_at !== undefined && l.item_index === undefined,
116
- );
117
- const sortedInserts = [...insertLinks].sort(
118
- (a, b) => b.insert_at - a.insert_at,
119
- );
120
-
121
- for (const link of sortedInserts) {
122
- const pos = Math.max(0, Math.min(link.insert_at, links.length));
123
-
124
- links.splice(pos, 0, makeSocialLink(link.social_link_icon, link.url));
125
- }
126
-
127
- // Reorder (requires ALL indices — partial reorder is not supported).
128
- if (args._reorder_social_links?.length) {
129
- const newOrder = args._reorder_social_links;
130
- const unique = new Set(newOrder);
131
-
132
- if (
133
- unique.size === newOrder.length &&
134
- newOrder.every((idx) => idx >= 0 && idx < links.length)
135
- ) {
136
- const reordered = newOrder.map((idx) => links[idx]);
137
-
138
- if (reordered.length === links.length) return reordered;
139
- }
140
- }
141
-
142
- return links;
143
- };
144
-
145
33
  const applyGlobalFooterChanges = (
146
34
  footer: AbstractPageFooterStyles,
147
35
  args: UpdateFooterArguments,
@@ -150,27 +38,85 @@ const applyGlobalFooterChanges = (
150
38
  if (args.copyright_text !== undefined)
151
39
  footer.copyrightText = args.copyright_text;
152
40
 
153
- // copyright_text_style is the same text-style object sections use (typography
154
- // + colour). Normalise each value the way section styles are normalised
155
- // (letterSpacing int→em, lineHeight snap, null→''), then detach from the
156
- // global preset via the override helper so a single call resolves the preset
157
- // and clears textStyleType once — see resolveGlobalPresetIntoStyle (BP-5420).
158
- if (args.copyright_text_style) {
159
- const updates: Partial<TextStyleDeclaration> = {};
41
+ if (args.copyright_text_font_family !== undefined)
42
+ footer.copyrightStyle = applyDetachedStyleOverride(
43
+ footer.copyrightStyle,
44
+ { fontFamily: args.copyright_text_font_family },
45
+ textStyleOptions,
46
+ );
160
47
 
161
- for (const [key, value] of Object.entries(args.copyright_text_style)) {
162
- const normalized = normalizeStyleValue(key, value);
48
+ const links = args.social_links;
163
49
 
164
- if (normalized !== undefined)
165
- (updates as Record<string, unknown>)[key] = normalized;
166
- }
50
+ // _replace_social_links: replace entire list (empty/absent social_links = clear all).
51
+ // When replacing, all other social link mutations (remove, insert, reorder) are skipped —
52
+ // they are superseded by the replacement and running them would cause duplicates.
53
+ if (args._replace_social_links) {
54
+ footer.socialLinks = (links ?? []).map((l) => ({
55
+ id: generateItemId(),
56
+ imageUrl: l.social_link_icon || '',
57
+ url: normaliseSocialUrl(l.url || ''),
58
+ imageTitle: '',
59
+ width: 0,
60
+ height: 0,
61
+ imageLink: '',
62
+ }));
63
+ } else {
64
+ // Targeted mutations — only run when NOT replacing the entire list.
65
+ // BP-5603: id-based contract (item_id / move_before_item_id / _remove_social_link_ids).
66
+ // Backfill ids on legacy links so they become referenceable after this call.
67
+ ensureItemIds(footer.socialLinks);
68
+
69
+ // Remove by id (order-independent — no index shifting needed).
70
+ footer.socialLinks = removeItemsByIds(
71
+ footer.socialLinks,
72
+ args._remove_social_link_ids,
73
+ );
167
74
 
168
- if (Object.keys(updates).length > 0)
169
- footer.copyrightStyle = applyDetachedStyleOverride(
170
- footer.copyrightStyle,
171
- updates,
172
- textStyleOptions,
173
- );
75
+ // Update existing links (item_id present) or insert new links (no item_id).
76
+ if (links?.length) {
77
+ footer.socialLinks = footer.socialLinks ?? [];
78
+
79
+ for (const link of links) {
80
+ if (link.item_id) {
81
+ // Update existing link in place; reposition if move_before_item_id set.
82
+ const existing = footer.socialLinks.find(
83
+ (l) => l.id === link.item_id,
84
+ );
85
+
86
+ // Unknown id — skip rather than guess at a position.
87
+ if (!existing) continue;
88
+
89
+ if (link.social_link_icon !== undefined)
90
+ existing.imageUrl = link.social_link_icon;
91
+
92
+ if (link.url !== undefined)
93
+ existing.url = normaliseSocialUrl(link.url);
94
+
95
+ repositionItemByAnchor(
96
+ footer.socialLinks,
97
+ existing,
98
+ link.move_before_item_id,
99
+ );
100
+ } else {
101
+ // New link — assign a stable id and position via move_before_item_id.
102
+ const newLink = {
103
+ id: generateItemId(),
104
+ imageUrl: link.social_link_icon || '',
105
+ url: normaliseSocialUrl(link.url || ''),
106
+ imageTitle: '',
107
+ width: 0,
108
+ height: 0,
109
+ imageLink: '' as string | false,
110
+ };
111
+
112
+ repositionItemByAnchor(
113
+ footer.socialLinks,
114
+ newLink,
115
+ link.move_before_item_id,
116
+ );
117
+ }
118
+ }
119
+ }
174
120
  }
175
121
  };
176
122
 
@@ -195,6 +141,12 @@ const applyViewportFooterChanges = (
195
141
  fill: overrides.icon_color,
196
142
  };
197
143
 
144
+ if (overrides.copyright_text_color !== undefined)
145
+ footer.copyrightStyle = {
146
+ ...footer.copyrightStyle,
147
+ color: overrides.copyright_text_color,
148
+ };
149
+
198
150
  if (overrides.is_logo_visible !== undefined)
199
151
  footer.isLogoVisible = overrides.is_logo_visible;
200
152
 
@@ -246,30 +198,13 @@ export class FooterBuilder extends BaseMetadataBuilder<UpdateFooterArguments> {
246
198
  const args = this.request.payload ?? {};
247
199
  const textStyleOptions = model.styles?.textStyles?.textStyleOptions;
248
200
 
249
- // Global scalar/style fields (copyright text + font) apply to both
250
- // viewports. These are idempotent assignments, safe to run per-viewport.
201
+ // Global fields (copyright_text, social links) always apply to both viewports.
251
202
  if (model.pageFooterDesktop)
252
203
  applyGlobalFooterChanges(model.pageFooterDesktop, args, textStyleOptions);
253
204
 
254
205
  if (model.pageFooterMobile)
255
206
  applyGlobalFooterChanges(model.pageFooterMobile, args, textStyleOptions);
256
207
 
257
- // Social links are a single global list. Derive the new list ONCE, then
258
- // assign an INDEPENDENT copy to each viewport — never mutate a (possibly
259
- // shared) viewport array in place. See computeSocialLinks.
260
- if (hasSocialLinkMutation(args)) {
261
- const current =
262
- model.pageFooterDesktop?.socialLinks ??
263
- model.pageFooterMobile?.socialLinks;
264
- const updated = computeSocialLinks(current, args);
265
-
266
- if (model.pageFooterDesktop)
267
- model.pageFooterDesktop.socialLinks = updated.map((l) => ({ ...l }));
268
-
269
- if (model.pageFooterMobile)
270
- model.pageFooterMobile.socialLinks = updated.map((l) => ({ ...l }));
271
- }
272
-
273
208
  // Viewport-specific styling via explicit sub-objects.
274
209
  if (args.desktop && model.pageFooterDesktop)
275
210
  applyViewportFooterChanges(model.pageFooterDesktop, args.desktop);
@@ -0,0 +1,184 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ END_ITEM_ANCHOR,
4
+ ensureBrandPageItemIds,
5
+ ensureItemIds,
6
+ generateItemId,
7
+ removeItemsByIds,
8
+ repositionItemByAnchor,
9
+ } from './item-id-utils';
10
+ import type { BrandPageModel } from '../../brand-page/models';
11
+
12
+ type Item = { id?: string; label: string };
13
+
14
+ const UUID_RE =
15
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
16
+
17
+ const items = (...labels: string[]): Item[] =>
18
+ labels.map((label) => ({ id: label, label }));
19
+
20
+ const labels = (list: Item[]): string[] => list.map((i) => i.label);
21
+
22
+ describe('generateItemId', () => {
23
+ it('returns unique uuid-shaped ids', () => {
24
+ const a = generateItemId();
25
+ const b = generateItemId();
26
+
27
+ expect(a).not.toBe(b);
28
+ expect(a).toMatch(
29
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
30
+ );
31
+ });
32
+ });
33
+
34
+ describe('ensureItemIds', () => {
35
+ it('backfills only the items missing an id, preserving existing ones', () => {
36
+ const list: Item[] = [
37
+ { id: 'keep', label: 'a' },
38
+ { label: 'b' },
39
+ { id: '', label: 'c' },
40
+ ];
41
+
42
+ ensureItemIds(list);
43
+
44
+ expect(list[0].id).toBe('keep');
45
+ expect(list[1].id).toBeTruthy();
46
+ expect(list[2].id).toBeTruthy();
47
+ expect(new Set(list.map((i) => i.id)).size).toBe(3);
48
+ });
49
+
50
+ it('is a no-op for undefined', () => {
51
+ expect(() => ensureItemIds(undefined)).not.toThrow();
52
+ });
53
+ });
54
+
55
+ describe('removeItemsByIds', () => {
56
+ it('removes by id regardless of order (no index shifting)', () => {
57
+ const result = removeItemsByIds(items('a', 'b', 'c', 'd'), ['c', 'a']);
58
+
59
+ expect(labels(result)).toEqual(['b', 'd']);
60
+ });
61
+
62
+ it('returns the list unchanged when no ids given', () => {
63
+ const list = items('a', 'b');
64
+
65
+ expect(removeItemsByIds(list, undefined)).toBe(list);
66
+ expect(removeItemsByIds(list, [])).toBe(list);
67
+ });
68
+
69
+ it('returns [] for undefined items', () => {
70
+ expect(removeItemsByIds(undefined, ['x'])).toEqual([]);
71
+ });
72
+ });
73
+
74
+ describe('repositionItemByAnchor', () => {
75
+ it('appends a new item when no anchor is given', () => {
76
+ const list = items('a', 'b');
77
+
78
+ repositionItemByAnchor(list, { id: 'new', label: 'new' }, undefined);
79
+
80
+ expect(labels(list)).toEqual(['a', 'b', 'new']);
81
+ });
82
+
83
+ it('leaves an existing item in place when no anchor is given', () => {
84
+ const list = items('a', 'b', 'c');
85
+
86
+ repositionItemByAnchor(list, list[1], undefined);
87
+
88
+ expect(labels(list)).toEqual(['a', 'b', 'c']);
89
+ });
90
+
91
+ it('inserts a new item before the anchor id', () => {
92
+ const list = items('a', 'b', 'c');
93
+
94
+ repositionItemByAnchor(list, { id: 'new', label: 'new' }, 'b');
95
+
96
+ expect(labels(list)).toEqual(['a', 'new', 'b', 'c']);
97
+ });
98
+
99
+ it('moves an existing item before the anchor id', () => {
100
+ const list = items('a', 'b', 'c', 'd');
101
+
102
+ // move 'd' before 'b'
103
+ repositionItemByAnchor(list, list[3], 'b');
104
+
105
+ expect(labels(list)).toEqual(['a', 'd', 'b', 'c']);
106
+ });
107
+
108
+ it('moves an existing item to the end with the __end__ sentinel', () => {
109
+ const list = items('a', 'b', 'c');
110
+
111
+ repositionItemByAnchor(list, list[0], END_ITEM_ANCHOR);
112
+
113
+ expect(labels(list)).toEqual(['b', 'c', 'a']);
114
+ });
115
+
116
+ it('falls through to the end when the anchor id is not found', () => {
117
+ const list = items('a', 'b');
118
+
119
+ repositionItemByAnchor(list, { id: 'new', label: 'new' }, 'missing');
120
+
121
+ expect(labels(list)).toEqual(['a', 'b', 'new']);
122
+ });
123
+ });
124
+
125
+ describe('ensureBrandPageItemIds', () => {
126
+ const makeModel = (): BrandPageModel =>
127
+ ({
128
+ pages: [
129
+ {
130
+ contents: [
131
+ {
132
+ type: 'image-gallery',
133
+ images: [{ imageUrl: 'a' }, { imageUrl: 'b' }],
134
+ },
135
+ {
136
+ type: 'testimonial-section',
137
+ items: [{ id: 'keep-me', name: 'X' }, { name: 'Y' }],
138
+ },
139
+ { type: 'text', textItems: [{ text: 'hi' }] },
140
+ { type: 'social-links', links: [{ url: 'u' }] },
141
+ { type: 'divider-section' }, // no item arrays — must not throw
142
+ ],
143
+ },
144
+ ],
145
+ pageFooterDesktop: { socialLinks: [{ url: 'f' }] },
146
+ pageFooterMobile: { socialLinks: [{ url: 'm' }] },
147
+ }) as unknown as BrandPageModel;
148
+
149
+ it('stamps a uuid id onto every id-less nested item across sections + footer', () => {
150
+ const model = makeModel();
151
+
152
+ ensureBrandPageItemIds(model);
153
+
154
+ const c = model.pages[0].contents as any[];
155
+
156
+ expect(c[0].images[0].id).toMatch(UUID_RE);
157
+ expect(c[0].images[1].id).toMatch(UUID_RE);
158
+ expect(c[1].items[1].id).toMatch(UUID_RE);
159
+ expect(c[2].textItems[0].id).toMatch(UUID_RE);
160
+ expect(c[3].links[0].id).toMatch(UUID_RE);
161
+ expect((model.pageFooterDesktop as any).socialLinks[0].id).toMatch(UUID_RE);
162
+ expect((model.pageFooterMobile as any).socialLinks[0].id).toMatch(UUID_RE);
163
+ });
164
+
165
+ it('preserves existing ids (idempotent)', () => {
166
+ const model = makeModel();
167
+
168
+ ensureBrandPageItemIds(model);
169
+ const first = (model.pages[0].contents as any[])[1].items[0].id;
170
+
171
+ ensureBrandPageItemIds(model);
172
+ const second = (model.pages[0].contents as any[])[1].items[0].id;
173
+
174
+ expect(first).toBe('keep-me');
175
+ expect(second).toBe('keep-me');
176
+ });
177
+
178
+ it('does not throw on an empty / partial model', () => {
179
+ expect(() => ensureBrandPageItemIds({} as BrandPageModel)).not.toThrow();
180
+ expect(() =>
181
+ ensureBrandPageItemIds({ pages: [] } as unknown as BrandPageModel),
182
+ ).not.toThrow();
183
+ });
184
+ });
@@ -0,0 +1,117 @@
1
+ import { v4 as uuidv4 } from 'uuid';
2
+ import type { BrandPageModel } from '../../brand-page/models';
3
+
4
+ /**
5
+ * Sentinel anchor meaning "the end of the list", mirroring the section-level
6
+ * `move_before_content_id: "__end__"` convention used in base-section-builder.
7
+ */
8
+ export const END_ITEM_ANCHOR = '__end__';
9
+
10
+ /** Generate a stable item id. Mirrors section-level `uuid.v4()` id generation. */
11
+ export const generateItemId = (): string => uuidv4();
12
+
13
+ /**
14
+ * Walk an entire BrandPageModel and backfill a stable uuid `id` onto every
15
+ * nested array item that lacks one (gallery images, images/shapes/testimonial
16
+ * items, text items, social links, and footer social links).
17
+ *
18
+ * BP-5603: this must run BEFORE the model is minified for the AI, so the same
19
+ * id the AI is shown (and copies into `item_id`) is the one the executor later
20
+ * matches. The maker holds a single live model instance for both the minify
21
+ * and the apply step, so mutating it here makes the ids round-trip; they then
22
+ * persist on the next save. Idempotent — existing ids are preserved.
23
+ */
24
+ export const ensureBrandPageItemIds = (model: BrandPageModel): void => {
25
+ const ensureContentItemIds = (content: Record<string, unknown>): void => {
26
+ // Every array field that the minifier surfaces as a list of items.
27
+ for (const field of ['images', 'items', 'textItems', 'links'] as const) {
28
+ const arr = content[field];
29
+
30
+ if (Array.isArray(arr)) ensureItemIds(arr as { id?: string }[]);
31
+ }
32
+ };
33
+
34
+ for (const page of model.pages ?? []) {
35
+ for (const content of page.contents ?? []) {
36
+ ensureContentItemIds(content as unknown as Record<string, unknown>);
37
+ }
38
+ }
39
+
40
+ ensureItemIds(model.pageFooterDesktop?.socialLinks);
41
+ ensureItemIds(model.pageFooterMobile?.socialLinks);
42
+ };
43
+
44
+ /**
45
+ * Backfill ids onto any items missing one. Per BP-5603 this runs in the
46
+ * metadata builder when a section is processed, so the persisted metadata
47
+ * returned to the client gains stable ids that subsequent AI tool calls can
48
+ * reference. Mutates in place.
49
+ */
50
+ export const ensureItemIds = <T extends { id?: string }>(
51
+ items: T[] | undefined,
52
+ ): void => {
53
+ if (!items) return;
54
+
55
+ for (const item of items) {
56
+ if (!item.id) item.id = generateItemId();
57
+ }
58
+ };
59
+
60
+ /**
61
+ * Remove items whose id appears in `ids`. Order-independent (no descending
62
+ * index sort needed) — the whole point of the id-based migration. Items
63
+ * without an id are never removed by id.
64
+ */
65
+ export const removeItemsByIds = <T extends { id?: string }>(
66
+ items: T[] | undefined,
67
+ ids: string[] | undefined,
68
+ ): T[] => {
69
+ if (!items?.length) return items ?? [];
70
+
71
+ if (!ids?.length) return items;
72
+
73
+ const toRemove = new Set(ids);
74
+
75
+ return items.filter((item) => !item.id || !toRemove.has(item.id));
76
+ };
77
+
78
+ /**
79
+ * Position `item` within `items` relative to an anchor id — the item-level
80
+ * mirror of base-section-builder's `moveContent`:
81
+ *
82
+ * - `moveBeforeItemId === undefined` → leave an existing item where it is;
83
+ * append a new (not-yet-present) item to the end.
84
+ * - `moveBeforeItemId === "__end__"` → move/insert at the very end.
85
+ * - anchor id found → insert immediately before it.
86
+ * - anchor id not found → fall through to the end (same
87
+ * forgiving behaviour as `moveContent`).
88
+ *
89
+ * Mutates `items` in place. `item` may be an existing element of `items`
90
+ * (a move) or a brand-new object (an insert).
91
+ */
92
+ export const repositionItemByAnchor = <T extends { id?: string }>(
93
+ items: T[],
94
+ item: T,
95
+ moveBeforeItemId: string | undefined,
96
+ ): void => {
97
+ const currentIndex = items.indexOf(item);
98
+
99
+ if (moveBeforeItemId === undefined) {
100
+ if (currentIndex === -1) items.push(item);
101
+
102
+ return;
103
+ }
104
+
105
+ if (currentIndex !== -1) items.splice(currentIndex, 1);
106
+
107
+ const anchorIndex =
108
+ moveBeforeItemId === END_ITEM_ANCHOR
109
+ ? -1
110
+ : items.findIndex((i) => i.id === moveBeforeItemId);
111
+
112
+ if (anchorIndex !== -1) {
113
+ items.splice(anchorIndex, 0, item);
114
+ } else {
115
+ items.push(item);
116
+ }
117
+ };