@designcrowd/library.brand-page 6.2.1 → 6.2.2

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 (106) hide show
  1. package/lib/src/{CreatedOnBrandCrowd-de-de-CtvPtYF6.js → CreatedOnBrandCrowd-de-de-7P2gNr-R.js} +1 -1
  2. package/lib/src/{CreatedOnBrandCrowd-en-us-eGF-EjAX.js → CreatedOnBrandCrowd-en-us-Dl7BymW6.js} +1 -1
  3. package/lib/src/{CreatedOnBrandCrowd-es-es-CSemsOtT.js → CreatedOnBrandCrowd-es-es-CO4FvWVw.js} +1 -1
  4. package/lib/src/{CreatedOnBrandCrowd-fr-fr-DzylX-AT.js → CreatedOnBrandCrowd-fr-ca-DHNqakQu.js} +1 -1
  5. package/lib/src/{CreatedOnBrandCrowd-fr-ca-DzylX-AT.js → CreatedOnBrandCrowd-fr-fr-DHNqakQu.js} +1 -1
  6. package/lib/src/{CreatedOnBrandCrowd-pt-pt-QMUxXQsE.js → CreatedOnBrandCrowd-pt-br-CGUC-ZbP.js} +1 -1
  7. package/lib/src/{CreatedOnBrandCrowd-pt-br-QMUxXQsE.js → CreatedOnBrandCrowd-pt-pt-CGUC-ZbP.js} +1 -1
  8. package/lib/src/{CreatedOnDcom-de-de-M7OoJt-1.js → CreatedOnDcom-de-de-Dvds-Vmy.js} +1 -1
  9. package/lib/src/{CreatedOnDcom-en-us-D7hMM0AI.js → CreatedOnDcom-en-us-fd3C-8Tp.js} +1 -1
  10. package/lib/src/{CreatedOnDcom-es-es-CLw-BLp2.js → CreatedOnDcom-es-es-DTXZW35-.js} +1 -1
  11. package/lib/src/{CreatedOnDcom-fr-ca-BRu_kmeu.js → CreatedOnDcom-fr-ca-DRCYOKPs.js} +1 -1
  12. package/lib/src/{CreatedOnDcom-fr-fr-IMnEv_yM.js → CreatedOnDcom-fr-fr-DpIB8x2x.js} +1 -1
  13. package/lib/src/{CreatedOnDcom-pt-br-Bt6v1p27.js → CreatedOnDcom-pt-br-Esnxy8wX.js} +1 -1
  14. package/lib/src/{CreatedOnDcom-pt-pt-BVsFJIJg.js → CreatedOnDcom-pt-pt-CaiVHFXR.js} +1 -1
  15. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +7 -1
  16. package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.d.ts +6 -0
  17. package/lib/src/brand-page-maker/composables/useEventTracker.d.ts +3 -0
  18. package/lib/src/brand-page-maker/composables/useEventTracker.js +10 -0
  19. package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.js +2 -4
  20. package/lib/src/{bundled-translations-KY0aSmMp.js → bundled-translations-BYUfWBT3.js} +9 -9
  21. package/lib/src/{bundled-translations.de-DE-BIFi4bYj.js → bundled-translations.de-DE-MmzUu1tt.js} +13 -13
  22. package/lib/src/{bundled-translations.es-ES-CHlKuPLO.js → bundled-translations.es-ES-D-tG33XL.js} +1 -1
  23. package/lib/src/{bundled-translations.fr-CA-DR1fvm8e.js → bundled-translations.fr-CA-CNc1H1h3.js} +1 -1
  24. package/lib/src/{bundled-translations.fr-FR-DnpWVIQg.js → bundled-translations.fr-FR-DX0-yepT.js} +1 -1
  25. package/lib/src/{bundled-translations.pt-BR-p8szTOiT.js → bundled-translations.pt-BR-ih1-LRgy.js} +9 -9
  26. package/lib/src/{bundled-translations.pt-PT-DEk39NhW.js → bundled-translations.pt-PT-Dq2CmD1p.js} +1 -1
  27. package/lib/src/bundles/bundled-translations.de-DE.json +3 -1
  28. package/lib/src/bundles/bundled-translations.es-ES.json +3 -1
  29. package/lib/src/bundles/bundled-translations.fr-CA.json +3 -1
  30. package/lib/src/bundles/bundled-translations.fr-FR.json +3 -1
  31. package/lib/src/bundles/bundled-translations.json +3 -1
  32. package/lib/src/bundles/bundled-translations.pt-BR.json +3 -1
  33. package/lib/src/bundles/bundled-translations.pt-PT.json +3 -1
  34. package/lib/src/composables/useTranslate.d.ts +1 -1
  35. package/lib/src/composables/useTranslate.js +1 -1
  36. package/lib/src/css/library.brand-page-brandCrowd.min.css +4 -4
  37. package/lib/src/css/library.brand-page-brandPage.min.css +4 -4
  38. package/lib/src/css/library.brand-page-designCom.min.css +4 -4
  39. package/lib/src/css/library.brand-page-preview.min.css +4 -4
  40. package/lib/src/index.mjs +2 -2
  41. package/lib/src/{lib-B38VVCSw.js → lib-V7aqfuiB.js} +18106 -17965
  42. package/lib/src/metadata-builder/builders/item-id-utils.d.ts +2 -0
  43. package/lib/src/metadata-builder/builders/item-id-utils.js +8 -2
  44. package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.d.ts +8 -1
  45. package/lib/src/shared/content-settings/components/content-settings/ReplaceImageSettingsForContent.mixin.d.ts +6 -0
  46. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItem.mixin.d.ts +1 -0
  47. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.d.ts +5 -0
  48. package/lib/src/shared/content-settings/components/payment-config-field/PaymentConfigField.mixin.d.ts +5 -0
  49. package/lib/src/shared/ecommerce-settings/util/refreshDuplicatedMetadata.d.ts +9 -1
  50. package/lib/src/shared/ecommerce-settings/util/refreshDuplicatedMetadata.js +31 -9
  51. package/lib/src/shared/logo-settings/components/EditLogoSettings.mixin.d.ts +1 -0
  52. package/lib/src/shared/logo-settings/components/LogoSettings.mixin.d.ts +5 -0
  53. package/lib/src/shared/page-navigation/store/page-navigation-store.d.ts +2 -1
  54. package/lib/src/shared/page-navigation/store/page-navigation-store.js +22 -9
  55. package/lib/src/shared/page-settings/components/new-page-modal/NewPageModal.mixin.d.ts +7 -1
  56. package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +10 -1
  57. package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.js +12 -35
  58. package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +6 -0
  59. package/lib/src/shared/page-settings/duplicate-page.util.d.ts +4 -0
  60. package/lib/src/shared/page-settings/duplicate-page.util.js +82 -0
  61. package/lib/src/shared/page-settings/models.d.ts +1 -0
  62. package/lib/src/shared/page-settings/store/page-settings-store.d.ts +5 -0
  63. package/lib/src/shared/page-settings/store/page-settings-store.js +90 -7
  64. package/lib/src/shared/page-settings/utils.d.ts +3 -0
  65. package/lib/src/shared/page-settings/utils.js +6 -0
  66. package/lib/src/shared/publish-brand-page-with-state/PublishBrandPageModalWithState.mixin.d.ts +1 -0
  67. package/lib/src/shared/side-bar/components/SideBarTabButton.mixin.d.ts +1 -0
  68. package/lib/src/shared/style-settings/components/StyleSettings.mixin.d.ts +1 -0
  69. package/lib/src/shared/template-settings/components/TemplatesSettings.mixin.d.ts +1 -0
  70. package/lib/src/tracker.mjs +1 -1
  71. package/lib/src/{useEventTracker-C5Ql5p7u.js → useEventTracker-DwPTmzpa.js} +13 -3
  72. package/package.json +1 -1
  73. package/src/brand-page-maker/composables/useEventTracker.ts +13 -0
  74. package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.ts +2 -4
  75. package/src/bundles/bundled-translations.de-DE.json +3 -1
  76. package/src/bundles/bundled-translations.es-ES.json +3 -1
  77. package/src/bundles/bundled-translations.fr-CA.json +3 -1
  78. package/src/bundles/bundled-translations.fr-FR.json +3 -1
  79. package/src/bundles/bundled-translations.json +3 -1
  80. package/src/bundles/bundled-translations.pt-BR.json +3 -1
  81. package/src/bundles/bundled-translations.pt-PT.json +3 -1
  82. package/src/composables/useTranslate.ts +2 -1
  83. package/src/metadata-builder/builders/item-id-utils.ts +9 -2
  84. package/src/shared/ecommerce-settings/components/product-config-field/ProductConfigOption.vue +0 -2
  85. package/src/shared/ecommerce-settings/util/refreshDuplicatedMetadata.ts +32 -10
  86. package/src/shared/page-navigation/store/page-navigation-store.ts +41 -13
  87. package/src/shared/page-settings/components/page-settings/NestedDraggable.vue +3 -0
  88. package/src/shared/page-settings/components/page-settings/PageElement.vue +2 -0
  89. package/src/shared/page-settings/components/page-settings/PageSettingMenu.test.ts +105 -0
  90. package/src/shared/page-settings/components/page-settings/PageSettingMenu.vue +16 -0
  91. package/src/shared/page-settings/components/page-settings/PageSettings.duplicate.test.ts +108 -0
  92. package/src/shared/page-settings/components/page-settings/PageSettings.mixin.ts +22 -50
  93. package/src/shared/page-settings/components/page-settings/PageSettings.vue +1 -0
  94. package/src/shared/page-settings/components/page-settings/i18n/page-settings.de-DE.json +3 -1
  95. package/src/shared/page-settings/components/page-settings/i18n/page-settings.es-ES.json +3 -1
  96. package/src/shared/page-settings/components/page-settings/i18n/page-settings.fr-CA.json +3 -1
  97. package/src/shared/page-settings/components/page-settings/i18n/page-settings.fr-FR.json +3 -1
  98. package/src/shared/page-settings/components/page-settings/i18n/page-settings.json +3 -1
  99. package/src/shared/page-settings/components/page-settings/i18n/page-settings.pt-BR.json +3 -1
  100. package/src/shared/page-settings/components/page-settings/i18n/page-settings.pt-PT.json +3 -1
  101. package/src/shared/page-settings/duplicate-page.util.test.ts +382 -0
  102. package/src/shared/page-settings/duplicate-page.util.ts +124 -0
  103. package/src/shared/page-settings/models.ts +1 -0
  104. package/src/shared/page-settings/store/page-settings-store.test.ts +512 -1
  105. package/src/shared/page-settings/store/page-settings-store.ts +145 -11
  106. package/src/shared/page-settings/utils.ts +10 -0
@@ -54,6 +54,8 @@ export declare const useUiPageSettingsStore: import("pinia").StoreDefinition<"ui
54
54
  setPages({ pages }: {
55
55
  pages: ContentPage[];
56
56
  }): void;
57
+ syncPagesWithOrders(): void;
58
+ showPageLimitUpgradeModalAsync(ctaLocation: string): Promise<void>;
57
59
  setNewPageModalVisibilityAsync({ isNewPageModalVisible, parentPageId, }: {
58
60
  isNewPageModalVisible: boolean;
59
61
  parentPageId?: string;
@@ -103,6 +105,9 @@ export declare const useUiPageSettingsStore: import("pinia").StoreDefinition<"ui
103
105
  title: string;
104
106
  newPageParentId?: string;
105
107
  }): ContentPage;
108
+ duplicatePage({ pageId, }: {
109
+ pageId: string;
110
+ }): Promise<ContentPage | undefined>;
106
111
  setSelectedSectionName({ sectionName, }: {
107
112
  sectionName?: PageSettingsSection;
108
113
  }): void;
@@ -2,6 +2,8 @@ import { defineStore } from 'pinia';
2
2
  import { PageHeaderDesktop, PageHeaderMobile, PageFooterDesktop, PageFooterMobile, VIEWPORTS, getNavigationType, } from '@brand-page';
3
3
  import { v4 as uuid } from 'uuid';
4
4
  import { PAGE_SECTIONS } from '../models';
5
+ import { toPageModels } from '../utils';
6
+ import { duplicatePageContents, getDuplicatePageTitle, } from '../duplicate-page.util';
5
7
  import { navigateToPageByPageId } from '@brand-page-maker/routing';
6
8
  import { getPageSlugFromPageTitle } from '@brand-page-maker/utils';
7
9
  import { useUiContentSettingsStore } from '../../content-settings/store';
@@ -78,16 +80,55 @@ export const useUiPageSettingsStore = defineStore('uiPageSettings', {
78
80
  setPages({ pages }) {
79
81
  this.pages = pages;
80
82
  },
81
- async setNewPageModalVisibilityAsync({ isNewPageModalVisible, parentPageId, }) {
83
+ // `orders` is the hierarchy; `pages` is a flat list that has to match it
84
+ // depth-first, because row indexes drive the home check and the hide/delete
85
+ // gating. Anything reshaping orders should re-derive pages after - except
86
+ // addNewPage, which predates this and appends by visibility instead; the
87
+ // next drag-end catches it up.
88
+ syncPagesWithOrders() {
89
+ const pageNavigationStore = usePageNavigationStore();
90
+ const pageIdOrder = [];
91
+ const collectPageIds = (orders) => {
92
+ for (const order of orders) {
93
+ const route = pageNavigationStore.routes.find((r) => r.id === order.routeId);
94
+ if (route?.pageId) {
95
+ pageIdOrder.push(route.pageId);
96
+ }
97
+ if (order.subRoutes?.length) {
98
+ collectPageIds(order.subRoutes);
99
+ }
100
+ }
101
+ };
102
+ collectPageIds(pageNavigationStore.orders);
103
+ if (!pageIdOrder.length) {
104
+ return;
105
+ }
106
+ const pageMap = new Map(this.pages.map((page) => [page.id, page]));
107
+ const orderedPages = [];
108
+ for (const pageId of pageIdOrder) {
109
+ const page = pageMap.get(pageId);
110
+ if (page) {
111
+ orderedPages.push(page);
112
+ pageMap.delete(pageId);
113
+ }
114
+ }
115
+ // A page with no route would otherwise vanish.
116
+ orderedPages.push(...pageMap.values());
117
+ this.setPages({ pages: orderedPages });
118
+ },
119
+ async showPageLimitUpgradeModalAsync(ctaLocation) {
82
120
  const uiStore = useUiStore();
83
121
  const eventTracker = useEventTracker();
122
+ eventTracker.setUpsellBridgeState({
123
+ cta_location: ctaLocation,
124
+ trigger_type: TRIGGER_TYPE.AUTOMATIC,
125
+ content_type: null,
126
+ });
127
+ await uiStore.showUpgradeModalAndSaveAsync(WEBSITE_UPGRADE_CONTEXT_TYPES.AddPage);
128
+ },
129
+ async setNewPageModalVisibilityAsync({ isNewPageModalVisible, parentPageId, }) {
84
130
  if (isNewPageModalVisible && !this.canAddPage) {
85
- eventTracker.setUpsellBridgeState({
86
- cta_location: UPGRADE_CTA_LOCATIONS.ADD_NEW_PAGE,
87
- trigger_type: TRIGGER_TYPE.AUTOMATIC,
88
- content_type: null,
89
- });
90
- await uiStore.showUpgradeModalAndSaveAsync(WEBSITE_UPGRADE_CONTEXT_TYPES.AddPage);
131
+ await this.showPageLimitUpgradeModalAsync(UPGRADE_CTA_LOCATIONS.ADD_NEW_PAGE);
91
132
  return;
92
133
  }
93
134
  this.isNewPageModalVisible = isNewPageModalVisible;
@@ -231,6 +272,48 @@ export const useUiPageSettingsStore = defineStore('uiPageSettings', {
231
272
  }
232
273
  return newPage;
233
274
  },
275
+ async duplicatePage({ pageId, }) {
276
+ const pageNavigationStore = usePageNavigationStore();
277
+ // Gate first: a paywalled duplicate must not leave a half-created page.
278
+ if (!this.canAddPage) {
279
+ await this.showPageLimitUpgradeModalAsync(UPGRADE_CTA_LOCATIONS.DUPLICATE_PAGE);
280
+ return undefined;
281
+ }
282
+ const sourcePage = this.pages.find((page) => page.id === pageId);
283
+ const sourceRoute = pageNavigationStore.routes.find((route) => route.pageId === pageId);
284
+ if (!sourcePage || !sourceRoute) {
285
+ return undefined;
286
+ }
287
+ const parentRouteId = pageNavigationStore.getParentRouteIdForPage(pageId);
288
+ const title = getDuplicatePageTitle(sourcePage.title, pageNavigationStore.getDisplayNamesForDepth(parentRouteId), Boolean(parentRouteId));
289
+ const newPage = {
290
+ id: uuid(),
291
+ title,
292
+ contents: duplicatePageContents(sourcePage.contents),
293
+ // A copy of a hidden page stays hidden, or it publishes into the nav.
294
+ isHidden: sourcePage.isHidden,
295
+ };
296
+ // addNavRoute navigates to the copy and reads its contents, so the page
297
+ // has to exist before the route does. syncPagesWithOrders places it.
298
+ this.setPages({ pages: [...this.pages, newPage] });
299
+ const newRoute = {
300
+ id: uuid(),
301
+ pageId: newPage.id,
302
+ pageSlug: getPageSlugFromPageTitle(title, pageNavigationStore.getPageSlugsForDepth(parentRouteId)),
303
+ isHidden: Boolean(sourceRoute.isHidden),
304
+ displayName: title,
305
+ };
306
+ pageNavigationStore.addNavRoute(newRoute, parentRouteId, sourceRoute.id);
307
+ this.syncPagesWithOrders();
308
+ // shopConfig.products is DERIVED from the shop blocks, not updated with
309
+ // them - without this the copy's fresh product ids never reach the save.
310
+ const shopConfigStore = useShopConfigStore();
311
+ shopConfigStore.initialiseShopStore({
312
+ paymentConfig: shopConfigStore.paymentConfig,
313
+ pages: toPageModels(this.pages),
314
+ });
315
+ return newPage;
316
+ },
234
317
  setSelectedSectionName({ sectionName, }) {
235
318
  this.selectedSectionName = sectionName;
236
319
  if (!sectionName) {
@@ -1,3 +1,6 @@
1
+ import type { PageModel } from '@brand-page';
2
+ import type { ContentPage } from './models';
1
3
  export declare const MAX_PAGE_TITLE_LENGTH = 30;
2
4
  export declare const MAX_SUBPAGE_TITLE_LENGTH = 20;
5
+ export declare const toPageModels: (pages: ContentPage[]) => PageModel[];
3
6
  export declare const getPageTitleError: (pageTitle: string, isSubPage: boolean) => string | null;
@@ -1,6 +1,12 @@
1
1
  import { errorTr } from '@src/composables/useTranslate';
2
2
  export const MAX_PAGE_TITLE_LENGTH = 30;
3
3
  export const MAX_SUBPAGE_TITLE_LENGTH = 20;
4
+ // The maker holds a page's sections as ContentItems (settings wrapper + content);
5
+ // everything that consumes a BrandPageModel wants the bare content.
6
+ export const toPageModels = (pages) => pages.map((page) => ({
7
+ ...page,
8
+ contents: page.contents.map((item) => item.settings.content),
9
+ }));
4
10
  export const getPageTitleError = (pageTitle, isSubPage) => {
5
11
  if (!pageTitle) {
6
12
  return errorTr('pageTitleError');
@@ -650,6 +650,7 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
650
650
  sendWebsiteManageAddPageEvent: (pageName: string, isSubPageMode?: boolean) => void;
651
651
  sendWebsiteManageRemovePageEvent: (pageName: string, isRouteNested?: boolean) => void;
652
652
  sendWebsiteManageReorderPageEvent: (pageName: string) => void;
653
+ sendWebsiteManageDuplicatePageEvent: (sourcePageName: string, isSubPage?: boolean) => void;
653
654
  sendPublishOptionsForFreeEvent: () => void;
654
655
  sendPublishOptionsForPremiumEvent: () => void;
655
656
  sendPublishOptionsForExitEvent: () => void;
@@ -60,6 +60,7 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
60
60
  sendWebsiteManageAddPageEvent: (pageName: string, isSubPageMode?: boolean) => void;
61
61
  sendWebsiteManageRemovePageEvent: (pageName: string, isRouteNested?: boolean) => void;
62
62
  sendWebsiteManageReorderPageEvent: (pageName: string) => void;
63
+ sendWebsiteManageDuplicatePageEvent: (sourcePageName: string, isSubPage?: boolean) => void;
63
64
  sendPublishOptionsForFreeEvent: () => void;
64
65
  sendPublishOptionsForPremiumEvent: () => void;
65
66
  sendPublishOptionsForExitEvent: () => void;
@@ -994,6 +994,7 @@ declare const _default: import("vue").DefineComponent<{}, {
994
994
  sendWebsiteManageAddPageEvent: (pageName: string, isSubPageMode?: boolean) => void;
995
995
  sendWebsiteManageRemovePageEvent: (pageName: string, isRouteNested?: boolean) => void;
996
996
  sendWebsiteManageReorderPageEvent: (pageName: string) => void;
997
+ sendWebsiteManageDuplicatePageEvent: (sourcePageName: string, isSubPage?: boolean) => void;
997
998
  sendPublishOptionsForFreeEvent: () => void;
998
999
  sendPublishOptionsForPremiumEvent: () => void;
999
1000
  sendPublishOptionsForExitEvent: () => void;
@@ -175,6 +175,7 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
175
175
  sendWebsiteManageAddPageEvent: (pageName: string, isSubPageMode?: boolean) => void;
176
176
  sendWebsiteManageRemovePageEvent: (pageName: string, isRouteNested?: boolean) => void;
177
177
  sendWebsiteManageReorderPageEvent: (pageName: string) => void;
178
+ sendWebsiteManageDuplicatePageEvent: (sourcePageName: string, isSubPage?: boolean) => void;
178
179
  sendPublishOptionsForFreeEvent: () => void;
179
180
  sendPublishOptionsForPremiumEvent: () => void;
180
181
  sendPublishOptionsForExitEvent: () => void;
@@ -1,4 +1,4 @@
1
- import { A as N, E as O, G as _, P as I, a as A, b as a, c as S, S as s, T as C, U as L, u as P } from "./useEventTracker-C5Ql5p7u.js";
1
+ import { A as N, E as O, G as _, P as I, a as A, b as a, c as S, S as s, T as C, U as L, u as P } from "./useEventTracker-DwPTmzpa.js";
2
2
  export {
3
3
  N as AI_CTA_LOCATIONS,
4
4
  O as EVENTS,
@@ -9,6 +9,7 @@ const E = {
9
9
  WEBSITE_MAKER_ADD_PAGE: "website_maker_add_page",
10
10
  WEBSITE_MAKER_REMOVE_PAGE: "website_maker_remove_page",
11
11
  WEBSITE_MAKER_REORDER_PAGE: "website_maker_reorder_page",
12
+ WEBSITE_MAKER_DUPLICATE_PAGE: "website_maker_duplicate_page",
12
13
  WEBSITE_MAKER_OPEN: "website_maker_open",
13
14
  WEBSITE_MAKER_PUBLISH_UPSELL_OPTION_MODAL: "website_maker_publish_upsell_option_modal",
14
15
  WEBSITE_MAKER_TEMPLATE_SEARCH: "website_maker_template_search",
@@ -62,6 +63,7 @@ const E = {
62
63
  REMOVE_WATERMARK_FOOTER_SETTINGS: "remove_watermark_footer_settings",
63
64
  PREVIEW_WATERMARK: "preview_watermark",
64
65
  ADD_NEW_PAGE: "add_new_page",
66
+ DUPLICATE_PAGE: "duplicate_page",
65
67
  ADD_CONTENT: "add_content",
66
68
  EDITOR_PUBLISH_TOP: "editor_publish_top",
67
69
  EDITOR_PUBLISH_BOTTOM: "editor_publish_bottom",
@@ -99,14 +101,14 @@ const E = {
99
101
  upsellOpenEventProps: {},
100
102
  domainUpsellOpenEventProps: {},
101
103
  publishOpenEventProps: {}
102
- }), l = I([]), _ = (o, A, e = !1, t = "maker_editor") => {
104
+ }), l = I([]), _ = (A, o, e = !1, t = "maker_editor") => {
103
105
  l.value.forEach((s) => {
104
106
  s({
105
107
  scope: t,
106
- eventName: o,
108
+ eventName: A,
107
109
  eventProps: {
108
110
  ...e ? {} : T.value,
109
- ...A
111
+ ...o
110
112
  }
111
113
  });
112
114
  });
@@ -216,6 +218,14 @@ const E = {
216
218
  page_name: e
217
219
  });
218
220
  },
221
+ // The SOURCE page, not the copy: tracking the copy would fragment the
222
+ // dimension across "About Copy", "About Copy 2".
223
+ sendWebsiteManageDuplicatePageEvent: (e, t) => {
224
+ _(E.WEBSITE_MAKER_DUPLICATE_PAGE, {
225
+ page_name: e,
226
+ is_sub_page: !!t
227
+ });
228
+ },
219
229
  sendPublishOptionsForFreeEvent: () => {
220
230
  _(E.WEBSITE_MAKER_PUBLISH_UPSELL_OPTION_MODAL, {
221
231
  upsell_option_continuation: i.FREEMIUM
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@designcrowd/library.brand-page",
3
3
  "description": "A collection of Maker/Designer applications",
4
- "version": "6.2.1",
4
+ "version": "6.2.2",
5
5
  "author": "Design.com Eng",
6
6
  "license": "UNLICENSED",
7
7
  "type": "module",
@@ -39,6 +39,7 @@ export const EVENTS = {
39
39
  WEBSITE_MAKER_ADD_PAGE: 'website_maker_add_page',
40
40
  WEBSITE_MAKER_REMOVE_PAGE: 'website_maker_remove_page',
41
41
  WEBSITE_MAKER_REORDER_PAGE: 'website_maker_reorder_page',
42
+ WEBSITE_MAKER_DUPLICATE_PAGE: 'website_maker_duplicate_page',
42
43
  WEBSITE_MAKER_OPEN: 'website_maker_open',
43
44
  WEBSITE_MAKER_PUBLISH_UPSELL_OPTION_MODAL:
44
45
  'website_maker_publish_upsell_option_modal',
@@ -107,6 +108,7 @@ export const UPGRADE_CTA_LOCATIONS = {
107
108
  REMOVE_WATERMARK_FOOTER_SETTINGS: 'remove_watermark_footer_settings',
108
109
  PREVIEW_WATERMARK: 'preview_watermark',
109
110
  ADD_NEW_PAGE: 'add_new_page',
111
+ DUPLICATE_PAGE: 'duplicate_page',
110
112
  ADD_CONTENT: 'add_content',
111
113
  EDITOR_PUBLISH_TOP: 'editor_publish_top',
112
114
  EDITOR_PUBLISH_BOTTOM: 'editor_publish_bottom',
@@ -346,6 +348,17 @@ const useEventTracker = () => {
346
348
  page_name: pageName,
347
349
  });
348
350
  },
351
+ // The SOURCE page, not the copy: tracking the copy would fragment the
352
+ // dimension across "About Copy", "About Copy 2".
353
+ sendWebsiteManageDuplicatePageEvent: (
354
+ sourcePageName: string,
355
+ isSubPage?: boolean,
356
+ ) => {
357
+ _publishEvent(EVENTS.WEBSITE_MAKER_DUPLICATE_PAGE, {
358
+ page_name: sourcePageName,
359
+ is_sub_page: Boolean(isSubPage),
360
+ });
361
+ },
349
362
  sendPublishOptionsForFreeEvent: () => {
350
363
  _publishEvent(EVENTS.WEBSITE_MAKER_PUBLISH_UPSELL_OPTION_MODAL, {
351
364
  upsell_option_continuation:
@@ -93,6 +93,7 @@ import {
93
93
  UI_SCREENSIZE_MODES,
94
94
  } from '@shared/models';
95
95
  import { PAGE_SECTIONS } from '@shared/page-settings/models';
96
+ import { toPageModels } from '@shared/page-settings/utils';
96
97
  import { useUiTemplateSettingsStore } from '@shared/template-settings/store';
97
98
  import { useBrandPageContainerStore } from '../brandpage-container';
98
99
  import { useLocalisedRouteStore } from '../route/localised-route-store';
@@ -1924,10 +1925,7 @@ export const useBrandPageMakerStore = defineStore('brandPageMaker', {
1924
1925
  const pageNavigationStore = usePageNavigationStore();
1925
1926
  const paletteStore = useUiColorPaletteStore();
1926
1927
 
1927
- const pages = pageSettingsStore.pages.map((p) => ({
1928
- ...p,
1929
- contents: p.contents.map((c) => c.settings.content),
1930
- })) as PageModel[];
1928
+ const pages = toPageModels(pageSettingsStore.pages);
1931
1929
 
1932
1930
  const backgroundStyles = backgroundSettingsStore.backgroundStyles;
1933
1931
  const logoStyles = styleSettingsStore.logoStyles;
@@ -1856,7 +1856,9 @@
1856
1856
  "showPage": "Seite anzeigen",
1857
1857
  "hidePage": "Seite ausblenden",
1858
1858
  "delete": "Seite löschen",
1859
- "addSubPage": "Unterseite hinzufügen"
1859
+ "addSubPage": "Unterseite hinzufügen",
1860
+ "duplicatePage": "Seite duplizieren",
1861
+ "duplicatePageTitle": "{{title}} Kopieren"
1860
1862
  },
1861
1863
  "pageSettingsSection": {
1862
1864
  "showNavigationBelow": "Navigation unter dem Logo anzeigen",
@@ -1856,7 +1856,9 @@
1856
1856
  "showPage": "Mostrar página",
1857
1857
  "hidePage": "Ocultar página",
1858
1858
  "delete": "Eliminar página",
1859
- "addSubPage": "Añadir subpágina"
1859
+ "addSubPage": "Añadir subpágina",
1860
+ "duplicatePage": "Duplicar página",
1861
+ "duplicatePageTitle": "{{title}} copia"
1860
1862
  },
1861
1863
  "pageSettingsSection": {
1862
1864
  "showNavigationBelow": "Muestra la navegación debajo del logo",
@@ -1856,7 +1856,9 @@
1856
1856
  "showPage": "Afficher la page",
1857
1857
  "hidePage": "Masquez la page",
1858
1858
  "delete": "Supprimer la page",
1859
- "addSubPage": "Ajouter une sous-page"
1859
+ "addSubPage": "Ajouter une sous-page",
1860
+ "duplicatePage": "Dupliquer la page",
1861
+ "duplicatePageTitle": "{{title}} Copie"
1860
1862
  },
1861
1863
  "pageSettingsSection": {
1862
1864
  "showNavigationBelow": "Afficher la navigation sous le logo",
@@ -1856,7 +1856,9 @@
1856
1856
  "showPage": "Afficher la page",
1857
1857
  "hidePage": "Masquez la page",
1858
1858
  "delete": "Supprimer la page",
1859
- "addSubPage": "Ajouter une sous-page"
1859
+ "addSubPage": "Ajouter une sous-page",
1860
+ "duplicatePage": "Dupliquer la page",
1861
+ "duplicatePageTitle": "Copie de {{title}}"
1860
1862
  },
1861
1863
  "pageSettingsSection": {
1862
1864
  "showNavigationBelow": "Afficher la navigation sous le logo",
@@ -1856,7 +1856,9 @@
1856
1856
  "showPage": "Show page",
1857
1857
  "hidePage": "Hide page",
1858
1858
  "delete": "Delete page",
1859
- "addSubPage": "Add subpage"
1859
+ "addSubPage": "Add subpage",
1860
+ "duplicatePage": "Duplicate page",
1861
+ "duplicatePageTitle": "{{title}} Copy"
1860
1862
  },
1861
1863
  "pageSettingsSection": {
1862
1864
  "showNavigationBelow": "Show navigation below logo",
@@ -1856,7 +1856,9 @@
1856
1856
  "showPage": "Mostrar página",
1857
1857
  "hidePage": "Ocultar página",
1858
1858
  "delete": "Eliminar página",
1859
- "addSubPage": "Adicionar subpágina"
1859
+ "addSubPage": "Adicionar subpágina",
1860
+ "duplicatePage": "Duplicar página",
1861
+ "duplicatePageTitle": "{{title}} Copiar"
1860
1862
  },
1861
1863
  "pageSettingsSection": {
1862
1864
  "showNavigationBelow": "Mostrar navegação abaixo do logotipo",
@@ -1856,7 +1856,9 @@
1856
1856
  "showPage": "Mostrar página",
1857
1857
  "hidePage": "Ocultar página",
1858
1858
  "delete": "Eliminar página",
1859
- "addSubPage": "Adicionar subpágina"
1859
+ "addSubPage": "Adicionar subpágina",
1860
+ "duplicatePage": "Duplicar página",
1861
+ "duplicatePageTitle": "{{title}} Cópia"
1860
1862
  },
1861
1863
  "pageSettingsSection": {
1862
1864
  "showNavigationBelow": "Mostrar a navegação por baixo do logótipo",
@@ -142,7 +142,8 @@ const productConfigOptionTr = (key) => tr(`productConfigOption.${key}`);
142
142
  const editLogoSettingsTr = (key) => tr(`editLogoSettings.${key}`);
143
143
  const myUploadSettingsTr = (key, valuesToInterpolate = {}) =>
144
144
  tr(`myUploadSettings.${key}`, valuesToInterpolate);
145
- const pageSettingsTr = (key) => tr(`pageSettings.${key}`);
145
+ const pageSettingsTr = (key, valuesToInterpolate = {}) =>
146
+ tr(`pageSettings.${key}`, valuesToInterpolate);
146
147
  const pageSettingsSectionTr = (key) => tr(`pageSettingsSection.${key}`);
147
148
  const templateSettingsTr = (key) => tr(`templateSettings.${key}`);
148
149
  const unsplashLibraryTr = (key) => tr(`unsplashLibrary.${key}`);
@@ -10,6 +10,14 @@ export const END_ITEM_ANCHOR = '__end__';
10
10
  /** Generate a stable item id. Mirrors section-level `uuid.v4()` id generation. */
11
11
  export const generateItemId = (): string => uuidv4();
12
12
 
13
+ /** Every array field the minifier surfaces to the AI as a list of items. */
14
+ export const ITEM_ID_BEARING_FIELDS = [
15
+ 'images',
16
+ 'items',
17
+ 'textItems',
18
+ 'links',
19
+ ] as const;
20
+
13
21
  /**
14
22
  * Walk an entire BrandPageModel and backfill a stable uuid `id` onto every
15
23
  * nested array item that lacks one (gallery images, images/shapes/testimonial
@@ -23,8 +31,7 @@ export const generateItemId = (): string => uuidv4();
23
31
  */
24
32
  export const ensureBrandPageItemIds = (model: BrandPageModel): void => {
25
33
  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) {
34
+ for (const field of ITEM_ID_BEARING_FIELDS) {
28
35
  const arr = content[field];
29
36
 
30
37
  if (Array.isArray(arr)) ensureItemIds(arr as { id?: string }[]);
@@ -96,7 +96,6 @@ const onEditProduct = (index: number) => {
96
96
  product.enableNotes,
97
97
  );
98
98
  const tempProduct = cloneDeep(shopSettingStore.getProductById(product.id));
99
- console.log(tempProduct, 'tempProduct');
100
99
  productSettingStore.openModal(tempProduct);
101
100
  };
102
101
 
@@ -147,7 +146,6 @@ const onSaveProductModal = (event: {
147
146
  onItemsChange(newProductArray);
148
147
  // Update shop store
149
148
  if (event.isNewProduct) {
150
- console.log(event.product, 'new product');
151
149
  shopSettingStore.addProduct(event.product);
152
150
  } else {
153
151
  shopSettingStore.updateProduct(event.product.id, event.product);
@@ -24,10 +24,9 @@ export function refreshDuplicatedMetadata(
24
24
 
25
25
  model.pages.forEach((page) =>
26
26
  page.contents.forEach((content) => {
27
+ rotateMonetizationIds(content);
28
+
27
29
  if (isShopContent(content)) {
28
- // Shop products collide on the global product / sku_key tables. The shop's payment-config
29
- // link is written with throwaway ids (not the content id), so only the product ids rotate.
30
- content.products.forEach(rotateProductIds);
31
30
  disconnectPayment(content.shopPaymentConfig);
32
31
  } else if (isPayment(content)) {
33
32
  resetPaymentBlock(content, content.requestPaymentConfig);
@@ -40,6 +39,25 @@ export function refreshDuplicatedMetadata(
40
39
  return model;
41
40
  }
42
41
 
42
+ /**
43
+ * Rotate the client-minted ids the brand-page-api keys a **global** row on — shop product / sku_key
44
+ * and payment-option ids. A verbatim copy collides with its source on those tables.
45
+ *
46
+ * Shared with page duplication, which wants only this half: that copy keeps the same owner, so it
47
+ * keeps its payment connection.
48
+ */
49
+ export function rotateMonetizationIds(content: Content): void {
50
+ if (isShopContent(content)) {
51
+ // The shop's payment-config link is written with throwaway ids (not the content id), so only
52
+ // the product ids rotate here.
53
+ content.products.forEach(rotateProductIds);
54
+ } else if (isPayment(content)) {
55
+ rotatePaymentOptionIds(content.requestPaymentConfig);
56
+ } else if (isSupportMe(content)) {
57
+ rotatePaymentOptionIds(content.supportMeConfig);
58
+ }
59
+ }
60
+
43
61
  function rotateProductIds(product: ProductModel): void {
44
62
  product.id = uuid();
45
63
 
@@ -72,19 +90,23 @@ function rotateProductIds(product: ProductModel): void {
72
90
  });
73
91
  }
74
92
 
93
+ // `brand_page_product` is keyed on the payment-option id, so a verbatim copy would adopt the
94
+ // source's product rows on its next payment upsert.
95
+ function rotatePaymentOptionIds(config: PaymentConfig): void {
96
+ config.paymentOptions.forEach((option) => {
97
+ option.id = uuid();
98
+ });
99
+ }
100
+
75
101
  /**
76
102
  * request-payment / support-me content. The api keys `content_block_payment_config` on the content
77
- * id (PK) and `brand_page_product` on the payment-option id (PK), both global. A verbatim copy
78
- * would, on its next payment upsert, repoint the source's config link and adopt its product rows —
79
- * so rotate both to make the copy independent, then drop the connection.
103
+ * id (PK), so a verbatim copy would repoint the source's config link on its next payment upsert -
104
+ * re-mint it, then drop the connection. (The option ids are rotated upstream, for both kinds of
105
+ * duplication.)
80
106
  */
81
107
  function resetPaymentBlock(content: Content, config: PaymentConfig): void {
82
108
  content.id = uuid();
83
109
 
84
- config.paymentOptions.forEach((option) => {
85
- option.id = uuid();
86
- });
87
-
88
110
  disconnectPayment(config);
89
111
  }
90
112
 
@@ -38,13 +38,13 @@ const findParentRouteIdForRouteId = (
38
38
  return undefined;
39
39
  };
40
40
 
41
- // This function will find all the page slugs that exist at the depth that we're modifying, this way, we can avoid duplication of page slugs
42
- // This will help support the use of similar named routes, e.g /home/home-1 and /my-home/home-1
43
- const getPageSlugsForCurrentDepth = (
41
+ // Routes at one depth. Slugs and titles only need to be unique among siblings,
42
+ // so /home/home-1 and /my-home/home-1 can coexist.
43
+ const getRoutesForCurrentDepth = (
44
44
  navModel: NavModel,
45
45
  parentRouteId?: string,
46
46
  excludeRouteId?: string,
47
- ): string[] => {
47
+ ): NavRoute[] => {
48
48
  let ordersAtDepth: NavOrder[];
49
49
 
50
50
  if (!parentRouteId) {
@@ -60,14 +60,21 @@ const getPageSlugsForCurrentDepth = (
60
60
 
61
61
  const routeIdsAtDepth = ordersAtDepth.map((order) => order.routeId);
62
62
 
63
- return navModel.routes
64
- .filter(
65
- (route) =>
66
- routeIdsAtDepth.includes(route.id) && route.id !== excludeRouteId,
67
- )
68
- .map((route) => route.pageSlug);
63
+ return navModel.routes.filter(
64
+ (route) =>
65
+ routeIdsAtDepth.includes(route.id) && route.id !== excludeRouteId,
66
+ );
69
67
  };
70
68
 
69
+ const getPageSlugsForCurrentDepth = (
70
+ navModel: NavModel,
71
+ parentRouteId?: string,
72
+ excludeRouteId?: string,
73
+ ): string[] =>
74
+ getRoutesForCurrentDepth(navModel, parentRouteId, excludeRouteId).map(
75
+ (route) => route.pageSlug,
76
+ );
77
+
71
78
  export const usePageNavigationStore = defineStore('pageNavigation', {
72
79
  state: (): PageNavigationState => ({
73
80
  routes: [],
@@ -102,6 +109,11 @@ export const usePageNavigationStore = defineStore('pageNavigation', {
102
109
  getPageSlugsForDepth(parentRouteId?: string): string[] {
103
110
  return getPageSlugsForCurrentDepth(this.nav, parentRouteId);
104
111
  },
112
+ getDisplayNamesForDepth(parentRouteId?: string): string[] {
113
+ return getRoutesForCurrentDepth(this.nav, parentRouteId).map(
114
+ (route) => route.displayName,
115
+ );
116
+ },
105
117
  getParentRouteIdForPage(pageId: string): string | undefined {
106
118
  const route = this.routes.find((r) => r.pageId === pageId);
107
119
 
@@ -143,12 +155,28 @@ export const usePageNavigationStore = defineStore('pageNavigation', {
143
155
  this.routes = nav.routes;
144
156
  this.orders = nav.orders;
145
157
  },
146
- addNavRoute(newRoute: NavRoute, parentRouteId?: string): void {
158
+ addNavRoute(
159
+ newRoute: NavRoute,
160
+ parentRouteId?: string,
161
+ insertAfterRouteId?: string,
162
+ ): void {
147
163
  const newOrder: NavOrder = {
148
164
  routeId: newRoute.id,
149
165
  subRoutes: [],
150
166
  };
151
167
 
168
+ const addToSiblings = (siblings: NavOrder[]): void => {
169
+ const afterIndex = insertAfterRouteId
170
+ ? siblings.findIndex((order) => order.routeId === insertAfterRouteId)
171
+ : -1;
172
+
173
+ if (afterIndex === -1) {
174
+ siblings.push(newOrder);
175
+ } else {
176
+ siblings.splice(afterIndex + 1, 0, newOrder);
177
+ }
178
+ };
179
+
152
180
  if (parentRouteId) {
153
181
  // Find where to insert the new route into order
154
182
  const parentOrder = findParentOrderByRouteId(
@@ -157,7 +185,7 @@ export const usePageNavigationStore = defineStore('pageNavigation', {
157
185
  );
158
186
 
159
187
  if (parentOrder) {
160
- parentOrder.subRoutes.push(newOrder);
188
+ addToSiblings(parentOrder.subRoutes);
161
189
  } else {
162
190
  console.error(
163
191
  `Parent route with id ${parentRouteId} not found in nav orders.`,
@@ -165,7 +193,7 @@ export const usePageNavigationStore = defineStore('pageNavigation', {
165
193
  }
166
194
  } else {
167
195
  // Push to root of orders object as this is not a nested route
168
- this.orders.push(newOrder);
196
+ addToSiblings(this.orders);
169
197
  }
170
198
 
171
199
  this.routes.push(newRoute);