@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
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { describe, test, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
BrandPageModel,
|
|
4
|
+
BUTTON_LINK_TYPE,
|
|
5
|
+
PageHeaderDesktop,
|
|
6
|
+
PageHeaderMobile,
|
|
7
|
+
type HeaderButton,
|
|
8
|
+
} from './index';
|
|
9
|
+
|
|
10
|
+
const aHeaderButton = (): HeaderButton => ({
|
|
11
|
+
isVisible: true,
|
|
12
|
+
text: 'Contact Us',
|
|
13
|
+
buttonLinkType: BUTTON_LINK_TYPE.url,
|
|
14
|
+
url: 'https://example.com',
|
|
15
|
+
buttonTextStyle: { textStyleType: 'buttons' },
|
|
16
|
+
fillColor: '#123456',
|
|
17
|
+
borderColor: '#654321',
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// The constructors copy fields one-by-one — a forgotten field is silently dropped on load.
|
|
21
|
+
describe('header button persistence', () => {
|
|
22
|
+
describe('PageHeader* constructor', () => {
|
|
23
|
+
test('keeps a persisted headerButton on both viewports', () => {
|
|
24
|
+
expect(
|
|
25
|
+
new PageHeaderDesktop({ headerButton: aHeaderButton() }).headerButton,
|
|
26
|
+
).toEqual(aHeaderButton());
|
|
27
|
+
expect(
|
|
28
|
+
new PageHeaderMobile({ headerButton: aHeaderButton() }).headerButton,
|
|
29
|
+
).toEqual(aHeaderButton());
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('leaves headerButton unset when absent (no default seeded)', () => {
|
|
33
|
+
expect(new PageHeaderDesktop().headerButton).toBeUndefined();
|
|
34
|
+
expect(new PageHeaderMobile().headerButton).toBeUndefined();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('keeps a page link button with buttonPageId', () => {
|
|
38
|
+
const button: HeaderButton = {
|
|
39
|
+
isVisible: true,
|
|
40
|
+
text: 'About',
|
|
41
|
+
buttonLinkType: BUTTON_LINK_TYPE.page,
|
|
42
|
+
buttonPageId: 'page-2',
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
expect(
|
|
46
|
+
new PageHeaderDesktop({ headerButton: button }).headerButton,
|
|
47
|
+
).toEqual(button);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('BrandPageModel round-trip', () => {
|
|
52
|
+
test('survives deserialization from persisted JSON on both viewports', () => {
|
|
53
|
+
const model = new BrandPageModel({
|
|
54
|
+
styles: {},
|
|
55
|
+
pages: [],
|
|
56
|
+
pageHeaderDesktop: { headerButton: aHeaderButton() },
|
|
57
|
+
pageHeaderMobile: { headerButton: aHeaderButton() },
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
expect(model.pageHeaderDesktop.headerButton).toEqual(aHeaderButton());
|
|
61
|
+
expect(model.pageHeaderMobile.headerButton).toEqual(aHeaderButton());
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('survives a second pass through the model (re-serialize then re-hydrate)', () => {
|
|
65
|
+
const first = new BrandPageModel({
|
|
66
|
+
styles: {},
|
|
67
|
+
pages: [],
|
|
68
|
+
pageHeaderDesktop: { headerButton: aHeaderButton() },
|
|
69
|
+
pageHeaderMobile: { headerButton: aHeaderButton() },
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const second = new BrandPageModel(JSON.parse(JSON.stringify(first)));
|
|
73
|
+
|
|
74
|
+
expect(second.pageHeaderDesktop.headerButton).toEqual(aHeaderButton());
|
|
75
|
+
expect(second.pageHeaderMobile.headerButton).toEqual(aHeaderButton());
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('an existing site with no headerButton stays without one', () => {
|
|
79
|
+
const model = new BrandPageModel({ styles: {}, pages: [] });
|
|
80
|
+
|
|
81
|
+
expect(model.pageHeaderDesktop.headerButton).toBeUndefined();
|
|
82
|
+
expect(model.pageHeaderMobile.headerButton).toBeUndefined();
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -713,6 +713,17 @@ export class NavModel {
|
|
|
713
713
|
}
|
|
714
714
|
}
|
|
715
715
|
|
|
716
|
+
export interface HeaderButton {
|
|
717
|
+
isVisible: boolean;
|
|
718
|
+
text: string;
|
|
719
|
+
buttonLinkType: ButtonLinkType;
|
|
720
|
+
url?: string;
|
|
721
|
+
buttonPageId?: string;
|
|
722
|
+
buttonTextStyle?: TextStyleDeclaration;
|
|
723
|
+
fillColor?: string;
|
|
724
|
+
borderColor?: string;
|
|
725
|
+
}
|
|
726
|
+
|
|
716
727
|
type AbstractPageHeaderStylesType = Partial<AbstractPageHeaderStyles>;
|
|
717
728
|
|
|
718
729
|
export abstract class AbstractPageHeaderStyles {
|
|
@@ -742,6 +753,9 @@ export abstract class AbstractPageHeaderStyles {
|
|
|
742
753
|
style: BrandPageCssStyle;
|
|
743
754
|
viewport: ViewportsType;
|
|
744
755
|
|
|
756
|
+
/** Missing = no button. `declare` keeps it off saved data so old migrations still see the same shape. */
|
|
757
|
+
declare headerButton?: HeaderButton;
|
|
758
|
+
|
|
745
759
|
protected constructor(options: AbstractPageHeaderStylesType = {}) {
|
|
746
760
|
if (typeof options.viewport === 'undefined') {
|
|
747
761
|
throw new Error('"viewport" is required');
|
|
@@ -779,6 +793,11 @@ export abstract class AbstractPageHeaderStyles {
|
|
|
779
793
|
...(options.backgroundStyle || {}),
|
|
780
794
|
backgroundOpacity: options.backgroundStyle?.backgroundOpacity ?? 100,
|
|
781
795
|
};
|
|
796
|
+
|
|
797
|
+
// Only set when given — a missing button must stay missing for old migrations.
|
|
798
|
+
if (options.headerButton) {
|
|
799
|
+
this.headerButton = options.headerButton;
|
|
800
|
+
}
|
|
782
801
|
}
|
|
783
802
|
}
|
|
784
803
|
|
|
@@ -17,3 +17,18 @@ export const getSlugFromContext = (context: BrandPageContext) => {
|
|
|
17
17
|
*/
|
|
18
18
|
export const isPreviewMode = (context: BrandPageContext): boolean =>
|
|
19
19
|
!!(context.isPreview || context.isLivePreview);
|
|
20
|
+
|
|
21
|
+
// Blocks a click in the maker preview (no link-follow, no bubbling); true when it fired.
|
|
22
|
+
export const guardPreviewClick = (
|
|
23
|
+
event: MouseEvent,
|
|
24
|
+
context: BrandPageContext,
|
|
25
|
+
): boolean => {
|
|
26
|
+
if (!context.isPreview) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
event.preventDefault();
|
|
31
|
+
event.stopPropagation();
|
|
32
|
+
|
|
33
|
+
return true;
|
|
34
|
+
};
|
|
@@ -208,3 +208,16 @@ export const findNavItemByPageSlug = (
|
|
|
208
208
|
|
|
209
209
|
return undefined;
|
|
210
210
|
};
|
|
211
|
+
|
|
212
|
+
// Nav shows when more than one page is visible, or one page has visible subpages.
|
|
213
|
+
// The header row and the side menu both key the button placement off this — one
|
|
214
|
+
// source so the two can never disagree.
|
|
215
|
+
export const hasVisibleNavigation = (navItems: NavItemViewModel[]): boolean => {
|
|
216
|
+
const visibleNavItems = navItems.filter((item) => !item.isHidden);
|
|
217
|
+
|
|
218
|
+
return (
|
|
219
|
+
visibleNavItems.length > 1 ||
|
|
220
|
+
(visibleNavItems.length === 1 &&
|
|
221
|
+
visibleNavItems[0].nestedNavItems.some((item) => !item.isHidden))
|
|
222
|
+
);
|
|
223
|
+
};
|
|
@@ -5,8 +5,14 @@ import {
|
|
|
5
5
|
type MailingListContent,
|
|
6
6
|
type ContactFormContent,
|
|
7
7
|
type ColorItem,
|
|
8
|
+
type HeaderButton,
|
|
9
|
+
} from '../models';
|
|
10
|
+
import {
|
|
11
|
+
BRAND_PAGE_TYPES,
|
|
12
|
+
BUTTON_LINK_TYPE,
|
|
13
|
+
PageHeaderDesktop,
|
|
14
|
+
PageStyles,
|
|
8
15
|
} from '../models';
|
|
9
|
-
import { BRAND_PAGE_TYPES, PageHeaderDesktop, PageStyles } from '../models';
|
|
10
16
|
import {
|
|
11
17
|
mapToBannerSectionViewModel,
|
|
12
18
|
mapToContactFormViewModel,
|
|
@@ -1114,3 +1120,147 @@ describe('container-aware text colour defaults (view-model layer)', () => {
|
|
|
1114
1120
|
expect(vm.nameStyle?.color).toBe('#00ff00');
|
|
1115
1121
|
});
|
|
1116
1122
|
});
|
|
1123
|
+
|
|
1124
|
+
describe('mapToPageHeaderViewModel header button', () => {
|
|
1125
|
+
const headerButton: HeaderButton = {
|
|
1126
|
+
isVisible: true,
|
|
1127
|
+
text: 'Contact Us',
|
|
1128
|
+
buttonLinkType: BUTTON_LINK_TYPE.url,
|
|
1129
|
+
url: 'https://example.com',
|
|
1130
|
+
buttonTextStyle: { textStyleType: 'buttons' },
|
|
1131
|
+
fillColor: '#123456',
|
|
1132
|
+
borderColor: '#654321',
|
|
1133
|
+
};
|
|
1134
|
+
|
|
1135
|
+
describe('headerButtonViewModel', () => {
|
|
1136
|
+
const mapButton = (overrides: Partial<HeaderButton> = {}) =>
|
|
1137
|
+
mapToPageHeaderViewModel(
|
|
1138
|
+
new PageHeaderDesktop({
|
|
1139
|
+
headerButton: { ...headerButton, ...overrides },
|
|
1140
|
+
}),
|
|
1141
|
+
new PageStyles(),
|
|
1142
|
+
[],
|
|
1143
|
+
).headerButtonViewModel;
|
|
1144
|
+
|
|
1145
|
+
test('is absent when the header has no button at all', () => {
|
|
1146
|
+
const vm = mapToPageHeaderViewModel(
|
|
1147
|
+
new PageHeaderDesktop(),
|
|
1148
|
+
new PageStyles(),
|
|
1149
|
+
[],
|
|
1150
|
+
);
|
|
1151
|
+
|
|
1152
|
+
expect(vm.headerButtonViewModel).toBeUndefined();
|
|
1153
|
+
});
|
|
1154
|
+
|
|
1155
|
+
test('is absent when the button is configured but hidden', () => {
|
|
1156
|
+
expect(mapButton({ isVisible: false })).toBeUndefined();
|
|
1157
|
+
});
|
|
1158
|
+
|
|
1159
|
+
test('carries the authored label', () => {
|
|
1160
|
+
expect(mapButton({ text: 'Book now' })?.title).toBe('Book now');
|
|
1161
|
+
});
|
|
1162
|
+
|
|
1163
|
+
// Wrapped lines justify; CSS text-align-last keeps the only/last line centered.
|
|
1164
|
+
test('the label text-align is justify', () => {
|
|
1165
|
+
expect(mapButton({})?.titleStyle.textAlign).toBe('justify');
|
|
1166
|
+
});
|
|
1167
|
+
|
|
1168
|
+
// No label means no button — the maker seeds "Contact Us", so this is a deliberate clear.
|
|
1169
|
+
test.each([[''], [' ']])(
|
|
1170
|
+
'a blank label hides the button (%j)',
|
|
1171
|
+
(text) => {
|
|
1172
|
+
expect(mapButton({ text })).toBeUndefined();
|
|
1173
|
+
},
|
|
1174
|
+
);
|
|
1175
|
+
|
|
1176
|
+
// Button.vue reads the link kind off `role` for non-section content, so the
|
|
1177
|
+
// authored link type has to land on the role/clickActionField pair.
|
|
1178
|
+
test.each([
|
|
1179
|
+
[BUTTON_LINK_TYPE.url, 'general-link', 'url'],
|
|
1180
|
+
[BUTTON_LINK_TYPE.page, 'general-link', 'page'],
|
|
1181
|
+
[BUTTON_LINK_TYPE.mailTo, 'email', 'url'],
|
|
1182
|
+
[BUTTON_LINK_TYPE.tel, 'phone', 'url'],
|
|
1183
|
+
])(
|
|
1184
|
+
'maps the %s link type to role %s / clickActionField %s',
|
|
1185
|
+
(buttonLinkType, role, clickActionField) => {
|
|
1186
|
+
const vm = mapButton({ buttonLinkType });
|
|
1187
|
+
|
|
1188
|
+
expect(vm?.role).toBe(role);
|
|
1189
|
+
expect(vm?.clickActionField).toBe(clickActionField);
|
|
1190
|
+
},
|
|
1191
|
+
);
|
|
1192
|
+
|
|
1193
|
+
test('routes a page link through the picked page id', () => {
|
|
1194
|
+
const vm = mapButton({
|
|
1195
|
+
buttonLinkType: BUTTON_LINK_TYPE.page,
|
|
1196
|
+
buttonPageId: 'page-2',
|
|
1197
|
+
url: undefined,
|
|
1198
|
+
});
|
|
1199
|
+
|
|
1200
|
+
expect(vm?.page).toBe('page-2');
|
|
1201
|
+
expect(vm?.pageSlug).toBe('page-2');
|
|
1202
|
+
});
|
|
1203
|
+
|
|
1204
|
+
test('overrides the theme fill and border with the authored colours', () => {
|
|
1205
|
+
const vm = mapButton({ fillColor: '#123456', borderColor: '#654321' });
|
|
1206
|
+
|
|
1207
|
+
expect(vm?.styles.style.backgroundColor).toBe('#123456');
|
|
1208
|
+
expect(vm?.styles.style.borderColor).toBe('#654321');
|
|
1209
|
+
});
|
|
1210
|
+
|
|
1211
|
+
test('keeps the site button style when no colours are authored', () => {
|
|
1212
|
+
const styles = new PageStyles();
|
|
1213
|
+
const vm = mapToPageHeaderViewModel(
|
|
1214
|
+
new PageHeaderDesktop({
|
|
1215
|
+
headerButton: {
|
|
1216
|
+
...headerButton,
|
|
1217
|
+
fillColor: undefined,
|
|
1218
|
+
borderColor: undefined,
|
|
1219
|
+
},
|
|
1220
|
+
}),
|
|
1221
|
+
styles,
|
|
1222
|
+
[],
|
|
1223
|
+
).headerButtonViewModel;
|
|
1224
|
+
|
|
1225
|
+
expect(vm?.styles.style.backgroundColor).toBe(
|
|
1226
|
+
styles.buttonStyles.style.backgroundColor,
|
|
1227
|
+
);
|
|
1228
|
+
expect(vm?.styles.selectedButtonStyleId).toBe(
|
|
1229
|
+
styles.buttonStyles.selectedButtonStyleId,
|
|
1230
|
+
);
|
|
1231
|
+
});
|
|
1232
|
+
|
|
1233
|
+
test('resolves the button text style against the site text styles', () => {
|
|
1234
|
+
const styles = new PageStyles();
|
|
1235
|
+
|
|
1236
|
+
styles.textStyles.textStyleOptions.buttons.style.fontFamily = 'Lato';
|
|
1237
|
+
|
|
1238
|
+
const vm = mapToPageHeaderViewModel(
|
|
1239
|
+
new PageHeaderDesktop({ headerButton }),
|
|
1240
|
+
styles,
|
|
1241
|
+
[],
|
|
1242
|
+
).headerButtonViewModel;
|
|
1243
|
+
|
|
1244
|
+
expect(vm?.titleStyle?.fontFamily).toBe('Lato');
|
|
1245
|
+
});
|
|
1246
|
+
|
|
1247
|
+
// The maker writes buttonTextStyle only once the author touches the style field,
|
|
1248
|
+
// so an untouched button still has to inherit the site's button text style.
|
|
1249
|
+
test('defaults to the site button text style when none is authored', () => {
|
|
1250
|
+
const styles = new PageStyles();
|
|
1251
|
+
|
|
1252
|
+
styles.textStyles.textStyleOptions.buttons.style.fontFamily = 'Lato';
|
|
1253
|
+
|
|
1254
|
+
const vm = mapToPageHeaderViewModel(
|
|
1255
|
+
new PageHeaderDesktop({
|
|
1256
|
+
headerButton: { ...headerButton, buttonTextStyle: undefined },
|
|
1257
|
+
}),
|
|
1258
|
+
styles,
|
|
1259
|
+
[],
|
|
1260
|
+
).headerButtonViewModel;
|
|
1261
|
+
|
|
1262
|
+
expect(vm?.titleStyle?.textStyleType).toBe('buttons');
|
|
1263
|
+
expect(vm?.titleStyle?.fontFamily).toBe('Lato');
|
|
1264
|
+
});
|
|
1265
|
+
});
|
|
1266
|
+
});
|
|
@@ -44,8 +44,11 @@ import type {
|
|
|
44
44
|
NavRoute,
|
|
45
45
|
NavOrder,
|
|
46
46
|
ColorItem,
|
|
47
|
+
HeaderButton,
|
|
48
|
+
ButtonLinkType,
|
|
49
|
+
ContentType,
|
|
47
50
|
} from '../models';
|
|
48
|
-
import { ButtonStyles, VIEWPORTS } from '../models';
|
|
51
|
+
import { ButtonStyles, TEXT_STYLE_TYPES, VIEWPORTS } from '../models';
|
|
49
52
|
import {
|
|
50
53
|
NavigationMobile,
|
|
51
54
|
NavigationDesktop,
|
|
@@ -1189,6 +1192,71 @@ export const mapToPageStyleViewModel = (
|
|
|
1189
1192
|
};
|
|
1190
1193
|
};
|
|
1191
1194
|
|
|
1195
|
+
// Maps each link kind to the role Button.vue already understands, so the button needs no new code.
|
|
1196
|
+
const HEADER_BUTTON_LINK_ROLES: Record<ButtonLinkType, ContentType> = {
|
|
1197
|
+
[BUTTON_LINK_TYPE.url]: 'general-link',
|
|
1198
|
+
[BUTTON_LINK_TYPE.page]: 'general-link',
|
|
1199
|
+
[BUTTON_LINK_TYPE.mailTo]: 'email',
|
|
1200
|
+
[BUTTON_LINK_TYPE.tel]: 'phone',
|
|
1201
|
+
};
|
|
1202
|
+
|
|
1203
|
+
const mapToHeaderButtonViewModel = (
|
|
1204
|
+
headerButton: HeaderButton | undefined,
|
|
1205
|
+
styles: PageStyles,
|
|
1206
|
+
): ButtonViewModel | undefined => {
|
|
1207
|
+
// No label means no button — hidden in preview and on the published site.
|
|
1208
|
+
if (!headerButton?.isVisible || !headerButton.text?.trim()) {
|
|
1209
|
+
return undefined;
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
const titleStyle: TextStyleDeclaration = headerButton.buttonTextStyle ?? {
|
|
1213
|
+
textStyleType: TEXT_STYLE_TYPES.buttons,
|
|
1214
|
+
};
|
|
1215
|
+
|
|
1216
|
+
const viewModel = mapToButtonViewModel(
|
|
1217
|
+
{
|
|
1218
|
+
id: 'page-header-button',
|
|
1219
|
+
// Old saved data may have no link kind — treat it as a url.
|
|
1220
|
+
type:
|
|
1221
|
+
HEADER_BUTTON_LINK_ROLES[headerButton.buttonLinkType] ??
|
|
1222
|
+
HEADER_BUTTON_LINK_ROLES[BUTTON_LINK_TYPE.url],
|
|
1223
|
+
iconUrl: '',
|
|
1224
|
+
modalImageUrl: '',
|
|
1225
|
+
colorReplacements: [],
|
|
1226
|
+
title: headerButton.text.trim(),
|
|
1227
|
+
titleStyle,
|
|
1228
|
+
url: headerButton.url ?? '',
|
|
1229
|
+
page: headerButton.buttonPageId ?? '',
|
|
1230
|
+
clickActionField:
|
|
1231
|
+
headerButton.buttonLinkType === BUTTON_LINK_TYPE.page ? 'page' : 'url',
|
|
1232
|
+
},
|
|
1233
|
+
styles,
|
|
1234
|
+
{ titleStyle },
|
|
1235
|
+
);
|
|
1236
|
+
|
|
1237
|
+
return {
|
|
1238
|
+
...viewModel,
|
|
1239
|
+
// A wrapped label justifies; the last (or only) line stays centered like a short label.
|
|
1240
|
+
titleStyle: {
|
|
1241
|
+
...viewModel.titleStyle,
|
|
1242
|
+
textAlign: 'justify',
|
|
1243
|
+
textAlignLast: 'center',
|
|
1244
|
+
},
|
|
1245
|
+
styles: {
|
|
1246
|
+
...viewModel.styles,
|
|
1247
|
+
style: {
|
|
1248
|
+
...viewModel.styles.style,
|
|
1249
|
+
...(headerButton.fillColor
|
|
1250
|
+
? { backgroundColor: headerButton.fillColor }
|
|
1251
|
+
: {}),
|
|
1252
|
+
...(headerButton.borderColor
|
|
1253
|
+
? { borderColor: headerButton.borderColor }
|
|
1254
|
+
: {}),
|
|
1255
|
+
},
|
|
1256
|
+
},
|
|
1257
|
+
};
|
|
1258
|
+
};
|
|
1259
|
+
|
|
1192
1260
|
export const mapToPageHeaderViewModel = (
|
|
1193
1261
|
pageHeader: PageHeaderDesktop | PageHeaderMobile,
|
|
1194
1262
|
pageStyles: PageStyles,
|
|
@@ -1283,6 +1351,10 @@ export const mapToPageHeaderViewModel = (
|
|
|
1283
1351
|
}),
|
|
1284
1352
|
style: pageHeader.style,
|
|
1285
1353
|
backgroundStyle: getHeaderBackgroundStyleWithOpacity(pageHeader),
|
|
1354
|
+
headerButtonViewModel: mapToHeaderButtonViewModel(
|
|
1355
|
+
pageHeader.headerButton,
|
|
1356
|
+
pageStyles,
|
|
1357
|
+
),
|
|
1286
1358
|
};
|
|
1287
1359
|
};
|
|
1288
1360
|
|
|
@@ -259,11 +259,14 @@ export interface PageHeaderViewModel extends Omit<
|
|
|
259
259
|
| 'logoTemplateImage'
|
|
260
260
|
| 'logoImageHeight'
|
|
261
261
|
| 'logoImageWidth'
|
|
262
|
+
| 'headerButton'
|
|
262
263
|
| 'backgroundStyle'
|
|
263
264
|
> {
|
|
264
265
|
logo: LogoViewModel;
|
|
265
266
|
pages: PageViewModel[];
|
|
266
267
|
backgroundStyle: BrandPageCssStyle;
|
|
268
|
+
/** Absent unless the header call-to-action button is configured AND visible. */
|
|
269
|
+
headerButtonViewModel?: ButtonViewModel;
|
|
267
270
|
}
|
|
268
271
|
|
|
269
272
|
export interface PageFooterViewModel extends Omit<
|
|
@@ -17,16 +17,6 @@ export default defineComponent({
|
|
|
17
17
|
|
|
18
18
|
return {
|
|
19
19
|
isAdminMaker: computed(() => containerStore.isAdminMaker),
|
|
20
|
-
// BP-5873: the Preview button is behind an AB test, and pre/post purchase are
|
|
21
|
-
// two separate experiments. Pick the one for the current purchase state — a
|
|
22
|
-
// user's value on the other side is independent and must not leak across.
|
|
23
|
-
isLivePreviewEnabled: computed(() => {
|
|
24
|
-
const experiment = containerStore.isPrePurchase
|
|
25
|
-
? containerStore.abTests.bp_5873_live_preview_prepurchase
|
|
26
|
-
: containerStore.abTests.bp_5873_live_preview_postpurchase;
|
|
27
|
-
|
|
28
|
-
return !!experiment?.isVariation;
|
|
29
|
-
}),
|
|
30
20
|
isSaving: computed(() => brandPageMakerStore.isSaving),
|
|
31
21
|
isPublished: computed(() => brandPageMakerStore.isPublished),
|
|
32
22
|
isPublishButtonEnabled: computed(
|
|
@@ -3,31 +3,13 @@ import { createPinia, setActivePinia } from 'pinia';
|
|
|
3
3
|
import { shallowMount } from '@vue/test-utils';
|
|
4
4
|
import ActionBarNavigation from './ActionBarNavigation.vue';
|
|
5
5
|
import { useBrandPageContainerStore } from '@brand-page-maker/stores';
|
|
6
|
-
import { getAbTestResults } from '@brand-page/models/brand-page-context.model';
|
|
7
|
-
|
|
8
|
-
// Must match `CampaignNames.LivePreview*` in bc-net. The two experiments share
|
|
9
|
-
// one set of variation values — they're told apart by campaign key, not value.
|
|
10
|
-
const PREPURCHASE = 'bc-bp-5873-live-preview-prepurchase';
|
|
11
|
-
const POSTPURCHASE = 'bc-bp-5873-live-preview-postpurchase';
|
|
12
|
-
const VARIATION_1 = 'bp-5873-variation-1';
|
|
13
|
-
const CONTROL = 'bp-5873-control';
|
|
14
6
|
|
|
15
7
|
const mountBar = () => shallowMount(ActionBarNavigation);
|
|
16
8
|
|
|
17
9
|
const hasPreviewButton = (wrapper: ReturnType<typeof mountBar>) =>
|
|
18
10
|
wrapper.find('[data-test-preview-button]').exists();
|
|
19
11
|
|
|
20
|
-
|
|
21
|
-
// parser rather than hand-building an AbTestResult — that's the contract under test.
|
|
22
|
-
const setAbTest = (
|
|
23
|
-
store: ReturnType<typeof useBrandPageContainerStore>,
|
|
24
|
-
key: string,
|
|
25
|
-
value: string,
|
|
26
|
-
) => {
|
|
27
|
-
store.abTests = getAbTestResults({ [key]: value } as never);
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
describe('ActionBarNavigation — BP-5873 preview AB test', () => {
|
|
12
|
+
describe('ActionBarNavigation — Preview button', () => {
|
|
31
13
|
let containerStore: ReturnType<typeof useBrandPageContainerStore>;
|
|
32
14
|
|
|
33
15
|
beforeEach(() => {
|
|
@@ -35,39 +17,18 @@ describe('ActionBarNavigation — BP-5873 preview AB test', () => {
|
|
|
35
17
|
containerStore = useBrandPageContainerStore();
|
|
36
18
|
});
|
|
37
19
|
|
|
38
|
-
test(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
containerStore.isPrePurchase = false;
|
|
44
|
-
|
|
45
|
-
setAbTest(containerStore, POSTPURCHASE, VARIATION_1);
|
|
46
|
-
expect(hasPreviewButton(mountBar())).toBe(true);
|
|
47
|
-
|
|
48
|
-
setAbTest(containerStore, POSTPURCHASE, CONTROL);
|
|
49
|
-
expect(hasPreviewButton(mountBar())).toBe(false);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test('pre-purchase: shows Preview on variation-1, hides it on control', () => {
|
|
53
|
-
containerStore.isPrePurchase = true;
|
|
20
|
+
test.each([
|
|
21
|
+
['pre-purchase', true],
|
|
22
|
+
['post-purchase', false],
|
|
23
|
+
])('shows Preview to the customer %s', (_, isPrePurchase) => {
|
|
24
|
+
containerStore.isPrePurchase = isPrePurchase;
|
|
54
25
|
|
|
55
|
-
setAbTest(containerStore, PREPURCHASE, VARIATION_1);
|
|
56
26
|
expect(hasPreviewButton(mountBar())).toBe(true);
|
|
57
|
-
|
|
58
|
-
setAbTest(containerStore, PREPURCHASE, CONTROL);
|
|
59
|
-
expect(hasPreviewButton(mountBar())).toBe(false);
|
|
60
27
|
});
|
|
61
28
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
test('the two experiments never read each other', () => {
|
|
65
|
-
containerStore.isPrePurchase = true;
|
|
66
|
-
setAbTest(containerStore, POSTPURCHASE, VARIATION_1);
|
|
67
|
-
expect(hasPreviewButton(mountBar())).toBe(false);
|
|
29
|
+
test('hides Preview in the admin maker', () => {
|
|
30
|
+
containerStore.isAdminMaker = true;
|
|
68
31
|
|
|
69
|
-
containerStore.isPrePurchase = false;
|
|
70
|
-
setAbTest(containerStore, PREPURCHASE, VARIATION_1);
|
|
71
32
|
expect(hasPreviewButton(mountBar())).toBe(false);
|
|
72
33
|
});
|
|
73
34
|
|
|
@@ -75,8 +36,6 @@ describe('ActionBarNavigation — BP-5873 preview AB test', () => {
|
|
|
75
36
|
// Save drops out. Preview must be a real column like the rest — while it was content
|
|
76
37
|
// width, Publish ate the whole remaining row on a published site.
|
|
77
38
|
test('Preview shares the mobile row equally with the other buttons', () => {
|
|
78
|
-
containerStore.isPrePurchase = false;
|
|
79
|
-
setAbTest(containerStore, POSTPURCHASE, VARIATION_1);
|
|
80
39
|
containerStore.brandPageStatus = 'Published';
|
|
81
40
|
|
|
82
41
|
const wrapper = mountBar();
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
class="tw-flex tw-items-center tw-border-t tw-border-solid tw-border-grayscale-600 tw-h-12 tw-px-2 lg:tw-px-0 lg:tw-h-auto lg:tw-border-none tw-gap-3 lg:tw-gap-5 tw-w-full lg:tw-justify-end tw-box-border"
|
|
5
5
|
>
|
|
6
6
|
<Button
|
|
7
|
-
v-if="!isAdminMaker
|
|
7
|
+
v-if="!isAdminMaker"
|
|
8
8
|
:label="constantsTr('previewDisplayName')"
|
|
9
9
|
variant="no-border"
|
|
10
10
|
:white="true"
|
|
@@ -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
|
|
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;
|
|
@@ -38,14 +38,15 @@ export const textValidator: ContentValidator<BannerSectionContent> = (
|
|
|
38
38
|
};
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
const VALID: ValidateResult = { message: '', isValid: true };
|
|
42
|
+
|
|
43
|
+
// The link rules alone — the header button uses these directly (its link is always required).
|
|
44
|
+
export const buttonLinkValidator = (
|
|
45
|
+
section: Pick<BannerSectionContent, 'buttonLinkType' | 'url'>,
|
|
46
|
+
): ValidateResult => {
|
|
44
47
|
if (
|
|
45
48
|
section.buttonLinkType === BUTTON_LINK_TYPE.url &&
|
|
46
|
-
(!isURL(section.url) || containsAtSymbol(section.url))
|
|
47
|
-
section.buttonText !== false &&
|
|
48
|
-
section.buttonText?.length > 0
|
|
49
|
+
(!isURL(section.url) || containsAtSymbol(section.url))
|
|
49
50
|
) {
|
|
50
51
|
return {
|
|
51
52
|
message: MISSING_BUTTON_URL_MESSAGE(),
|
|
@@ -56,9 +57,7 @@ export const urlValidator: ContentValidator<
|
|
|
56
57
|
if (
|
|
57
58
|
section.buttonLinkType === BUTTON_LINK_TYPE.mailTo &&
|
|
58
59
|
typeof section.url === 'string' &&
|
|
59
|
-
!isEmail(section.url)
|
|
60
|
-
section.buttonText !== false &&
|
|
61
|
-
section.buttonText?.length > 0
|
|
60
|
+
!isEmail(section.url)
|
|
62
61
|
) {
|
|
63
62
|
return {
|
|
64
63
|
message: MISSING_BUTTON_EMAIL_MESSAGE(),
|
|
@@ -66,24 +65,29 @@ export const urlValidator: ContentValidator<
|
|
|
66
65
|
};
|
|
67
66
|
}
|
|
68
67
|
|
|
69
|
-
if (
|
|
70
|
-
section.buttonLinkType === BUTTON_LINK_TYPE.tel &&
|
|
71
|
-
!isTel(section.url) &&
|
|
72
|
-
section.buttonText !== false &&
|
|
73
|
-
section.buttonText?.length > 0
|
|
74
|
-
) {
|
|
68
|
+
if (section.buttonLinkType === BUTTON_LINK_TYPE.tel && !isTel(section.url)) {
|
|
75
69
|
return {
|
|
76
70
|
message: MISSING_BUTTON_TEL_MESSAGE(),
|
|
77
71
|
isValid: false,
|
|
78
72
|
};
|
|
79
73
|
}
|
|
80
74
|
|
|
81
|
-
return
|
|
82
|
-
message: '',
|
|
83
|
-
isValid: true,
|
|
84
|
-
};
|
|
75
|
+
return VALID;
|
|
85
76
|
};
|
|
86
77
|
|
|
78
|
+
type ButtonLinkFields = Pick<
|
|
79
|
+
BannerSectionContent,
|
|
80
|
+
'buttonLinkType' | 'url' | 'buttonText'
|
|
81
|
+
>;
|
|
82
|
+
|
|
83
|
+
// The banner's rule: its button text is optional, and only a button with text needs a link.
|
|
84
|
+
export const urlValidator: ContentValidator<ButtonLinkFields> = (
|
|
85
|
+
section,
|
|
86
|
+
): ValidateResult =>
|
|
87
|
+
section.buttonText !== false && section.buttonText?.length > 0
|
|
88
|
+
? buttonLinkValidator(section)
|
|
89
|
+
: VALID;
|
|
90
|
+
|
|
87
91
|
export const pageValidator: ContentValidator<
|
|
88
92
|
BannerSectionContent | BookingSectionContent
|
|
89
93
|
> = (section): ValidateResult => {
|