@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
@@ -6,6 +6,8 @@ import type { BrandPageModel } from '../../brand-page/models';
6
6
  export declare const END_ITEM_ANCHOR = "__end__";
7
7
  /** Generate a stable item id. Mirrors section-level `uuid.v4()` id generation. */
8
8
  export declare const generateItemId: () => string;
9
+ /** Every array field the minifier surfaces to the AI as a list of items. */
10
+ export declare const ITEM_ID_BEARING_FIELDS: readonly ["images", "items", "textItems", "links"];
9
11
  /**
10
12
  * Walk an entire BrandPageModel and backfill a stable uuid `id` onto every
11
13
  * nested array item that lacks one (gallery images, images/shapes/testimonial
@@ -6,6 +6,13 @@ import { v4 as uuidv4 } from 'uuid';
6
6
  export const END_ITEM_ANCHOR = '__end__';
7
7
  /** Generate a stable item id. Mirrors section-level `uuid.v4()` id generation. */
8
8
  export const generateItemId = () => uuidv4();
9
+ /** Every array field the minifier surfaces to the AI as a list of items. */
10
+ export const ITEM_ID_BEARING_FIELDS = [
11
+ 'images',
12
+ 'items',
13
+ 'textItems',
14
+ 'links',
15
+ ];
9
16
  /**
10
17
  * Walk an entire BrandPageModel and backfill a stable uuid `id` onto every
11
18
  * nested array item that lacks one (gallery images, images/shapes/testimonial
@@ -19,8 +26,7 @@ export const generateItemId = () => uuidv4();
19
26
  */
20
27
  export const ensureBrandPageItemIds = (model) => {
21
28
  const ensureContentItemIds = (content) => {
22
- // Every array field that the minifier surfaces as a list of items.
23
- for (const field of ['images', 'items', 'textItems', 'links']) {
29
+ for (const field of ITEM_ID_BEARING_FIELDS) {
24
30
  const arr = content[field];
25
31
  if (Array.isArray(arr))
26
32
  ensureItemIds(arr);
@@ -471,6 +471,7 @@ declare const _default: import("vue").DefineComponent<{}, {
471
471
  hasVisibleNavigation(): boolean;
472
472
  }, {
473
473
  getPageSlugsForDepth(parentRouteId?: string): string[];
474
+ getDisplayNamesForDepth(parentRouteId?: string): string[];
474
475
  getParentRouteIdForPage(pageId: string): string | undefined;
475
476
  getParentRouteIdForRoute(routeId: string): string | undefined;
476
477
  getFirstOrDefaultRoute(): import("@brand-page").NavRoute;
@@ -483,7 +484,7 @@ declare const _default: import("vue").DefineComponent<{}, {
483
484
  setNav({ nav }: {
484
485
  nav: import("@brand-page").NavModel;
485
486
  }): void;
486
- addNavRoute(newRoute: import("@brand-page").NavRoute, parentRouteId?: string): void;
487
+ addNavRoute(newRoute: import("@brand-page").NavRoute, parentRouteId?: string, insertAfterRouteId?: string): void;
487
488
  removeNavRoute(routeId: string): void;
488
489
  onDisplayNameChange(routeId: string, newDisplayName: string): void;
489
490
  setRouteTitleAndSlugByPageId(pageId: string, displayName: string, slug: string): void;
@@ -508,6 +509,8 @@ declare const _default: import("vue").DefineComponent<{}, {
508
509
  setPages({ pages }: {
509
510
  pages: import("../../../page-settings/models").ContentPage[];
510
511
  }): void;
512
+ syncPagesWithOrders(): void;
513
+ showPageLimitUpgradeModalAsync(ctaLocation: string): Promise<void>;
511
514
  setNewPageModalVisibilityAsync({ isNewPageModalVisible, parentPageId, }: {
512
515
  isNewPageModalVisible: boolean;
513
516
  parentPageId?: string;
@@ -557,6 +560,9 @@ declare const _default: import("vue").DefineComponent<{}, {
557
560
  title: string;
558
561
  newPageParentId?: string;
559
562
  }): import("../../../page-settings/models").ContentPage;
563
+ duplicatePage({ pageId, }: {
564
+ pageId: string;
565
+ }): Promise<import("../../../page-settings/models").ContentPage | undefined>;
560
566
  setSelectedSectionName({ sectionName, }: {
561
567
  sectionName?: import("../../../page-settings/store").PageSettingsSection;
562
568
  }): void;
@@ -681,6 +687,7 @@ declare const _default: import("vue").DefineComponent<{}, {
681
687
  sendWebsiteManageAddPageEvent: (pageName: string, isSubPageMode?: boolean) => void;
682
688
  sendWebsiteManageRemovePageEvent: (pageName: string, isRouteNested?: boolean) => void;
683
689
  sendWebsiteManageReorderPageEvent: (pageName: string) => void;
690
+ sendWebsiteManageDuplicatePageEvent: (sourcePageName: string, isSubPage?: boolean) => void;
684
691
  sendPublishOptionsForFreeEvent: () => void;
685
692
  sendPublishOptionsForPremiumEvent: () => void;
686
693
  sendPublishOptionsForExitEvent: () => void;
@@ -516,6 +516,8 @@ declare const _default: import("vue").DefineComponent<{}, {
516
516
  setPages({ pages }: {
517
517
  pages: import("../../../page-settings/models").ContentPage[];
518
518
  }): void;
519
+ syncPagesWithOrders(): void;
520
+ showPageLimitUpgradeModalAsync(ctaLocation: string): Promise<void>;
519
521
  setNewPageModalVisibilityAsync({ isNewPageModalVisible, parentPageId, }: {
520
522
  isNewPageModalVisible: boolean;
521
523
  parentPageId?: string;
@@ -565,6 +567,9 @@ declare const _default: import("vue").DefineComponent<{}, {
565
567
  title: string;
566
568
  newPageParentId?: string;
567
569
  }): import("../../../page-settings/models").ContentPage;
570
+ duplicatePage({ pageId, }: {
571
+ pageId: string;
572
+ }): Promise<import("../../../page-settings/models").ContentPage | undefined>;
568
573
  setSelectedSectionName({ sectionName, }: {
569
574
  sectionName?: import("../../../page-settings/store").PageSettingsSection;
570
575
  }): void;
@@ -800,6 +805,7 @@ declare const _default: import("vue").DefineComponent<{}, {
800
805
  sendWebsiteManageAddPageEvent: (pageName: string, isSubPageMode?: boolean) => void;
801
806
  sendWebsiteManageRemovePageEvent: (pageName: string, isRouteNested?: boolean) => void;
802
807
  sendWebsiteManageReorderPageEvent: (pageName: string) => void;
808
+ sendWebsiteManageDuplicatePageEvent: (sourcePageName: string, isSubPage?: boolean) => void;
803
809
  sendPublishOptionsForFreeEvent: () => void;
804
810
  sendPublishOptionsForPremiumEvent: () => void;
805
811
  sendPublishOptionsForExitEvent: () => void;
@@ -532,6 +532,7 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
532
532
  sendWebsiteManageAddPageEvent: (pageName: string, isSubPageMode?: boolean) => void;
533
533
  sendWebsiteManageRemovePageEvent: (pageName: string, isRouteNested?: boolean) => void;
534
534
  sendWebsiteManageReorderPageEvent: (pageName: string) => void;
535
+ sendWebsiteManageDuplicatePageEvent: (sourcePageName: string, isSubPage?: boolean) => void;
535
536
  sendPublishOptionsForFreeEvent: () => void;
536
537
  sendPublishOptionsForPremiumEvent: () => void;
537
538
  sendPublishOptionsForExitEvent: () => void;
@@ -738,6 +738,8 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
738
738
  setPages({ pages }: {
739
739
  pages: import("../../../page-settings/models").ContentPage[];
740
740
  }): void;
741
+ syncPagesWithOrders(): void;
742
+ showPageLimitUpgradeModalAsync(ctaLocation: string): Promise<void>;
741
743
  setNewPageModalVisibilityAsync({ isNewPageModalVisible, parentPageId, }: {
742
744
  isNewPageModalVisible: boolean;
743
745
  parentPageId?: string;
@@ -787,6 +789,9 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
787
789
  title: string;
788
790
  newPageParentId?: string;
789
791
  }): import("../../../page-settings/models").ContentPage;
792
+ duplicatePage({ pageId, }: {
793
+ pageId: string;
794
+ }): Promise<import("../../../page-settings/models").ContentPage | undefined>;
790
795
  setSelectedSectionName({ sectionName, }: {
791
796
  sectionName?: import("../../../page-settings/store").PageSettingsSection;
792
797
  }): void;
@@ -540,6 +540,8 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
540
540
  setPages({ pages }: {
541
541
  pages: import("../../../page-settings/models").ContentPage[];
542
542
  }): void;
543
+ syncPagesWithOrders(): void;
544
+ showPageLimitUpgradeModalAsync(ctaLocation: string): Promise<void>;
543
545
  setNewPageModalVisibilityAsync({ isNewPageModalVisible, parentPageId, }: {
544
546
  isNewPageModalVisible: boolean;
545
547
  parentPageId?: string;
@@ -589,6 +591,9 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
589
591
  title: string;
590
592
  newPageParentId?: string;
591
593
  }): import("../../../page-settings/models").ContentPage;
594
+ duplicatePage({ pageId, }: {
595
+ pageId: string;
596
+ }): Promise<import("../../../page-settings/models").ContentPage | undefined>;
592
597
  setSelectedSectionName({ sectionName, }: {
593
598
  sectionName?: import("@shared/page-settings/store").PageSettingsSection;
594
599
  }): void;
@@ -1,4 +1,4 @@
1
- import type { BrandPageModel } from '@brand-page/models';
1
+ import type { BrandPageModel, Content } from '@brand-page/models';
2
2
  /**
3
3
  * Refresh a (migrated, current-version) BrandPageModel so it is safe to persist as a *duplicate*.
4
4
  * Two things travel with a verbatim copy that must not: client-minted identifiers that the
@@ -9,3 +9,11 @@ import type { BrandPageModel } from '@brand-page/models';
9
9
  * See `shop-duplication.md`.
10
10
  */
11
11
  export declare function refreshDuplicatedMetadata(model: BrandPageModel): BrandPageModel;
12
+ /**
13
+ * Rotate the client-minted ids the brand-page-api keys a **global** row on — shop product / sku_key
14
+ * and payment-option ids. A verbatim copy collides with its source on those tables.
15
+ *
16
+ * Shared with page duplication, which wants only this half: that copy keeps the same owner, so it
17
+ * keeps its payment connection.
18
+ */
19
+ export declare function rotateMonetizationIds(content: Content): void;
@@ -16,10 +16,8 @@ export function refreshDuplicatedMetadata(model) {
16
16
  // content-level connections below are cleared. Disconnect it too; the new owner reconnects.
17
17
  model.paymentConfig = undefined;
18
18
  model.pages.forEach((page) => page.contents.forEach((content) => {
19
+ rotateMonetizationIds(content);
19
20
  if (isShopContent(content)) {
20
- // Shop products collide on the global product / sku_key tables. The shop's payment-config
21
- // link is written with throwaway ids (not the content id), so only the product ids rotate.
22
- content.products.forEach(rotateProductIds);
23
21
  disconnectPayment(content.shopPaymentConfig);
24
22
  }
25
23
  else if (isPayment(content)) {
@@ -31,6 +29,26 @@ export function refreshDuplicatedMetadata(model) {
31
29
  }));
32
30
  return model;
33
31
  }
32
+ /**
33
+ * Rotate the client-minted ids the brand-page-api keys a **global** row on — shop product / sku_key
34
+ * and payment-option ids. A verbatim copy collides with its source on those tables.
35
+ *
36
+ * Shared with page duplication, which wants only this half: that copy keeps the same owner, so it
37
+ * keeps its payment connection.
38
+ */
39
+ export function rotateMonetizationIds(content) {
40
+ if (isShopContent(content)) {
41
+ // The shop's payment-config link is written with throwaway ids (not the content id), so only
42
+ // the product ids rotate here.
43
+ content.products.forEach(rotateProductIds);
44
+ }
45
+ else if (isPayment(content)) {
46
+ rotatePaymentOptionIds(content.requestPaymentConfig);
47
+ }
48
+ else if (isSupportMe(content)) {
49
+ rotatePaymentOptionIds(content.supportMeConfig);
50
+ }
51
+ }
34
52
  function rotateProductIds(product) {
35
53
  product.id = uuid();
36
54
  // The option id is the only value cross-referenced inside a product — SKUs point at product
@@ -56,17 +74,21 @@ function rotateProductIds(product) {
56
74
  sku.skuKey = generateSku(product.name, sku.variant[0]?.option);
57
75
  });
58
76
  }
77
+ // `brand_page_product` is keyed on the payment-option id, so a verbatim copy would adopt the
78
+ // source's product rows on its next payment upsert.
79
+ function rotatePaymentOptionIds(config) {
80
+ config.paymentOptions.forEach((option) => {
81
+ option.id = uuid();
82
+ });
83
+ }
59
84
  /**
60
85
  * request-payment / support-me content. The api keys `content_block_payment_config` on the content
61
- * id (PK) and `brand_page_product` on the payment-option id (PK), both global. A verbatim copy
62
- * would, on its next payment upsert, repoint the source's config link and adopt its product rows —
63
- * so rotate both to make the copy independent, then drop the connection.
86
+ * id (PK), so a verbatim copy would repoint the source's config link on its next payment upsert -
87
+ * re-mint it, then drop the connection. (The option ids are rotated upstream, for both kinds of
88
+ * duplication.)
64
89
  */
65
90
  function resetPaymentBlock(content, config) {
66
91
  content.id = uuid();
67
- config.paymentOptions.forEach((option) => {
68
- option.id = uuid();
69
- });
70
92
  disconnectPayment(config);
71
93
  }
72
94
  /**
@@ -299,6 +299,7 @@ declare const _default: import("vue").DefineComponent<{}, {
299
299
  sendWebsiteManageAddPageEvent: (pageName: string, isSubPageMode?: boolean) => void;
300
300
  sendWebsiteManageRemovePageEvent: (pageName: string, isRouteNested?: boolean) => void;
301
301
  sendWebsiteManageReorderPageEvent: (pageName: string) => void;
302
+ sendWebsiteManageDuplicatePageEvent: (sourcePageName: string, isSubPage?: boolean) => void;
302
303
  sendPublishOptionsForFreeEvent: () => void;
303
304
  sendPublishOptionsForPremiumEvent: () => void;
304
305
  sendPublishOptionsForExitEvent: () => void;
@@ -707,6 +707,8 @@ declare const _default: import("vue").DefineComponent<{}, {
707
707
  setPages({ pages }: {
708
708
  pages: import("../../page-settings/models").ContentPage[];
709
709
  }): void;
710
+ syncPagesWithOrders(): void;
711
+ showPageLimitUpgradeModalAsync(ctaLocation: string): Promise<void>;
710
712
  setNewPageModalVisibilityAsync({ isNewPageModalVisible, parentPageId, }: {
711
713
  isNewPageModalVisible: boolean;
712
714
  parentPageId?: string;
@@ -756,6 +758,9 @@ declare const _default: import("vue").DefineComponent<{}, {
756
758
  title: string;
757
759
  newPageParentId?: string;
758
760
  }): import("../../page-settings/models").ContentPage;
761
+ duplicatePage({ pageId, }: {
762
+ pageId: string;
763
+ }): Promise<import("../../page-settings/models").ContentPage | undefined>;
759
764
  setSelectedSectionName({ sectionName, }: {
760
765
  sectionName?: import("@shared/page-settings/store").PageSettingsSection;
761
766
  }): void;
@@ -9,6 +9,7 @@ export declare const usePageNavigationStore: import("pinia").StoreDefinition<"pa
9
9
  hasVisibleNavigation(): boolean;
10
10
  }, {
11
11
  getPageSlugsForDepth(parentRouteId?: string): string[];
12
+ getDisplayNamesForDepth(parentRouteId?: string): string[];
12
13
  getParentRouteIdForPage(pageId: string): string | undefined;
13
14
  getParentRouteIdForRoute(routeId: string): string | undefined;
14
15
  getFirstOrDefaultRoute(): NavRoute;
@@ -21,7 +22,7 @@ export declare const usePageNavigationStore: import("pinia").StoreDefinition<"pa
21
22
  setNav({ nav }: {
22
23
  nav: NavModel;
23
24
  }): void;
24
- addNavRoute(newRoute: NavRoute, parentRouteId?: string): void;
25
+ addNavRoute(newRoute: NavRoute, parentRouteId?: string, insertAfterRouteId?: string): void;
25
26
  removeNavRoute(routeId: string): void;
26
27
  onDisplayNameChange(routeId: string, newDisplayName: string): void;
27
28
  setRouteTitleAndSlugByPageId(pageId: string, displayName: string, slug: string): void;
@@ -16,9 +16,9 @@ const findParentRouteIdForRouteId = (orders, targetRouteId, parentRouteId) => {
16
16
  }
17
17
  return undefined;
18
18
  };
19
- // 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
20
- // This will help support the use of similar named routes, e.g /home/home-1 and /my-home/home-1
21
- const getPageSlugsForCurrentDepth = (navModel, parentRouteId, excludeRouteId) => {
19
+ // Routes at one depth. Slugs and titles only need to be unique among siblings,
20
+ // so /home/home-1 and /my-home/home-1 can coexist.
21
+ const getRoutesForCurrentDepth = (navModel, parentRouteId, excludeRouteId) => {
22
22
  let ordersAtDepth;
23
23
  if (!parentRouteId) {
24
24
  ordersAtDepth = navModel.orders;
@@ -28,10 +28,9 @@ const getPageSlugsForCurrentDepth = (navModel, parentRouteId, excludeRouteId) =>
28
28
  ordersAtDepth = parentOrder?.subRoutes || [];
29
29
  }
30
30
  const routeIdsAtDepth = ordersAtDepth.map((order) => order.routeId);
31
- return navModel.routes
32
- .filter((route) => routeIdsAtDepth.includes(route.id) && route.id !== excludeRouteId)
33
- .map((route) => route.pageSlug);
31
+ return navModel.routes.filter((route) => routeIdsAtDepth.includes(route.id) && route.id !== excludeRouteId);
34
32
  };
33
+ const getPageSlugsForCurrentDepth = (navModel, parentRouteId, excludeRouteId) => getRoutesForCurrentDepth(navModel, parentRouteId, excludeRouteId).map((route) => route.pageSlug);
35
34
  export const usePageNavigationStore = defineStore('pageNavigation', {
36
35
  state: () => ({
37
36
  routes: [],
@@ -62,6 +61,9 @@ export const usePageNavigationStore = defineStore('pageNavigation', {
62
61
  getPageSlugsForDepth(parentRouteId) {
63
62
  return getPageSlugsForCurrentDepth(this.nav, parentRouteId);
64
63
  },
64
+ getDisplayNamesForDepth(parentRouteId) {
65
+ return getRoutesForCurrentDepth(this.nav, parentRouteId).map((route) => route.displayName);
66
+ },
65
67
  getParentRouteIdForPage(pageId) {
66
68
  const route = this.routes.find((r) => r.pageId === pageId);
67
69
  if (!route) {
@@ -93,16 +95,27 @@ export const usePageNavigationStore = defineStore('pageNavigation', {
93
95
  this.routes = nav.routes;
94
96
  this.orders = nav.orders;
95
97
  },
96
- addNavRoute(newRoute, parentRouteId) {
98
+ addNavRoute(newRoute, parentRouteId, insertAfterRouteId) {
97
99
  const newOrder = {
98
100
  routeId: newRoute.id,
99
101
  subRoutes: [],
100
102
  };
103
+ const addToSiblings = (siblings) => {
104
+ const afterIndex = insertAfterRouteId
105
+ ? siblings.findIndex((order) => order.routeId === insertAfterRouteId)
106
+ : -1;
107
+ if (afterIndex === -1) {
108
+ siblings.push(newOrder);
109
+ }
110
+ else {
111
+ siblings.splice(afterIndex + 1, 0, newOrder);
112
+ }
113
+ };
101
114
  if (parentRouteId) {
102
115
  // Find where to insert the new route into order
103
116
  const parentOrder = findParentOrderByRouteId(this.orders, parentRouteId);
104
117
  if (parentOrder) {
105
- parentOrder.subRoutes.push(newOrder);
118
+ addToSiblings(parentOrder.subRoutes);
106
119
  }
107
120
  else {
108
121
  console.error(`Parent route with id ${parentRouteId} not found in nav orders.`);
@@ -110,7 +123,7 @@ export const usePageNavigationStore = defineStore('pageNavigation', {
110
123
  }
111
124
  else {
112
125
  // Push to root of orders object as this is not a nested route
113
- this.orders.push(newOrder);
126
+ addToSiblings(this.orders);
114
127
  }
115
128
  this.routes.push(newRoute);
116
129
  this.reorderVisibleAndHiddenRoutes();
@@ -17,6 +17,8 @@ declare const _default: import("vue").DefineComponent<{}, {
17
17
  setPages({ pages }: {
18
18
  pages: import("../../models").ContentPage[];
19
19
  }): void;
20
+ syncPagesWithOrders(): void;
21
+ showPageLimitUpgradeModalAsync(ctaLocation: string): Promise<void>;
20
22
  setNewPageModalVisibilityAsync({ isNewPageModalVisible, parentPageId, }: {
21
23
  isNewPageModalVisible: boolean;
22
24
  parentPageId?: string;
@@ -66,6 +68,9 @@ declare const _default: import("vue").DefineComponent<{}, {
66
68
  title: string;
67
69
  newPageParentId?: string;
68
70
  }): import("../../models").ContentPage;
71
+ duplicatePage({ pageId, }: {
72
+ pageId: string;
73
+ }): Promise<import("../../models").ContentPage | undefined>;
69
74
  setSelectedSectionName({ sectionName, }: {
70
75
  sectionName?: import("../../store").PageSettingsSection;
71
76
  }): void;
@@ -82,6 +87,7 @@ declare const _default: import("vue").DefineComponent<{}, {
82
87
  hasVisibleNavigation(): boolean;
83
88
  }, {
84
89
  getPageSlugsForDepth(parentRouteId?: string): string[];
90
+ getDisplayNamesForDepth(parentRouteId?: string): string[];
85
91
  getParentRouteIdForPage(pageId: string): string | undefined;
86
92
  getParentRouteIdForRoute(routeId: string): string | undefined;
87
93
  getFirstOrDefaultRoute(): import("../../../../brand-page").NavRoute;
@@ -94,7 +100,7 @@ declare const _default: import("vue").DefineComponent<{}, {
94
100
  setNav({ nav }: {
95
101
  nav: import("../../../../brand-page").NavModel;
96
102
  }): void;
97
- addNavRoute(newRoute: import("../../../../brand-page").NavRoute, parentRouteId?: string): void;
103
+ addNavRoute(newRoute: import("../../../../brand-page").NavRoute, parentRouteId?: string, insertAfterRouteId?: string): void;
98
104
  removeNavRoute(routeId: string): void;
99
105
  onDisplayNameChange(routeId: string, newDisplayName: string): void;
100
106
  setRouteTitleAndSlugByPageId(pageId: string, displayName: string, slug: string): void;
@@ -39,6 +39,7 @@ declare const _default: import("vue").DefineComponent<{}, {
39
39
  fields: Record<string, import("../../../content-settings/models").ContentItemFieldSettings>;
40
40
  };
41
41
  }[];
42
+ isHidden?: boolean | undefined;
42
43
  };
43
44
  pageTitle: string;
44
45
  pageTitleError: string | null;
@@ -4151,6 +4152,8 @@ declare const _default: import("vue").DefineComponent<{}, {
4151
4152
  setPages({ pages }: {
4152
4153
  pages: ContentPage[];
4153
4154
  }): void;
4155
+ syncPagesWithOrders(): void;
4156
+ showPageLimitUpgradeModalAsync(ctaLocation: string): Promise<void>;
4154
4157
  setNewPageModalVisibilityAsync({ isNewPageModalVisible, parentPageId, }: {
4155
4158
  isNewPageModalVisible: boolean;
4156
4159
  parentPageId?: string;
@@ -4200,6 +4203,9 @@ declare const _default: import("vue").DefineComponent<{}, {
4200
4203
  title: string;
4201
4204
  newPageParentId?: string;
4202
4205
  }): ContentPage;
4206
+ duplicatePage({ pageId, }: {
4207
+ pageId: string;
4208
+ }): Promise<ContentPage | undefined>;
4203
4209
  setSelectedSectionName({ sectionName, }: {
4204
4210
  sectionName?: PageSettingsSection;
4205
4211
  }): void;
@@ -4216,6 +4222,7 @@ declare const _default: import("vue").DefineComponent<{}, {
4216
4222
  hasVisibleNavigation(): boolean;
4217
4223
  }, {
4218
4224
  getPageSlugsForDepth(parentRouteId?: string): string[];
4225
+ getDisplayNamesForDepth(parentRouteId?: string): string[];
4219
4226
  getParentRouteIdForPage(pageId: string): string | undefined;
4220
4227
  getParentRouteIdForRoute(routeId: string): string | undefined;
4221
4228
  getFirstOrDefaultRoute(): import("@brand-page").NavRoute;
@@ -4228,7 +4235,7 @@ declare const _default: import("vue").DefineComponent<{}, {
4228
4235
  setNav({ nav }: {
4229
4236
  nav: import("@brand-page").NavModel;
4230
4237
  }): void;
4231
- addNavRoute(newRoute: import("@brand-page").NavRoute, parentRouteId?: string): void;
4238
+ addNavRoute(newRoute: import("@brand-page").NavRoute, parentRouteId?: string, insertAfterRouteId?: string): void;
4232
4239
  removeNavRoute(routeId: string): void;
4233
4240
  onDisplayNameChange(routeId: string, newDisplayName: string): void;
4234
4241
  setRouteTitleAndSlugByPageId(pageId: string, displayName: string, slug: string): void;
@@ -5169,6 +5176,7 @@ declare const _default: import("vue").DefineComponent<{}, {
5169
5176
  sendWebsiteManageAddPageEvent: (pageName: string, isSubPageMode?: boolean) => void;
5170
5177
  sendWebsiteManageRemovePageEvent: (pageName: string, isRouteNested?: boolean) => void;
5171
5178
  sendWebsiteManageReorderPageEvent: (pageName: string) => void;
5179
+ sendWebsiteManageDuplicatePageEvent: (sourcePageName: string, isSubPage?: boolean) => void;
5172
5180
  sendPublishOptionsForFreeEvent: () => void;
5173
5181
  sendPublishOptionsForPremiumEvent: () => void;
5174
5182
  sendPublishOptionsForExitEvent: () => void;
@@ -5211,6 +5219,7 @@ declare const _default: import("vue").DefineComponent<{}, {
5211
5219
  isRootPage(index: number): boolean;
5212
5220
  onAddSubPageSelected(pageId: string): Promise<void>;
5213
5221
  onAddNewPageSelected(): Promise<void>;
5222
+ onDuplicatePageSelected(pageId: string): Promise<void>;
5214
5223
  onChangePageVisibility(pageId: string): void;
5215
5224
  onDeletePageSelected(pageId: string): void;
5216
5225
  onConfirmDeletePage(pageId: string): void;
@@ -69,40 +69,6 @@ export default defineComponent({
69
69
  },
70
70
  });
71
71
  const isDraggingPage = ref(false);
72
- const syncPagesWithOrders = () => {
73
- const pageIdOrder = [];
74
- const collectPageIds = (orders) => {
75
- for (const order of orders) {
76
- const route = pageNavigationStore.routes.find((r) => r.id === order.routeId);
77
- if (route?.pageId) {
78
- pageIdOrder.push(route.pageId);
79
- }
80
- if (order.subRoutes && order.subRoutes.length > 0) {
81
- collectPageIds(order.subRoutes);
82
- }
83
- }
84
- };
85
- collectPageIds(pageNavigationStore.orders);
86
- if (!pageIdOrder.length) {
87
- return;
88
- }
89
- const pageMap = new Map();
90
- for (const page of pageSettingsStore.pages) {
91
- pageMap.set(page.id, page);
92
- }
93
- const orderedPages = [];
94
- for (const pageId of pageIdOrder) {
95
- const page = pageMap.get(pageId);
96
- if (page) {
97
- orderedPages.push(page);
98
- pageMap.delete(pageId);
99
- }
100
- }
101
- for (const remainingPage of pageMap.values()) {
102
- orderedPages.push(remainingPage);
103
- }
104
- pageSettingsStore.setPages({ pages: orderedPages });
105
- };
106
72
  const onDraggingStart = () => {
107
73
  isDraggingPage.value = true;
108
74
  };
@@ -117,7 +83,7 @@ export default defineComponent({
117
83
  }
118
84
  }
119
85
  }
120
- syncPagesWithOrders();
86
+ pageSettingsStore.syncPagesWithOrders();
121
87
  // Recalc the moved route's slug to ensure uniqueness at its new depth
122
88
  if (routeId) {
123
89
  pageNavigationStore.recalculateSlugForRoute(routeId, previousParentRouteId);
@@ -256,6 +222,17 @@ export default defineComponent({
256
222
  isNewPageModalVisible: true,
257
223
  });
258
224
  },
225
+ async onDuplicatePageSelected(pageId) {
226
+ const sourcePage = this.pageSettingsStore.pages.find((p) => p.id === pageId);
227
+ const isSubPage = Boolean(this.pageNavigationStore.getParentRouteIdForPage(pageId));
228
+ const newPage = await this.pageSettingsStore.duplicatePage({ pageId });
229
+ // Nothing was created - paywalled, or the page is gone. The upsell is
230
+ // already tracked through the bridge's cta_location.
231
+ if (!newPage || !sourcePage) {
232
+ return;
233
+ }
234
+ this.eventTracker.sendWebsiteManageDuplicatePageEvent(sourcePage.title, isSubPage);
235
+ },
259
236
  onChangePageVisibility(pageId) {
260
237
  const page = this.pageSettingsStore.pages.find((p) => p.id === pageId);
261
238
  const currentPageVisibility = Boolean(page?.isHidden);
@@ -3430,6 +3430,8 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
3430
3430
  setPages({ pages }: {
3431
3431
  pages: import("../../models").ContentPage[];
3432
3432
  }): void;
3433
+ syncPagesWithOrders(): void;
3434
+ showPageLimitUpgradeModalAsync(ctaLocation: string): Promise<void>;
3433
3435
  setNewPageModalVisibilityAsync({ isNewPageModalVisible, parentPageId, }: {
3434
3436
  isNewPageModalVisible: boolean;
3435
3437
  parentPageId?: string;
@@ -3479,6 +3481,9 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
3479
3481
  title: string;
3480
3482
  newPageParentId?: string;
3481
3483
  }): import("../../models").ContentPage;
3484
+ duplicatePage({ pageId, }: {
3485
+ pageId: string;
3486
+ }): Promise<import("../../models").ContentPage | undefined>;
3482
3487
  setSelectedSectionName({ sectionName, }: {
3483
3488
  sectionName?: import("../../store").PageSettingsSection;
3484
3489
  }): void;
@@ -4790,6 +4795,7 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
4790
4795
  fields: Record<string, ContentItemFieldSettings>;
4791
4796
  };
4792
4797
  }[];
4798
+ isHidden?: boolean | undefined;
4793
4799
  }[];
4794
4800
  }, {
4795
4801
  PAGE_SECTION_STYLE_OPTIONS: {
@@ -0,0 +1,4 @@
1
+ import type { Content } from '@brand-page';
2
+ import type { ContentItem } from '../content-settings/models';
3
+ export declare const getDuplicatePageTitle: (sourceTitle: string, siblingTitles: string[], isSubPage: boolean) => string;
4
+ export declare const duplicatePageContents: (contents: ContentItem<Content>[]) => ContentItem<Content>[];
@@ -0,0 +1,82 @@
1
+ import cloneDeep from 'lodash.clonedeep';
2
+ import { v4 as uuid } from 'uuid';
3
+ import { rotateMonetizationIds } from '../ecommerce-settings/util/refreshDuplicatedMetadata';
4
+ import { generateItemId, ITEM_ID_BEARING_FIELDS, } from '@src/metadata-builder/builders/item-id-utils';
5
+ import { pageSettingsTr } from '@src/composables/useTranslate';
6
+ import { MAX_PAGE_TITLE_LENGTH, MAX_SUBPAGE_TITLE_LENGTH } from './utils';
7
+ const composeTitle = (baseTitle, index) => {
8
+ const copyTitle = pageSettingsTr('duplicatePageTitle', {
9
+ title: baseTitle,
10
+ // A page title is text, not markup, and i18next escapes interpolated values
11
+ // by default - "Tom & Jerry" would come back as "Tom &amp; Jerry".
12
+ interpolation: { escapeValue: false },
13
+ }).trim();
14
+ return index > 1 ? `${copyTitle} ${index}` : copyTitle;
15
+ };
16
+ // The localised affix may sit either side of the title and is not a fixed
17
+ // length, so fit by trimming the base and re-composing rather than by slicing
18
+ // the finished string - that would cut the affix off in some locales.
19
+ const composeTitleWithinLimit = (baseTitle, index, maxLength) => {
20
+ const candidate = composeTitle(baseTitle, index);
21
+ const overflow = candidate.length - maxLength;
22
+ if (overflow <= 0) {
23
+ return candidate;
24
+ }
25
+ const trimmedBase = baseTitle
26
+ .slice(0, Math.max(0, baseTitle.length - overflow))
27
+ .trimEnd();
28
+ const trimmedCandidate = composeTitle(trimmedBase, index);
29
+ if (trimmedCandidate.length <= maxLength) {
30
+ return trimmedCandidate;
31
+ }
32
+ // A locale whose affix alone exceeds the limit: keep trimming the base rather
33
+ // than slicing the finished title, which would cut the trailing index and let
34
+ // two indexes collapse to the same name.
35
+ const affixLength = composeTitle('', index).length;
36
+ return composeTitle(baseTitle.slice(0, Math.max(0, maxLength - affixLength)).trimEnd(), index);
37
+ };
38
+ export const getDuplicatePageTitle = (sourceTitle, siblingTitles, isSubPage) => {
39
+ const maxLength = isSubPage
40
+ ? MAX_SUBPAGE_TITLE_LENGTH
41
+ : MAX_PAGE_TITLE_LENGTH;
42
+ const takenTitles = new Set(siblingTitles);
43
+ // N siblings block at most N candidates, so one of the first N+1 is free.
44
+ const lastIndex = siblingTitles.length + 1;
45
+ for (let index = 1; index < lastIndex; index += 1) {
46
+ const candidate = composeTitleWithinLimit(sourceTitle, index, maxLength);
47
+ if (!takenTitles.has(candidate)) {
48
+ return candidate;
49
+ }
50
+ }
51
+ return composeTitleWithinLimit(sourceTitle, lastIndex, maxLength);
52
+ };
53
+ // These ids are what the AI builder is shown and what its executor matches on
54
+ // (see ensureBrandPageItemIds), so a copy sharing them would put two identical
55
+ // ids in front of it.
56
+ const renewItemIds = (content) => {
57
+ const fields = content;
58
+ for (const field of ITEM_ID_BEARING_FIELDS) {
59
+ const items = fields[field];
60
+ if (!Array.isArray(items)) {
61
+ continue;
62
+ }
63
+ for (const item of items) {
64
+ if (item.id) {
65
+ item.id = generateItemId();
66
+ }
67
+ }
68
+ }
69
+ };
70
+ export const duplicatePageContents = (contents) => cloneDeep(contents).map((item) => {
71
+ // ContentItem.id is re-derived from content.id on every reload
72
+ // (content-settings.util: mapBrandPageContentToContentSettingsItem), so the
73
+ // two must be minted together or itemStates drifts until the next load.
74
+ const newId = uuid();
75
+ item.id = newId;
76
+ item.settings.content.id = newId;
77
+ // Ids only. The copy keeps the source's payment connection - same owner, so it should carry on
78
+ // taking payments; it is website duplication that disconnects.
79
+ rotateMonetizationIds(item.settings.content);
80
+ renewItemIds(item.settings.content);
81
+ return item;
82
+ });
@@ -6,6 +6,7 @@ export interface ContentPage {
6
6
  id: string;
7
7
  title: string;
8
8
  contents: ContentItem<Content>[];
9
+ isHidden?: boolean;
9
10
  }
10
11
  export declare const PAGE_SECTIONS: {
11
12
  readonly header: "header";