@designcrowd/library.brand-page 6.2.0 → 6.2.2-live-preview-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/src/{CreatedOnBrandCrowd-de-de-VQCQvlMg.js → CreatedOnBrandCrowd-de-de-Cn0M-eZ1.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-en-us-DSf3BjCG.js → CreatedOnBrandCrowd-en-us-DRLMSDZr.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-es-es-DGcdM9Ec.js → CreatedOnBrandCrowd-es-es-BglTu73U.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-fr-C9g1fc39.js → CreatedOnBrandCrowd-fr-ca-Bj6YZ_el.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-ca-C9g1fc39.js → CreatedOnBrandCrowd-fr-fr-Bj6YZ_el.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-pt-DSXr7Bxl.js → CreatedOnBrandCrowd-pt-br-DEoOmQ1F.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-br-DSXr7Bxl.js → CreatedOnBrandCrowd-pt-pt-DEoOmQ1F.js} +1 -1
- package/lib/src/{CreatedOnDcom-de-de-BPdCL6wS.js → CreatedOnDcom-de-de-BZ7gTt4Z.js} +1 -1
- package/lib/src/{CreatedOnDcom-en-us-CLkSTQRu.js → CreatedOnDcom-en-us-CD6Km5sH.js} +1 -1
- package/lib/src/{CreatedOnDcom-es-es-CRknMAGv.js → CreatedOnDcom-es-es-CNpZHvEl.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-ca-ClAY_dHI.js → CreatedOnDcom-fr-ca-URS-ydJ1.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-fr-DQtv9rR-.js → CreatedOnDcom-fr-fr-D2AVeA3H.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-br-BbIxvmxv.js → CreatedOnDcom-pt-br-C32frnFs.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-pt-BOfOesBj.js → CreatedOnDcom-pt-pt-TDYGjRXS.js} +1 -1
- package/lib/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.mixin.d.ts +16 -2
- package/lib/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.mixin.js +17 -0
- package/lib/src/brand-page/components/page/Page.mixin.d.ts +2 -1
- package/lib/src/brand-page/components/page/Page.mixin.js +12 -7
- package/lib/src/brand-page/components/page-header/HeaderLayout.stories.d.ts +1 -0
- package/lib/src/brand-page/components/page-header/HeaderLayout.stories.js +9 -0
- package/lib/src/brand-page/components/page-header/PageHeader.mixin.d.ts +3 -1
- package/lib/src/brand-page/components/page-header/PageHeader.mixin.js +10 -4
- package/lib/src/brand-page/constants/sections/header-display.constants.d.ts +3 -0
- package/lib/src/brand-page/constants/sections/header-display.constants.js +15 -0
- package/lib/src/brand-page/models/brand-page-context.model.d.ts +0 -2
- package/lib/src/brand-page/models/brand-page-context.model.js +0 -12
- package/lib/src/brand-page/models/index.d.ts +12 -0
- package/lib/src/brand-page/models/index.js +4 -0
- package/lib/src/brand-page/utils/context.util.d.ts +1 -0
- package/lib/src/brand-page/utils/context.util.js +9 -0
- package/lib/src/brand-page/utils/route.util.d.ts +1 -0
- package/lib/src/brand-page/utils/route.util.js +9 -0
- package/lib/src/brand-page/utils/view-model.util.js +53 -1
- package/lib/src/brand-page/view-models.d.ts +3 -1
- package/lib/src/brand-page-maker/components/action-bar/ActionBar.mixin.d.ts +0 -1
- package/lib/src/brand-page-maker/components/action-bar/ActionBar.mixin.js +0 -9
- package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +9 -44
- package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.d.ts +8 -43
- package/lib/src/brand-page-maker/components/preview/Preview.mixin.d.ts +0 -14
- package/lib/src/brand-page-maker/components/quick-link-bar/QuickLinkBar.mixin.d.ts +0 -42
- package/lib/src/brand-page-maker/components/seo-settings/SeoPageSettings.mixin.d.ts +0 -42
- package/lib/src/brand-page-maker/components/seo-settings/UrlSettings.mixin.d.ts +0 -42
- package/lib/src/brand-page-maker/components/seo-settings/UseExistingDomainModal.mixin.d.ts +0 -42
- package/lib/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.d.ts +0 -42
- package/lib/src/brand-page-maker/components/upgrade-to-premium-button/UpgradeToPremiumButton.mixin.d.ts +0 -42
- package/lib/src/brand-page-maker/composables/useEventTracker.d.ts +3 -0
- package/lib/src/brand-page-maker/composables/useEventTracker.js +10 -0
- package/lib/src/brand-page-maker/stores/ai-checkpoint/ai-checkpoint-store.d.ts +1626 -0
- package/lib/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.d.ts +0 -42
- package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.js +2 -4
- package/lib/src/brand-page-maker/validators/section-button.validator.d.ts +5 -2
- package/lib/src/brand-page-maker/validators/section-button.validator.js +11 -15
- package/lib/src/{bundled-translations-B2q5GWTQ.js → bundled-translations-BYUfWBT3.js} +17 -17
- package/lib/src/{bundled-translations.de-DE-FhH2H_rd.js → bundled-translations.de-DE-MmzUu1tt.js} +21 -21
- package/lib/src/{bundled-translations.es-ES-_ZExxMi7.js → bundled-translations.es-ES-D-tG33XL.js} +5 -5
- package/lib/src/{bundled-translations.fr-CA-Tg-ODIiC.js → bundled-translations.fr-CA-CNc1H1h3.js} +1 -1
- package/lib/src/{bundled-translations.fr-FR-C0sU51Mz.js → bundled-translations.fr-FR-DX0-yepT.js} +9 -9
- package/lib/src/{bundled-translations.pt-BR-MYFIhA4u.js → bundled-translations.pt-BR-ih1-LRgy.js} +9 -9
- package/lib/src/{bundled-translations.pt-PT-DeUBSzCB.js → bundled-translations.pt-PT-Dq2CmD1p.js} +22 -22
- package/lib/src/bundles/bundled-translations.de-DE.json +9 -2
- package/lib/src/bundles/bundled-translations.es-ES.json +9 -2
- package/lib/src/bundles/bundled-translations.fr-CA.json +9 -2
- package/lib/src/bundles/bundled-translations.fr-FR.json +9 -2
- package/lib/src/bundles/bundled-translations.json +9 -2
- package/lib/src/bundles/bundled-translations.pt-BR.json +9 -2
- package/lib/src/bundles/bundled-translations.pt-PT.json +9 -2
- package/lib/src/composables/useTranslate.d.ts +1 -1
- package/lib/src/composables/useTranslate.js +1 -1
- package/lib/src/css/library.brand-page-brandCrowd.min.css +577 -139
- package/lib/src/css/library.brand-page-brandPage.min.css +577 -139
- package/lib/src/css/library.brand-page-designCom.min.css +577 -139
- package/lib/src/css/library.brand-page-preview.min.css +577 -139
- package/lib/src/index.mjs +2 -2
- package/lib/src/{lib-BMVF4uNy.js → lib-Dm9AuOJL.js} +22487 -21900
- package/lib/src/metadata-builder/builders/item-id-utils.d.ts +2 -0
- package/lib/src/metadata-builder/builders/item-id-utils.js +8 -2
- package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.d.ts +10 -2
- package/lib/src/shared/content-settings/components/content-settings/ReplaceImageSettingsForContent.mixin.d.ts +7 -1
- package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItem.mixin.d.ts +1 -0
- package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.d.ts +6 -1
- package/lib/src/shared/content-settings/components/payment-config-field/PaymentConfigField.mixin.d.ts +6 -43
- package/lib/src/shared/content-settings/components/text-field/TextField.mixin.d.ts +14 -2
- package/lib/src/shared/content-settings/components/text-field/TextField.mixin.js +14 -0
- package/lib/src/shared/ecommerce-settings/util/refreshDuplicatedMetadata.d.ts +9 -1
- package/lib/src/shared/ecommerce-settings/util/refreshDuplicatedMetadata.js +31 -9
- package/lib/src/shared/logo-settings/components/EditLogoSettings.mixin.d.ts +1 -0
- package/lib/src/shared/logo-settings/components/LogoSettings.mixin.d.ts +6 -43
- package/lib/src/shared/page-navigation/store/page-navigation-store.d.ts +3 -1
- package/lib/src/shared/page-navigation/store/page-navigation-store.js +34 -9
- package/lib/src/shared/page-settings/components/delete-page-modal/DeletePageModal.mixin.d.ts +1 -1
- package/lib/src/shared/page-settings/components/new-page-modal/NewPageModal.mixin.d.ts +9 -2
- package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +200 -44
- package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.js +12 -35
- package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +190 -44
- package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.js +34 -2
- package/lib/src/shared/page-settings/duplicate-page.util.d.ts +4 -0
- package/lib/src/shared/page-settings/duplicate-page.util.js +82 -0
- package/lib/src/shared/page-settings/models.d.ts +1 -0
- package/lib/src/shared/page-settings/store/page-settings-store.d.ts +7 -2
- package/lib/src/shared/page-settings/store/page-settings-store.js +90 -7
- package/lib/src/shared/page-settings/utils.d.ts +3 -0
- package/lib/src/shared/page-settings/utils.js +6 -0
- package/lib/src/shared/publish-brand-page-with-state/PublishBrandPageModalWithState.mixin.d.ts +1 -56
- package/lib/src/shared/side-bar/components/SideBar.mixin.d.ts +0 -42
- package/lib/src/shared/side-bar/components/SideBarTabButton.mixin.d.ts +1 -0
- package/lib/src/shared/style-settings/components/StyleSettings.mixin.d.ts +4 -42
- package/lib/src/shared/template-settings/components/TemplatesSettings.mixin.d.ts +1 -0
- package/lib/src/tracker.mjs +1 -1
- package/lib/src/{useEventTracker-C5Ql5p7u.js → useEventTracker-DwPTmzpa.js} +13 -3
- package/package.json +2 -2
- package/src/brand-page/components/navigation/hamburger/HamburgerNavigation.vue +1 -0
- package/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.mixin.ts +18 -1
- package/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.vue +73 -3
- package/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.vue.test.ts +181 -0
- package/src/brand-page/components/page/Page.mixin.ts +21 -11
- package/src/brand-page/components/page/Page.vue +2 -0
- package/src/brand-page/components/page-header/HeaderLayout.stories.ts +10 -0
- package/src/brand-page/components/page-header/HeaderLayout.vue +415 -71
- package/src/brand-page/components/page-header/PageHeader.mixin.ts +14 -10
- package/src/brand-page/components/page-header/PageHeader.vue +16 -1
- package/src/brand-page/components/page-header/PageHeader.vue.test.ts +339 -0
- package/src/brand-page/constants/sections/header-display.constants.test.ts +44 -0
- package/src/brand-page/constants/sections/header-display.constants.ts +35 -0
- package/src/brand-page/models/brand-page-context.model.ts +0 -23
- package/src/brand-page/models/header-button.model.test.ts +85 -0
- package/src/brand-page/models/index.ts +19 -0
- package/src/brand-page/utils/context.util.ts +15 -0
- package/src/brand-page/utils/route.util.ts +13 -0
- package/src/brand-page/utils/view-model.util.test.ts +151 -1
- package/src/brand-page/utils/view-model.util.ts +73 -1
- package/src/brand-page/view-models.ts +3 -0
- package/src/brand-page-maker/components/action-bar/ActionBar.mixin.ts +0 -10
- package/src/brand-page-maker/components/action-bar/ActionBarNavigation.test.ts +8 -49
- package/src/brand-page-maker/components/action-bar/ActionBarNavigation.vue +1 -1
- package/src/brand-page-maker/composables/useEventTracker.ts +13 -0
- package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.ts +2 -4
- package/src/brand-page-maker/validators/section-button.validator.ts +23 -19
- package/src/bundles/bundled-translations.de-DE.json +9 -2
- package/src/bundles/bundled-translations.es-ES.json +9 -2
- package/src/bundles/bundled-translations.fr-CA.json +9 -2
- package/src/bundles/bundled-translations.fr-FR.json +9 -2
- package/src/bundles/bundled-translations.json +9 -2
- package/src/bundles/bundled-translations.pt-BR.json +9 -2
- package/src/bundles/bundled-translations.pt-PT.json +9 -2
- package/src/composables/useTranslate.ts +2 -1
- package/src/metadata-builder/builders/item-id-utils.ts +9 -2
- package/src/shared/content-settings/components/text-field/TextField.mixin.ts +15 -0
- package/src/shared/content-settings/components/url-field/UrlField.vue +1 -0
- package/src/shared/ecommerce-settings/components/product-config-field/ProductConfigOption.vue +0 -2
- package/src/shared/ecommerce-settings/util/refreshDuplicatedMetadata.ts +32 -10
- package/src/shared/page-navigation/store/page-navigation-store.ts +57 -13
- package/src/shared/page-settings/components/header-settings-panel/HeaderSettingsPanel.button-section.test.ts +131 -0
- package/src/shared/page-settings/components/header-settings-panel/HeaderSettingsPanel.vue +65 -10
- package/src/shared/page-settings/components/page-settings/NestedDraggable.vue +3 -0
- package/src/shared/page-settings/components/page-settings/PageElement.vue +2 -0
- package/src/shared/page-settings/components/page-settings/PageSettingMenu.test.ts +105 -0
- package/src/shared/page-settings/components/page-settings/PageSettingMenu.vue +16 -0
- package/src/shared/page-settings/components/page-settings/PageSettings.duplicate.test.ts +108 -0
- package/src/shared/page-settings/components/page-settings/PageSettings.mixin.ts +22 -50
- package/src/shared/page-settings/components/page-settings/PageSettings.vue +1 -0
- package/src/shared/page-settings/components/page-settings/i18n/page-settings.de-DE.json +3 -1
- package/src/shared/page-settings/components/page-settings/i18n/page-settings.es-ES.json +3 -1
- package/src/shared/page-settings/components/page-settings/i18n/page-settings.fr-CA.json +3 -1
- package/src/shared/page-settings/components/page-settings/i18n/page-settings.fr-FR.json +3 -1
- package/src/shared/page-settings/components/page-settings/i18n/page-settings.json +3 -1
- package/src/shared/page-settings/components/page-settings/i18n/page-settings.pt-BR.json +3 -1
- package/src/shared/page-settings/components/page-settings/i18n/page-settings.pt-PT.json +3 -1
- package/src/shared/page-settings/components/page-settings-section-styles/HeaderButtonSettings.test.ts +305 -0
- package/src/shared/page-settings/components/page-settings-section-styles/HeaderButtonSettings.vue +265 -0
- package/src/shared/page-settings/components/page-settings-section-styles/HeaderDisplayStyle.test.ts +354 -0
- package/src/shared/page-settings/components/page-settings-section-styles/HeaderDisplayStyle.vue +48 -9
- package/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.header-button.test.ts +114 -0
- package/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.ts +38 -1
- package/src/shared/page-settings/components/page-settings-section-styles/i18n/page-settings-section.de-DE.json +6 -1
- package/src/shared/page-settings/components/page-settings-section-styles/i18n/page-settings-section.es-ES.json +6 -1
- package/src/shared/page-settings/components/page-settings-section-styles/i18n/page-settings-section.fr-CA.json +6 -1
- package/src/shared/page-settings/components/page-settings-section-styles/i18n/page-settings-section.fr-FR.json +6 -1
- package/src/shared/page-settings/components/page-settings-section-styles/i18n/page-settings-section.json +6 -1
- package/src/shared/page-settings/components/page-settings-section-styles/i18n/page-settings-section.pt-BR.json +6 -1
- package/src/shared/page-settings/components/page-settings-section-styles/i18n/page-settings-section.pt-PT.json +6 -1
- package/src/shared/page-settings/duplicate-page.util.test.ts +382 -0
- package/src/shared/page-settings/duplicate-page.util.ts +124 -0
- package/src/shared/page-settings/models.ts +1 -0
- package/src/shared/page-settings/store/page-settings-store.test.ts +441 -1
- package/src/shared/page-settings/store/page-settings-store.ts +147 -11
- package/src/shared/page-settings/utils.ts +10 -0
|
@@ -3,13 +3,14 @@ import type Vue from 'vue';
|
|
|
3
3
|
import { createApp } from 'vue';
|
|
4
4
|
import { createPinia, setActivePinia } from 'pinia';
|
|
5
5
|
import type { ContentPage } from '../models';
|
|
6
|
-
import type { TextContent } from '@brand-page';
|
|
6
|
+
import type { Content, TextContent } from '@brand-page';
|
|
7
7
|
import type { ContentItem } from '../../content-settings/models';
|
|
8
8
|
import {
|
|
9
9
|
UNLIMITED_MAX_NUM_OF_PAGES,
|
|
10
10
|
useBrandPageContainerStore,
|
|
11
11
|
useBrandPageMakerStore,
|
|
12
12
|
useBrandPageMakerUserStore,
|
|
13
|
+
useUiStore,
|
|
13
14
|
} from '@brand-page-maker/stores';
|
|
14
15
|
import {
|
|
15
16
|
INITIALISE_PAYLOAD,
|
|
@@ -19,6 +20,9 @@ import { useUiPageSettingsStore } from './index';
|
|
|
19
20
|
import { FabricRenderClient } from '../../../clients/fabric-render-client';
|
|
20
21
|
import { setLocaleAsync } from '@src/composables/useTranslate';
|
|
21
22
|
import { usePageNavigationStore } from '@src/shared/page-navigation/store/page-navigation-store';
|
|
23
|
+
import { useShopConfigStore } from '@shared/ecommerce-settings/store/shop-settings-store';
|
|
24
|
+
import { useEventTracker } from '@brand-page-maker/composables/useEventTracker';
|
|
25
|
+
import { toPageModels } from '../utils';
|
|
22
26
|
|
|
23
27
|
vi.mock('../../../clients/brand-page-client', () => ({
|
|
24
28
|
default: {
|
|
@@ -371,3 +375,439 @@ describe('page store module', () => {
|
|
|
371
375
|
});
|
|
372
376
|
});
|
|
373
377
|
});
|
|
378
|
+
|
|
379
|
+
describe('duplicatePage', () => {
|
|
380
|
+
const initialiseAsync = async () => {
|
|
381
|
+
const store = useBrandPageMakerStore();
|
|
382
|
+
const userStore = useBrandPageMakerUserStore();
|
|
383
|
+
const pageSettingsStore = useUiPageSettingsStore();
|
|
384
|
+
|
|
385
|
+
userStore.setUserState({
|
|
386
|
+
features: {
|
|
387
|
+
...USER_FEATURES,
|
|
388
|
+
maxNumOfPages: UNLIMITED_MAX_NUM_OF_PAGES,
|
|
389
|
+
},
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// No setPages here: injecting route-less pages would leave `pages` and
|
|
393
|
+
// `nav.orders` describing different sites, and duplicatePage re-derives
|
|
394
|
+
// one from the other.
|
|
395
|
+
await store.initialise(INITIALISE_PAYLOAD);
|
|
396
|
+
|
|
397
|
+
return {
|
|
398
|
+
pageSettingsStore,
|
|
399
|
+
pageNavigationStore: usePageNavigationStore(),
|
|
400
|
+
userStore,
|
|
401
|
+
};
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
beforeAll(async () => {
|
|
405
|
+
FabricRenderClient.setInstance({ baseUrl: 'http://localhost:3000' });
|
|
406
|
+
|
|
407
|
+
setActivePinia(createPinia());
|
|
408
|
+
|
|
409
|
+
await setLocaleAsync('en-US');
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
test('inserts the copy directly below the source page', async () => {
|
|
413
|
+
const { pageSettingsStore } = await initialiseAsync();
|
|
414
|
+
|
|
415
|
+
const source = pageSettingsStore.addNewPage({ title: 'About' });
|
|
416
|
+
|
|
417
|
+
const copy = await pageSettingsStore.duplicatePage({ pageId: source.id });
|
|
418
|
+
|
|
419
|
+
const ids = pageSettingsStore.pages.map((p) => p.id);
|
|
420
|
+
|
|
421
|
+
expect(ids.indexOf(copy!.id)).toEqual(ids.indexOf(source.id) + 1);
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
test('inserts the copy directly below the source route in orders', async () => {
|
|
425
|
+
const { pageSettingsStore, pageNavigationStore } = await initialiseAsync();
|
|
426
|
+
|
|
427
|
+
const source = pageSettingsStore.addNewPage({ title: 'About' });
|
|
428
|
+
const sourceRoute = pageNavigationStore.routes.find(
|
|
429
|
+
(r) => r.pageId === source.id,
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
const copy = await pageSettingsStore.duplicatePage({ pageId: source.id });
|
|
433
|
+
|
|
434
|
+
const copyRoute = pageNavigationStore.routes.find(
|
|
435
|
+
(r) => r.pageId === copy?.id,
|
|
436
|
+
);
|
|
437
|
+
const sourceOrderIndex = pageNavigationStore.orders.findIndex(
|
|
438
|
+
(o) => o.routeId === sourceRoute?.id,
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
expect(pageNavigationStore.orders[sourceOrderIndex + 1].routeId).toEqual(
|
|
442
|
+
copyRoute?.id,
|
|
443
|
+
);
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
test('names the copy after the source and derives its slug', async () => {
|
|
447
|
+
const { pageSettingsStore, pageNavigationStore } = await initialiseAsync();
|
|
448
|
+
|
|
449
|
+
const source = pageSettingsStore.addNewPage({ title: 'About' });
|
|
450
|
+
|
|
451
|
+
const copy = await pageSettingsStore.duplicatePage({ pageId: source.id });
|
|
452
|
+
|
|
453
|
+
expect(copy?.title).toEqual('About Copy');
|
|
454
|
+
expect(
|
|
455
|
+
pageNavigationStore.routes.find((r) => r.pageId === copy?.id)?.pageSlug,
|
|
456
|
+
).toEqual('about-copy');
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
test('indexes the title when the same page is duplicated twice', async () => {
|
|
460
|
+
const { pageSettingsStore } = await initialiseAsync();
|
|
461
|
+
|
|
462
|
+
const source = pageSettingsStore.addNewPage({ title: 'About' });
|
|
463
|
+
|
|
464
|
+
await pageSettingsStore.duplicatePage({ pageId: source.id });
|
|
465
|
+
|
|
466
|
+
const second = await pageSettingsStore.duplicatePage({
|
|
467
|
+
pageId: source.id,
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
expect(second?.title).toEqual('About Copy 2');
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
test('deep clones contents with fresh ids', async () => {
|
|
474
|
+
const { pageSettingsStore } = await initialiseAsync();
|
|
475
|
+
|
|
476
|
+
const source = pageSettingsStore.addNewPage({ title: 'About' });
|
|
477
|
+
|
|
478
|
+
pageSettingsStore.setPageContents({
|
|
479
|
+
pageId: source.id,
|
|
480
|
+
contents: [
|
|
481
|
+
{
|
|
482
|
+
id: 'content-1',
|
|
483
|
+
type: 'text',
|
|
484
|
+
settings: { content: { id: 'content-1', text: 'hello' } },
|
|
485
|
+
} as unknown as ContentItem<TextContent>,
|
|
486
|
+
],
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
const copy = await pageSettingsStore.duplicatePage({ pageId: source.id });
|
|
490
|
+
|
|
491
|
+
expect(copy?.contents).toHaveLength(1);
|
|
492
|
+
expect(copy?.contents[0].id).not.toEqual('content-1');
|
|
493
|
+
expect(copy?.contents[0].id).toEqual(copy?.contents[0].settings.content.id);
|
|
494
|
+
|
|
495
|
+
const sourcePage = pageSettingsStore.pages.find((p) => p.id === source.id);
|
|
496
|
+
|
|
497
|
+
expect(sourcePage?.contents[0].id).toEqual('content-1');
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
test('keeps a duplicated sub-page under the same parent', async () => {
|
|
501
|
+
const { pageSettingsStore, pageNavigationStore } = await initialiseAsync();
|
|
502
|
+
|
|
503
|
+
const parent = pageSettingsStore.addNewPage({ title: 'Portfolio' });
|
|
504
|
+
const child = pageSettingsStore.addNewPage({
|
|
505
|
+
title: 'Portfolio 1',
|
|
506
|
+
newPageParentId: parent.id,
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
const copy = await pageSettingsStore.duplicatePage({ pageId: child.id });
|
|
510
|
+
|
|
511
|
+
expect(copy?.title).toEqual('Portfolio 1 Copy');
|
|
512
|
+
expect(
|
|
513
|
+
pageNavigationStore.getParentRouteIdForPage(copy?.id as string),
|
|
514
|
+
).toEqual(pageNavigationStore.getParentRouteIdForPage(child.id));
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
test('duplicating a parent copies only the parent', async () => {
|
|
518
|
+
const { pageSettingsStore, pageNavigationStore } = await initialiseAsync();
|
|
519
|
+
|
|
520
|
+
const parent = pageSettingsStore.addNewPage({ title: 'Portfolio' });
|
|
521
|
+
|
|
522
|
+
pageSettingsStore.addNewPage({
|
|
523
|
+
title: 'Portfolio 1',
|
|
524
|
+
newPageParentId: parent.id,
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
const copy = await pageSettingsStore.duplicatePage({ pageId: parent.id });
|
|
528
|
+
|
|
529
|
+
const copyRoute = pageNavigationStore.routes.find(
|
|
530
|
+
(r) => r.pageId === copy?.id,
|
|
531
|
+
);
|
|
532
|
+
const copyOrder = pageNavigationStore.orders.find(
|
|
533
|
+
(o) => o.routeId === copyRoute?.id,
|
|
534
|
+
);
|
|
535
|
+
|
|
536
|
+
expect(copyOrder?.subRoutes).toEqual([]);
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
test("a parent copy is the source's next sibling in orders", async () => {
|
|
540
|
+
const { pageSettingsStore, pageNavigationStore } = await initialiseAsync();
|
|
541
|
+
|
|
542
|
+
const parent = pageSettingsStore.addNewPage({ title: 'Portfolio' });
|
|
543
|
+
|
|
544
|
+
pageSettingsStore.addNewPage({
|
|
545
|
+
title: 'Portfolio 1',
|
|
546
|
+
newPageParentId: parent.id,
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
const parentRoute = pageNavigationStore.routes.find(
|
|
550
|
+
(r) => r.pageId === parent.id,
|
|
551
|
+
);
|
|
552
|
+
const copy = await pageSettingsStore.duplicatePage({ pageId: parent.id });
|
|
553
|
+
const copyRoute = pageNavigationStore.routes.find(
|
|
554
|
+
(r) => r.pageId === copy?.id,
|
|
555
|
+
);
|
|
556
|
+
|
|
557
|
+
const parentOrderIndex = pageNavigationStore.orders.findIndex(
|
|
558
|
+
(o) => o.routeId === parentRoute?.id,
|
|
559
|
+
);
|
|
560
|
+
|
|
561
|
+
expect(pageNavigationStore.orders[parentOrderIndex + 1].routeId).toEqual(
|
|
562
|
+
copyRoute?.id,
|
|
563
|
+
);
|
|
564
|
+
|
|
565
|
+
// pages is flat and must stay depth-first with orders, so the copy sits
|
|
566
|
+
// after the source's sub-tree - not between the parent and its children.
|
|
567
|
+
const titles = pageSettingsStore.pages.map((p) => p.title);
|
|
568
|
+
|
|
569
|
+
expect(titles.indexOf('Portfolio Copy')).toEqual(
|
|
570
|
+
titles.indexOf('Portfolio 1') + 1,
|
|
571
|
+
);
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
test('a hidden page yields a hidden copy that stays beside it', async () => {
|
|
575
|
+
const { pageSettingsStore, pageNavigationStore } = await initialiseAsync();
|
|
576
|
+
|
|
577
|
+
const source = pageSettingsStore.addNewPage({ title: 'About' });
|
|
578
|
+
const sourceRoute = pageNavigationStore.routes.find(
|
|
579
|
+
(r) => r.pageId === source.id,
|
|
580
|
+
);
|
|
581
|
+
|
|
582
|
+
pageSettingsStore.setPageVisibility({ isHidden: true, pageId: source.id });
|
|
583
|
+
pageNavigationStore.updateRouteVisibility(sourceRoute?.id as string, true);
|
|
584
|
+
|
|
585
|
+
const copy = await pageSettingsStore.duplicatePage({ pageId: source.id });
|
|
586
|
+
|
|
587
|
+
const copyRoute = pageNavigationStore.routes.find(
|
|
588
|
+
(r) => r.pageId === copy?.id,
|
|
589
|
+
);
|
|
590
|
+
|
|
591
|
+
expect(copy?.isHidden).toBe(true);
|
|
592
|
+
expect(copyRoute?.isHidden).toBe(true);
|
|
593
|
+
|
|
594
|
+
// reorderVisibleAndHiddenRoutes partitions root orders by visibility, so a
|
|
595
|
+
// visible copy of a hidden page would be pulled away from its source.
|
|
596
|
+
const sourceOrderIndex = pageNavigationStore.orders.findIndex(
|
|
597
|
+
(o) => o.routeId === sourceRoute?.id,
|
|
598
|
+
);
|
|
599
|
+
|
|
600
|
+
expect(pageNavigationStore.orders[sourceOrderIndex + 1].routeId).toEqual(
|
|
601
|
+
copyRoute?.id,
|
|
602
|
+
);
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
test('makes the copy the active page', async () => {
|
|
606
|
+
const { pageSettingsStore, pageNavigationStore } = await initialiseAsync();
|
|
607
|
+
|
|
608
|
+
const source = pageSettingsStore.addNewPage({ title: 'About' });
|
|
609
|
+
|
|
610
|
+
const copy = await pageSettingsStore.duplicatePage({ pageId: source.id });
|
|
611
|
+
|
|
612
|
+
expect(pageNavigationStore.activePageId).toEqual(copy?.id);
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
test('at the page limit adds nothing and attributes the upsell to duplicate_page', async () => {
|
|
616
|
+
const { pageSettingsStore, userStore } = await initialiseAsync();
|
|
617
|
+
|
|
618
|
+
userStore.setUserState({
|
|
619
|
+
features: {
|
|
620
|
+
...USER_FEATURES,
|
|
621
|
+
maxNumOfPages: pageSettingsStore.pages.length,
|
|
622
|
+
},
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
const pageCount = pageSettingsStore.pages.length;
|
|
626
|
+
|
|
627
|
+
const uiStore = useUiStore();
|
|
628
|
+
const modalSpy = vi
|
|
629
|
+
.spyOn(uiStore, 'showUpgradeModalAndSaveAsync')
|
|
630
|
+
.mockResolvedValue(undefined as never);
|
|
631
|
+
|
|
632
|
+
const copy = await pageSettingsStore.duplicatePage({
|
|
633
|
+
pageId: pageSettingsStore.pages[0].id,
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
expect(copy).toBeUndefined();
|
|
637
|
+
expect(pageSettingsStore.pages).toHaveLength(pageCount);
|
|
638
|
+
expect(modalSpy).toHaveBeenCalled();
|
|
639
|
+
|
|
640
|
+
// Attribution is the reason DUPLICATE_PAGE exists - without this the funnel
|
|
641
|
+
// can silently regress to add_new_page and still look fine. The bridge
|
|
642
|
+
// state is module-level, so it is read back through a published event.
|
|
643
|
+
const eventTracker = useEventTracker();
|
|
644
|
+
const events: { eventProps: { cta_location?: string } }[] = [];
|
|
645
|
+
|
|
646
|
+
eventTracker.subscribe((e) => events.push(e));
|
|
647
|
+
// sendUpsellOpenEvent would overwrite cta_location with its own optional arg.
|
|
648
|
+
eventTracker.sendUpsellContinuationEvent('exit');
|
|
649
|
+
|
|
650
|
+
expect(events[events.length - 1]?.eventProps.cta_location).toEqual(
|
|
651
|
+
'duplicate_page',
|
|
652
|
+
);
|
|
653
|
+
|
|
654
|
+
modalSpy.mockRestore();
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
test("the copy's page and nav route both live in brandPageData", async () => {
|
|
658
|
+
// Undo restores brandPageData wholesale, so anything not in it survives an
|
|
659
|
+
// undo. Both halves of a duplicate have to be in there or undo would drop
|
|
660
|
+
// the page and strand its nav entry.
|
|
661
|
+
const { pageSettingsStore } = await initialiseAsync();
|
|
662
|
+
const makerStore = useBrandPageMakerStore();
|
|
663
|
+
|
|
664
|
+
const source = pageSettingsStore.addNewPage({ title: 'About' });
|
|
665
|
+
const copy = await pageSettingsStore.duplicatePage({ pageId: source.id });
|
|
666
|
+
|
|
667
|
+
const snapshot = makerStore.brandPageData;
|
|
668
|
+
|
|
669
|
+
expect(snapshot.pages.some((p) => p.id === copy?.id)).toBe(true);
|
|
670
|
+
expect(snapshot.nav.routes.some((r) => r.pageId === copy?.id)).toBe(true);
|
|
671
|
+
expect(
|
|
672
|
+
snapshot.nav.orders.some((o) =>
|
|
673
|
+
snapshot.nav.routes.some(
|
|
674
|
+
(r) => r.id === o.routeId && r.pageId === copy?.id,
|
|
675
|
+
),
|
|
676
|
+
),
|
|
677
|
+
).toBe(true);
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
test('adds the copy shop products to the site-wide catalogue', async () => {
|
|
681
|
+
// The catalogue is derived from the shop blocks and is what the save
|
|
682
|
+
// upserts. Without a re-derive the copy's fresh product ids never reach the
|
|
683
|
+
// backend and checkout cannot resolve them on the copied page.
|
|
684
|
+
const { pageSettingsStore } = await initialiseAsync();
|
|
685
|
+
const shopConfigStore = useShopConfigStore();
|
|
686
|
+
|
|
687
|
+
const source = pageSettingsStore.addNewPage({ title: 'Shop' });
|
|
688
|
+
|
|
689
|
+
pageSettingsStore.setPageContents({
|
|
690
|
+
pageId: source.id,
|
|
691
|
+
contents: [
|
|
692
|
+
{
|
|
693
|
+
id: 'shop-1',
|
|
694
|
+
type: 'shop-section',
|
|
695
|
+
settings: {
|
|
696
|
+
content: {
|
|
697
|
+
id: 'shop-1',
|
|
698
|
+
type: 'shop-section',
|
|
699
|
+
products: [
|
|
700
|
+
{ id: 'product-1', name: 'Latte', variant: [], skus: [] },
|
|
701
|
+
],
|
|
702
|
+
shopPaymentConfig: { paymentConfig: { id: 'stripe-1' } },
|
|
703
|
+
},
|
|
704
|
+
},
|
|
705
|
+
} as unknown as ContentItem<Content>,
|
|
706
|
+
],
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
shopConfigStore.initialiseShopStore({
|
|
710
|
+
paymentConfig: { id: 'stripe-1' } as never,
|
|
711
|
+
pages: toPageModels(pageSettingsStore.pages) as never,
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
expect(shopConfigStore.products).toHaveLength(1);
|
|
715
|
+
|
|
716
|
+
await pageSettingsStore.duplicatePage({ pageId: source.id });
|
|
717
|
+
|
|
718
|
+
expect(shopConfigStore.products).toHaveLength(2);
|
|
719
|
+
|
|
720
|
+
const ids = shopConfigStore.products.map((p) => p.id);
|
|
721
|
+
|
|
722
|
+
expect(new Set(ids).size).toBe(2);
|
|
723
|
+
expect(shopConfigStore.paymentConfig?.id).toEqual('stripe-1');
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
test('is a no-op for an unknown page id', async () => {
|
|
727
|
+
const { pageSettingsStore } = await initialiseAsync();
|
|
728
|
+
|
|
729
|
+
const pageCount = pageSettingsStore.pages.length;
|
|
730
|
+
|
|
731
|
+
const copy = await pageSettingsStore.duplicatePage({
|
|
732
|
+
pageId: 'does-not-exist',
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
expect(copy).toBeUndefined();
|
|
736
|
+
expect(pageSettingsStore.pages).toHaveLength(pageCount);
|
|
737
|
+
});
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
describe('syncPagesWithOrders', () => {
|
|
741
|
+
const initialiseAsync = async () => {
|
|
742
|
+
const store = useBrandPageMakerStore();
|
|
743
|
+
const userStore = useBrandPageMakerUserStore();
|
|
744
|
+
const pageSettingsStore = useUiPageSettingsStore();
|
|
745
|
+
|
|
746
|
+
userStore.setUserState({
|
|
747
|
+
features: {
|
|
748
|
+
...USER_FEATURES,
|
|
749
|
+
maxNumOfPages: UNLIMITED_MAX_NUM_OF_PAGES,
|
|
750
|
+
},
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
await store.initialise(INITIALISE_PAYLOAD);
|
|
754
|
+
|
|
755
|
+
return { pageSettingsStore, pageNavigationStore: usePageNavigationStore() };
|
|
756
|
+
};
|
|
757
|
+
|
|
758
|
+
beforeAll(async () => {
|
|
759
|
+
FabricRenderClient.setInstance({ baseUrl: 'http://localhost:3000' });
|
|
760
|
+
|
|
761
|
+
setActivePinia(createPinia());
|
|
762
|
+
|
|
763
|
+
await setLocaleAsync('en-US');
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
test('reorders pages to match the order of their routes', async () => {
|
|
767
|
+
const { pageSettingsStore, pageNavigationStore } = await initialiseAsync();
|
|
768
|
+
|
|
769
|
+
pageSettingsStore.addNewPage({ title: 'About' });
|
|
770
|
+
pageSettingsStore.addNewPage({ title: 'Contact' });
|
|
771
|
+
|
|
772
|
+
pageNavigationStore.orders.reverse();
|
|
773
|
+
|
|
774
|
+
pageSettingsStore.syncPagesWithOrders();
|
|
775
|
+
|
|
776
|
+
const orderedPageIds = pageNavigationStore.orders.map(
|
|
777
|
+
(order) =>
|
|
778
|
+
pageNavigationStore.routes.find((r) => r.id === order.routeId)?.pageId,
|
|
779
|
+
);
|
|
780
|
+
|
|
781
|
+
expect(pageSettingsStore.pages.map((p) => p.id)).toEqual(orderedPageIds);
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
test('keeps a page that has no route yet', async () => {
|
|
785
|
+
const { pageSettingsStore } = await initialiseAsync();
|
|
786
|
+
|
|
787
|
+
const orphan = { ...pageSettingsStore.pages[0], id: 'no-route-yet' };
|
|
788
|
+
|
|
789
|
+
pageSettingsStore.setPages({
|
|
790
|
+
pages: [...pageSettingsStore.pages, orphan],
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
pageSettingsStore.syncPagesWithOrders();
|
|
794
|
+
|
|
795
|
+
expect(pageSettingsStore.pages.map((p) => p.id)).toContain('no-route-yet');
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
test('leaves pages untouched when no route resolves to a page', async () => {
|
|
799
|
+
const { pageSettingsStore, pageNavigationStore } = await initialiseAsync();
|
|
800
|
+
|
|
801
|
+
pageSettingsStore.addNewPage({ title: 'About' });
|
|
802
|
+
|
|
803
|
+
const before = pageSettingsStore.pages.map((p) => p.id);
|
|
804
|
+
|
|
805
|
+
// Mid-flight state: orders exist but none of them names a page yet.
|
|
806
|
+
// Re-deriving from that would wipe the site.
|
|
807
|
+
pageNavigationStore.routes = [];
|
|
808
|
+
|
|
809
|
+
pageSettingsStore.syncPagesWithOrders();
|
|
810
|
+
|
|
811
|
+
expect(pageSettingsStore.pages.map((p) => p.id)).toEqual(before);
|
|
812
|
+
});
|
|
813
|
+
});
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import type { NavigationType } from '@brand-page';
|
|
13
13
|
import type {
|
|
14
14
|
Content,
|
|
15
|
+
HeaderButton,
|
|
15
16
|
ImageGalleryItem,
|
|
16
17
|
LinksItem,
|
|
17
18
|
LogoImageModel,
|
|
@@ -19,11 +20,17 @@ import type {
|
|
|
19
20
|
LogoTemplateImageModel,
|
|
20
21
|
BrandPagePaymentConfig,
|
|
21
22
|
BrandPagePaymentConfigCurrency,
|
|
23
|
+
NavOrder,
|
|
22
24
|
NavRoute,
|
|
23
25
|
} from '@brand-page';
|
|
24
26
|
import { v4 as uuid } from 'uuid';
|
|
25
27
|
import type { ContentPage } from '../models';
|
|
26
28
|
import { PAGE_SECTIONS } from '../models';
|
|
29
|
+
import { toPageModels } from '../utils';
|
|
30
|
+
import {
|
|
31
|
+
duplicatePageContents,
|
|
32
|
+
getDuplicatePageTitle,
|
|
33
|
+
} from '../duplicate-page.util';
|
|
27
34
|
import { navigateToPageByPageId } from '@brand-page-maker/routing';
|
|
28
35
|
import { getPageSlugFromPageTitle } from '@brand-page-maker/utils';
|
|
29
36
|
import { useUiContentSettingsStore } from '../../content-settings/store';
|
|
@@ -162,6 +169,71 @@ export const useUiPageSettingsStore = defineStore('uiPageSettings', {
|
|
|
162
169
|
setPages({ pages }: { pages: ContentPage[] }): void {
|
|
163
170
|
this.pages = pages;
|
|
164
171
|
},
|
|
172
|
+
// `orders` is the hierarchy; `pages` is a flat list that has to match it
|
|
173
|
+
// depth-first, because row indexes drive the home check and the hide/delete
|
|
174
|
+
// gating. Anything reshaping orders should re-derive pages after - except
|
|
175
|
+
// addNewPage, which predates this and appends by visibility instead; the
|
|
176
|
+
// next drag-end catches it up.
|
|
177
|
+
syncPagesWithOrders(): void {
|
|
178
|
+
const pageNavigationStore = usePageNavigationStore();
|
|
179
|
+
const pageIdOrder: string[] = [];
|
|
180
|
+
|
|
181
|
+
const collectPageIds = (orders: NavOrder[]): void => {
|
|
182
|
+
for (const order of orders) {
|
|
183
|
+
const route = pageNavigationStore.routes.find(
|
|
184
|
+
(r) => r.id === order.routeId,
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
if (route?.pageId) {
|
|
188
|
+
pageIdOrder.push(route.pageId);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (order.subRoutes?.length) {
|
|
192
|
+
collectPageIds(order.subRoutes);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
collectPageIds(pageNavigationStore.orders);
|
|
198
|
+
|
|
199
|
+
if (!pageIdOrder.length) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const pageMap = new Map<string, ContentPage>(
|
|
204
|
+
this.pages.map((page) => [page.id, page]),
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
const orderedPages: ContentPage[] = [];
|
|
208
|
+
|
|
209
|
+
for (const pageId of pageIdOrder) {
|
|
210
|
+
const page = pageMap.get(pageId);
|
|
211
|
+
|
|
212
|
+
if (page) {
|
|
213
|
+
orderedPages.push(page);
|
|
214
|
+
pageMap.delete(pageId);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// A page with no route would otherwise vanish.
|
|
219
|
+
orderedPages.push(...pageMap.values());
|
|
220
|
+
|
|
221
|
+
this.setPages({ pages: orderedPages });
|
|
222
|
+
},
|
|
223
|
+
async showPageLimitUpgradeModalAsync(ctaLocation: string): Promise<void> {
|
|
224
|
+
const uiStore = useUiStore();
|
|
225
|
+
const eventTracker = useEventTracker();
|
|
226
|
+
|
|
227
|
+
eventTracker.setUpsellBridgeState({
|
|
228
|
+
cta_location: ctaLocation,
|
|
229
|
+
trigger_type: TRIGGER_TYPE.AUTOMATIC,
|
|
230
|
+
content_type: null,
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
await uiStore.showUpgradeModalAndSaveAsync(
|
|
234
|
+
WEBSITE_UPGRADE_CONTEXT_TYPES.AddPage,
|
|
235
|
+
);
|
|
236
|
+
},
|
|
165
237
|
async setNewPageModalVisibilityAsync({
|
|
166
238
|
isNewPageModalVisible,
|
|
167
239
|
parentPageId,
|
|
@@ -169,18 +241,9 @@ export const useUiPageSettingsStore = defineStore('uiPageSettings', {
|
|
|
169
241
|
isNewPageModalVisible: boolean;
|
|
170
242
|
parentPageId?: string;
|
|
171
243
|
}) {
|
|
172
|
-
const uiStore = useUiStore();
|
|
173
|
-
const eventTracker = useEventTracker();
|
|
174
|
-
|
|
175
244
|
if (isNewPageModalVisible && !this.canAddPage) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
trigger_type: TRIGGER_TYPE.AUTOMATIC,
|
|
179
|
-
content_type: null,
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
await uiStore.showUpgradeModalAndSaveAsync(
|
|
183
|
-
WEBSITE_UPGRADE_CONTEXT_TYPES.AddPage,
|
|
245
|
+
await this.showPageLimitUpgradeModalAsync(
|
|
246
|
+
UPGRADE_CTA_LOCATIONS.ADD_NEW_PAGE,
|
|
184
247
|
);
|
|
185
248
|
|
|
186
249
|
return;
|
|
@@ -242,6 +305,7 @@ export const useUiPageSettingsStore = defineStore('uiPageSettings', {
|
|
|
242
305
|
| LinksItem[]
|
|
243
306
|
| BrandPageCssStyle
|
|
244
307
|
| Partial<ImageGalleryItem>
|
|
308
|
+
| HeaderButton
|
|
245
309
|
| number
|
|
246
310
|
| null
|
|
247
311
|
| undefined;
|
|
@@ -475,6 +539,78 @@ export const useUiPageSettingsStore = defineStore('uiPageSettings', {
|
|
|
475
539
|
|
|
476
540
|
return newPage;
|
|
477
541
|
},
|
|
542
|
+
async duplicatePage({
|
|
543
|
+
pageId,
|
|
544
|
+
}: {
|
|
545
|
+
pageId: string;
|
|
546
|
+
}): Promise<ContentPage | undefined> {
|
|
547
|
+
const pageNavigationStore = usePageNavigationStore();
|
|
548
|
+
|
|
549
|
+
// Gate first: a paywalled duplicate must not leave a half-created page.
|
|
550
|
+
if (!this.canAddPage) {
|
|
551
|
+
await this.showPageLimitUpgradeModalAsync(
|
|
552
|
+
UPGRADE_CTA_LOCATIONS.DUPLICATE_PAGE,
|
|
553
|
+
);
|
|
554
|
+
|
|
555
|
+
return undefined;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const sourcePage = this.pages.find((page) => page.id === pageId);
|
|
559
|
+
|
|
560
|
+
const sourceRoute = pageNavigationStore.routes.find(
|
|
561
|
+
(route) => route.pageId === pageId,
|
|
562
|
+
);
|
|
563
|
+
|
|
564
|
+
if (!sourcePage || !sourceRoute) {
|
|
565
|
+
return undefined;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
const parentRouteId = pageNavigationStore.getParentRouteIdForPage(pageId);
|
|
569
|
+
|
|
570
|
+
const title = getDuplicatePageTitle(
|
|
571
|
+
sourcePage.title,
|
|
572
|
+
pageNavigationStore.getDisplayNamesForDepth(parentRouteId),
|
|
573
|
+
Boolean(parentRouteId),
|
|
574
|
+
);
|
|
575
|
+
|
|
576
|
+
const newPage: ContentPage = {
|
|
577
|
+
id: uuid(),
|
|
578
|
+
title,
|
|
579
|
+
contents: duplicatePageContents(sourcePage.contents),
|
|
580
|
+
// A copy of a hidden page stays hidden, or it publishes into the nav.
|
|
581
|
+
isHidden: sourcePage.isHidden,
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
// addNavRoute navigates to the copy and reads its contents, so the page
|
|
585
|
+
// has to exist before the route does. syncPagesWithOrders places it.
|
|
586
|
+
this.setPages({ pages: [...this.pages, newPage] });
|
|
587
|
+
|
|
588
|
+
const newRoute: NavRoute = {
|
|
589
|
+
id: uuid(),
|
|
590
|
+
pageId: newPage.id,
|
|
591
|
+
pageSlug: getPageSlugFromPageTitle(
|
|
592
|
+
title,
|
|
593
|
+
pageNavigationStore.getPageSlugsForDepth(parentRouteId),
|
|
594
|
+
),
|
|
595
|
+
isHidden: Boolean(sourceRoute.isHidden),
|
|
596
|
+
displayName: title,
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
pageNavigationStore.addNavRoute(newRoute, parentRouteId, sourceRoute.id);
|
|
600
|
+
|
|
601
|
+
this.syncPagesWithOrders();
|
|
602
|
+
|
|
603
|
+
// shopConfig.products is DERIVED from the shop blocks, not updated with
|
|
604
|
+
// them - without this the copy's fresh product ids never reach the save.
|
|
605
|
+
const shopConfigStore = useShopConfigStore();
|
|
606
|
+
|
|
607
|
+
shopConfigStore.initialiseShopStore({
|
|
608
|
+
paymentConfig: shopConfigStore.paymentConfig,
|
|
609
|
+
pages: toPageModels(this.pages),
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
return newPage;
|
|
613
|
+
},
|
|
478
614
|
setSelectedSectionName({
|
|
479
615
|
sectionName,
|
|
480
616
|
}: {
|
|
@@ -1,8 +1,18 @@
|
|
|
1
|
+
import type { PageModel } from '@brand-page';
|
|
1
2
|
import { errorTr } from '@src/composables/useTranslate';
|
|
3
|
+
import type { ContentPage } from './models';
|
|
2
4
|
|
|
3
5
|
export const MAX_PAGE_TITLE_LENGTH = 30;
|
|
4
6
|
export const MAX_SUBPAGE_TITLE_LENGTH = 20;
|
|
5
7
|
|
|
8
|
+
// The maker holds a page's sections as ContentItems (settings wrapper + content);
|
|
9
|
+
// everything that consumes a BrandPageModel wants the bare content.
|
|
10
|
+
export const toPageModels = (pages: ContentPage[]): PageModel[] =>
|
|
11
|
+
pages.map((page) => ({
|
|
12
|
+
...page,
|
|
13
|
+
contents: page.contents.map((item) => item.settings.content),
|
|
14
|
+
})) as PageModel[];
|
|
15
|
+
|
|
6
16
|
export const getPageTitleError = (
|
|
7
17
|
pageTitle: string,
|
|
8
18
|
isSubPage: boolean,
|