@designcrowd/library.brand-page 6.1.15 → 6.1.16
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-a7cNT5AA.js → CreatedOnBrandCrowd-de-de-CmNhDMIf.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-en-us-BpZ89I9T.js → CreatedOnBrandCrowd-en-us-DgplxxWa.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-es-es-Db0Ot-mQ.js → CreatedOnBrandCrowd-es-es-DFdhFOa1.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-fr-Mbd7Hhxh.js → CreatedOnBrandCrowd-fr-ca-DjLCWBLB.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-ca-Mbd7Hhxh.js → CreatedOnBrandCrowd-fr-fr-DjLCWBLB.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-pt-B98aoT6y.js → CreatedOnBrandCrowd-pt-br-CI2XD0OS.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-br-B98aoT6y.js → CreatedOnBrandCrowd-pt-pt-CI2XD0OS.js} +1 -1
- package/lib/src/{CreatedOnDcom-de-de-b_GUYmhX.js → CreatedOnDcom-de-de-CkLkBztx.js} +1 -1
- package/lib/src/{CreatedOnDcom-en-us-BtTIwekM.js → CreatedOnDcom-en-us-frnxBJJv.js} +1 -1
- package/lib/src/{CreatedOnDcom-es-es-WbhHNEg-.js → CreatedOnDcom-es-es-CrqgiERj.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-ca-DnQOAvS5.js → CreatedOnDcom-fr-ca-CPoEcusj.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-fr-D7Q-aBNQ.js → CreatedOnDcom-fr-fr-BqrRH3dV.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-br-DnqkF_ve.js → CreatedOnDcom-pt-br-CpbVi3Sw.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-pt-DNyyHF1o.js → CreatedOnDcom-pt-pt-s0XZ3c7y.js} +1 -1
- package/lib/src/brand-page/models/index.d.ts +4 -1
- package/lib/src/brand-page/models/index.js +4 -1
- package/lib/src/brand-page/utils/view-model.util.js +18 -2
- package/lib/src/brand-page/view-models.d.ts +2 -1
- package/lib/src/brand-page-maker/stores/ai-checkpoint/ai-checkpoint-store.d.ts +18 -0
- package/lib/src/{bundled-translations-BiFAlQdC.js → bundled-translations-B2q5GWTQ.js} +1 -1
- package/lib/src/{bundled-translations.de-DE-DuRWnOP-.js → bundled-translations.de-DE-FhH2H_rd.js} +5 -5
- package/lib/src/{bundled-translations.es-ES-CokHdZtW.js → bundled-translations.es-ES-_ZExxMi7.js} +5 -5
- package/lib/src/{bundled-translations.fr-CA-CJcMmubA.js → bundled-translations.fr-CA-Tg-ODIiC.js} +1 -1
- package/lib/src/{bundled-translations.fr-FR-DYPup8fJ.js → bundled-translations.fr-FR-C0sU51Mz.js} +1 -1
- package/lib/src/{bundled-translations.pt-BR-GSsp1kuV.js → bundled-translations.pt-BR-MYFIhA4u.js} +9 -9
- package/lib/src/{bundled-translations.pt-PT-DbkY8UQ3.js → bundled-translations.pt-PT-DeUBSzCB.js} +9 -9
- package/lib/src/bundles/bundled-translations.de-DE.json +1 -0
- package/lib/src/bundles/bundled-translations.es-ES.json +1 -0
- package/lib/src/bundles/bundled-translations.fr-CA.json +1 -0
- package/lib/src/bundles/bundled-translations.fr-FR.json +1 -0
- package/lib/src/bundles/bundled-translations.json +1 -0
- package/lib/src/bundles/bundled-translations.pt-BR.json +1 -0
- package/lib/src/bundles/bundled-translations.pt-PT.json +1 -0
- package/lib/src/css/library.brand-page-brandCrowd.min.css +1 -1
- package/lib/src/css/library.brand-page-brandPage.min.css +1 -1
- package/lib/src/css/library.brand-page-designCom.min.css +1 -1
- package/lib/src/css/library.brand-page-preview.min.css +1 -1
- package/lib/src/index.mjs +1 -1
- package/lib/src/{lib-33AmrlUy.js → lib-O_eJgJHP.js} +8465 -8415
- package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +2 -0
- package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +5 -0
- package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.js +25 -0
- package/lib/src/utils/minify-brand-page-metadata.d.ts +1 -0
- package/lib/src/utils/minify-brand-page-metadata.js +13 -3
- package/package.json +1 -1
- package/src/brand-page/models/index.ts +9 -2
- package/src/brand-page/utils/__snapshots__/migration.util.test.ts.snap +18 -6
- package/src/brand-page/utils/__snapshots__/migration2.util.test.ts.snap +6 -2
- package/src/brand-page/utils/__snapshots__/model.util.test.ts.snap +12 -4
- package/src/brand-page/utils/view-model.util.test.ts +145 -1
- package/src/brand-page/utils/view-model.util.ts +27 -2
- package/src/brand-page/view-models.ts +2 -0
- package/src/bundles/bundled-translations.de-DE.json +1 -0
- package/src/bundles/bundled-translations.es-ES.json +1 -0
- package/src/bundles/bundled-translations.fr-CA.json +1 -0
- package/src/bundles/bundled-translations.fr-FR.json +1 -0
- package/src/bundles/bundled-translations.json +1 -0
- package/src/bundles/bundled-translations.pt-BR.json +1 -0
- package/src/bundles/bundled-translations.pt-PT.json +1 -0
- package/src/shared/page-settings/components/header-settings-panel/HeaderSettingsPanel.test.ts +111 -0
- package/src/shared/page-settings/components/header-settings-panel/HeaderSettingsPanel.vue +18 -0
- package/src/shared/page-settings/components/page-settings/i18n/page-settings.de-DE.json +1 -0
- package/src/shared/page-settings/components/page-settings/i18n/page-settings.es-ES.json +1 -0
- package/src/shared/page-settings/components/page-settings/i18n/page-settings.fr-CA.json +1 -0
- package/src/shared/page-settings/components/page-settings/i18n/page-settings.fr-FR.json +1 -0
- package/src/shared/page-settings/components/page-settings/i18n/page-settings.json +1 -0
- package/src/shared/page-settings/components/page-settings/i18n/page-settings.pt-BR.json +1 -0
- package/src/shared/page-settings/components/page-settings/i18n/page-settings.pt-PT.json +1 -0
- package/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.ts +33 -0
- package/src/utils/minify-brand-page-metadata.test.ts +70 -1
- package/src/utils/minify-brand-page-metadata.ts +19 -4
- package/lib/src/metadata-builder/builders/reorder-pages-builder.d.ts +0 -13
- package/lib/src/metadata-builder/builders/reorder-pages-builder.js +0 -49
- package/src/metadata-builder/builders/reorder-pages-builder.ts +0 -80
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { createPinia, setActivePinia } from 'pinia';
|
|
3
|
+
import { shallowMount } from '@vue/test-utils';
|
|
4
|
+
import HeaderSettingsPanel from './HeaderSettingsPanel.vue';
|
|
5
|
+
import { useUiPageSettingsStore } from '../../store';
|
|
6
|
+
import { PAGE_SECTIONS } from '../../models';
|
|
7
|
+
import { VIEWPORTS } from '@brand-page';
|
|
8
|
+
|
|
9
|
+
// Coverage for BP-5851: the header background opacity slider.
|
|
10
|
+
describe('HeaderSettingsPanel — background opacity (BP-5851)', () => {
|
|
11
|
+
let pageSettingsStore: ReturnType<typeof useUiPageSettingsStore>;
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
setActivePinia(createPinia());
|
|
15
|
+
pageSettingsStore = useUiPageSettingsStore();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const mountPanel = () =>
|
|
19
|
+
shallowMount(HeaderSettingsPanel, {
|
|
20
|
+
props: {
|
|
21
|
+
isAdminMaker: false,
|
|
22
|
+
isBrandSite: true,
|
|
23
|
+
logoTheme: {},
|
|
24
|
+
sectionName: PAGE_SECTIONS.header,
|
|
25
|
+
styleOptions: [],
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const header = (viewport: 'mobile' | 'desktop' = VIEWPORTS.mobile) =>
|
|
30
|
+
pageSettingsStore.sections[PAGE_SECTIONS.header][viewport];
|
|
31
|
+
|
|
32
|
+
test('defaults to opacity 100 and hides the slider when no background color is set', () => {
|
|
33
|
+
const wrapper = mountPanel();
|
|
34
|
+
|
|
35
|
+
expect(header().backgroundStyle.backgroundColor).toBeFalsy();
|
|
36
|
+
expect((wrapper.vm as any).backgroundOpacity).toBe(100);
|
|
37
|
+
expect((wrapper.vm as any).showBackgroundOpacity).toBe(false);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('shows the slider once a non-transparent background color is set', async () => {
|
|
41
|
+
const wrapper = mountPanel();
|
|
42
|
+
|
|
43
|
+
header().backgroundStyle = { backgroundColor: '#336699' };
|
|
44
|
+
await wrapper.vm.$nextTick();
|
|
45
|
+
|
|
46
|
+
expect((wrapper.vm as any).showBackgroundOpacity).toBe(true);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('an un-constructed header (backgroundStyle carrying no backgroundOpacity) reads as 100', async () => {
|
|
50
|
+
const wrapper = mountPanel();
|
|
51
|
+
|
|
52
|
+
header().backgroundStyle = { backgroundColor: '#336699' };
|
|
53
|
+
await wrapper.vm.$nextTick();
|
|
54
|
+
|
|
55
|
+
expect(header().backgroundStyle.backgroundOpacity).toBeUndefined();
|
|
56
|
+
expect((wrapper.vm as any).backgroundOpacity).toBe(100);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('hides the slider again when the color is switched to transparent', async () => {
|
|
60
|
+
const wrapper = mountPanel();
|
|
61
|
+
|
|
62
|
+
header().backgroundStyle = { backgroundColor: '#336699' };
|
|
63
|
+
await wrapper.vm.$nextTick();
|
|
64
|
+
expect((wrapper.vm as any).showBackgroundOpacity).toBe(true);
|
|
65
|
+
|
|
66
|
+
header().backgroundStyle = { backgroundColor: 'transparent' };
|
|
67
|
+
await wrapper.vm.$nextTick();
|
|
68
|
+
|
|
69
|
+
expect((wrapper.vm as any).showBackgroundOpacity).toBe(false);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('onBackgroundOpacityChange merges the value into backgroundStyle of the active viewport section', () => {
|
|
73
|
+
const wrapper = mountPanel();
|
|
74
|
+
|
|
75
|
+
header().backgroundStyle = {
|
|
76
|
+
backgroundColor: '#336699',
|
|
77
|
+
backgroundOpacity: 100,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
(wrapper.vm as any).onBackgroundOpacityChange(45);
|
|
81
|
+
|
|
82
|
+
expect(header().backgroundStyle.backgroundOpacity).toBe(45);
|
|
83
|
+
// A merge, not a replace — the sibling backgroundColor must survive.
|
|
84
|
+
expect(header().backgroundStyle.backgroundColor).toBe('#336699');
|
|
85
|
+
// The other viewport is untouched — opacity is per-viewport, like backgroundColor.
|
|
86
|
+
expect(header(VIEWPORTS.desktop).backgroundStyle.backgroundOpacity).toBe(
|
|
87
|
+
100,
|
|
88
|
+
);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('onBackgroundOpacityChange coerces a string value from the slider input', () => {
|
|
92
|
+
const wrapper = mountPanel();
|
|
93
|
+
|
|
94
|
+
(wrapper.vm as any).onBackgroundOpacityChange('60');
|
|
95
|
+
|
|
96
|
+
expect(header().backgroundStyle.backgroundOpacity).toBe(60);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('onBackgroundOpacityChange clamps an out-of-range value (mid-typing in the Slider text field)', () => {
|
|
100
|
+
const wrapper = mountPanel();
|
|
101
|
+
|
|
102
|
+
(wrapper.vm as any).onBackgroundOpacityChange('-5');
|
|
103
|
+
expect(header().backgroundStyle.backgroundOpacity).toBe(0);
|
|
104
|
+
|
|
105
|
+
(wrapper.vm as any).onBackgroundOpacityChange(500);
|
|
106
|
+
expect(header().backgroundStyle.backgroundOpacity).toBe(100);
|
|
107
|
+
|
|
108
|
+
(wrapper.vm as any).onBackgroundOpacityChange('not-a-number');
|
|
109
|
+
expect(header().backgroundStyle.backgroundOpacity).toBe(0);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
@@ -95,6 +95,22 @@
|
|
|
95
95
|
:toggle-unique-id="sectionName"
|
|
96
96
|
@update:model-value="onSelectColor"
|
|
97
97
|
/>
|
|
98
|
+
<div
|
|
99
|
+
v-if="showBackgroundOpacity"
|
|
100
|
+
data-test-header-background-opacity
|
|
101
|
+
class="tw-mt-4"
|
|
102
|
+
>
|
|
103
|
+
<span class="header-settings-panel_option-title">
|
|
104
|
+
{{ pageSettingsTr('backgroundOpacity') }}
|
|
105
|
+
</span>
|
|
106
|
+
<Slider
|
|
107
|
+
:model-value="backgroundOpacity"
|
|
108
|
+
:min="0"
|
|
109
|
+
:max="100"
|
|
110
|
+
suffix="%"
|
|
111
|
+
@update:model-value="onBackgroundOpacityChange"
|
|
112
|
+
/>
|
|
113
|
+
</div>
|
|
98
114
|
</CollapsiblePanel>
|
|
99
115
|
|
|
100
116
|
<CollapsiblePanel
|
|
@@ -144,6 +160,7 @@
|
|
|
144
160
|
import {
|
|
145
161
|
CollapsiblePanel,
|
|
146
162
|
HelloBar,
|
|
163
|
+
Slider,
|
|
147
164
|
TabMenu,
|
|
148
165
|
Toggle,
|
|
149
166
|
} from '@designcrowd/fe-shared-lib';
|
|
@@ -163,6 +180,7 @@ export default {
|
|
|
163
180
|
CollapsiblePanel,
|
|
164
181
|
ColorField,
|
|
165
182
|
HelloBar,
|
|
183
|
+
Slider,
|
|
166
184
|
TabMenu,
|
|
167
185
|
Toggle,
|
|
168
186
|
HeaderDisplayStyle,
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
"headerSettings": "Kopfzeilen-Einstellungen",
|
|
4
4
|
"navigationSectionTitle": "Navigation",
|
|
5
5
|
"headerSectionTitle": "Header",
|
|
6
|
+
"backgroundOpacity": "Hintergrunddeckkraft",
|
|
6
7
|
"buttonSectionTitle": "Schaltfläche",
|
|
7
8
|
"footerSettings": "Fußzeilen-Einstellungen",
|
|
8
9
|
"managePages": "Seiten verwalten",
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
"headerSettings": "Configuración del encabezado",
|
|
4
4
|
"navigationSectionTitle": "Navegación",
|
|
5
5
|
"headerSectionTitle": "Encabezado",
|
|
6
|
+
"backgroundOpacity": "Opacidad del fondo",
|
|
6
7
|
"buttonSectionTitle": "Botón",
|
|
7
8
|
"footerSettings": "Configuración del pie de página",
|
|
8
9
|
"managePages": "Gestionar páginas",
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
"headerSettings": "Paramètres d'en-tête",
|
|
4
4
|
"navigationSectionTitle": "Navigation",
|
|
5
5
|
"headerSectionTitle": "En-tête",
|
|
6
|
+
"backgroundOpacity": "Opacité de l'arrière-plan",
|
|
6
7
|
"buttonSectionTitle": "Bouton",
|
|
7
8
|
"footerSettings": "Paramètres de pied de page",
|
|
8
9
|
"managePages": "Gérer les pages",
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
"headerSettings": "Paramètres d'en-tête",
|
|
4
4
|
"navigationSectionTitle": "Navigation",
|
|
5
5
|
"headerSectionTitle": "En-tête",
|
|
6
|
+
"backgroundOpacity": "Opacité de l'arrière-plan",
|
|
6
7
|
"buttonSectionTitle": "Bouton",
|
|
7
8
|
"footerSettings": "Paramètres de pied de page",
|
|
8
9
|
"managePages": "Gérer les pages",
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
"headerSettings": "Configurações do cabeçalho",
|
|
4
4
|
"navigationSectionTitle": "Navegação",
|
|
5
5
|
"headerSectionTitle": "Cabeçalho",
|
|
6
|
+
"backgroundOpacity": "Opacidade do plano de fundo",
|
|
6
7
|
"buttonSectionTitle": "Botão",
|
|
7
8
|
"footerSettings": "Definições de rodapé",
|
|
8
9
|
"managePages": "Gerenciar páginas",
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
"headerSettings": "Definições de cabeçalho",
|
|
4
4
|
"navigationSectionTitle": "Navegação",
|
|
5
5
|
"headerSectionTitle": "Cabeçalho",
|
|
6
|
+
"backgroundOpacity": "Opacidade do fundo",
|
|
6
7
|
"buttonSectionTitle": "Botão",
|
|
7
8
|
"footerSettings": "Definições de rodapé",
|
|
8
9
|
"managePages": "Gerir páginas",
|
|
@@ -24,6 +24,7 @@ import type {
|
|
|
24
24
|
PageFooterMobile,
|
|
25
25
|
LogoImageModel,
|
|
26
26
|
LogoViewModel,
|
|
27
|
+
BackgroundColorStyle,
|
|
27
28
|
} from '@brand-page';
|
|
28
29
|
import {
|
|
29
30
|
LOGO_SLIDER_DESKTOP_MAX,
|
|
@@ -39,6 +40,7 @@ import type {
|
|
|
39
40
|
UpdateItemValuePayload,
|
|
40
41
|
} from '../../../content-settings/models';
|
|
41
42
|
import { UI_DEVICE_MODES, UI_EVENT_SOURCES } from '../../../models';
|
|
43
|
+
import { isTransparentColour } from '@brand-page/utils/color.util';
|
|
42
44
|
|
|
43
45
|
import type { PageSettingsStateSectionStyles } from '../../store';
|
|
44
46
|
import { useUiPageSettingsStore } from '../../store';
|
|
@@ -216,6 +218,19 @@ export default defineComponent({
|
|
|
216
218
|
return this.sections[this.sectionName][this.viewport]
|
|
217
219
|
.backgroundColorEnabled;
|
|
218
220
|
},
|
|
221
|
+
backgroundOpacity(): number {
|
|
222
|
+
// Real default: an un-constructed (JSON.parse'd) header never saw the constructor's default.
|
|
223
|
+
return (
|
|
224
|
+
this.sections[this.sectionName][this.viewport].backgroundStyle
|
|
225
|
+
?.backgroundOpacity ?? 100
|
|
226
|
+
);
|
|
227
|
+
},
|
|
228
|
+
showBackgroundOpacity(): boolean {
|
|
229
|
+
// isTransparentColour('') is false, so an unset color needs its own check.
|
|
230
|
+
return (
|
|
231
|
+
!!this.backgroundColor && !isTransparentColour(this.backgroundColor)
|
|
232
|
+
);
|
|
233
|
+
},
|
|
219
234
|
copyrightPlaceholder(): string {
|
|
220
235
|
return commonTr(`copyrightTextPlaceholder`);
|
|
221
236
|
},
|
|
@@ -544,6 +559,24 @@ export default defineComponent({
|
|
|
544
559
|
});
|
|
545
560
|
},
|
|
546
561
|
|
|
562
|
+
onBackgroundOpacityChange(value: number | string) {
|
|
563
|
+
// Clamped here: the Slider's text field can emit an out-of-range value mid-typing.
|
|
564
|
+
const clamped = Math.min(100, Math.max(0, Number(value) || 0));
|
|
565
|
+
|
|
566
|
+
// A variable, not an inline literal — backgroundOpacity isn't on BrandPageCssStyle.
|
|
567
|
+
const backgroundStyle: BackgroundColorStyle = {
|
|
568
|
+
backgroundOpacity: clamped,
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
this.pageSettingsStore.setPageSettingsSectionValue({
|
|
572
|
+
sectionName: this.sectionName,
|
|
573
|
+
propName: 'backgroundStyle',
|
|
574
|
+
value: backgroundStyle,
|
|
575
|
+
merge: true,
|
|
576
|
+
viewport: this.viewport,
|
|
577
|
+
});
|
|
578
|
+
},
|
|
579
|
+
|
|
547
580
|
onBackgroundEnable(enabled: UpdateItemValuePayload<boolean>) {
|
|
548
581
|
this.pageSettingsStore.setPageSettingsSectionValue({
|
|
549
582
|
sectionName: this.sectionName,
|
|
@@ -1,7 +1,76 @@
|
|
|
1
1
|
import { describe, test, expect } from 'vitest';
|
|
2
|
-
import
|
|
2
|
+
import { BRAND_SITE } from '@brand-page-maker/components/brand-page-maker/__fixtures__';
|
|
3
|
+
import { BrandPageModel, PageHeaderDesktop } from '@brand-page';
|
|
3
4
|
import { minifyBrandPageMetadata } from './minify-brand-page-metadata';
|
|
4
5
|
|
|
6
|
+
// Coverage for BP-5851: the AI website builder reads the minified metadata to
|
|
7
|
+
// understand the current site, so backgroundOpacity must be visible there too
|
|
8
|
+
// (read-only — see docs/plans for BP-5851) whenever the header isn't fully opaque.
|
|
9
|
+
describe('minifyBrandPageMetadata — header backgroundOpacity (BP-5851)', () => {
|
|
10
|
+
const buildModel = (pageHeaderDesktop: PageHeaderDesktop) =>
|
|
11
|
+
new BrandPageModel({
|
|
12
|
+
...BRAND_SITE,
|
|
13
|
+
pages: [],
|
|
14
|
+
pageHeaderDesktop,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('opacity below 100 is included in the minified header backgroundStyle', () => {
|
|
18
|
+
const model = buildModel(
|
|
19
|
+
new PageHeaderDesktop({
|
|
20
|
+
backgroundStyle: { backgroundColor: '#112233', backgroundOpacity: 65 },
|
|
21
|
+
}),
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const minified = minifyBrandPageMetadata(model);
|
|
25
|
+
|
|
26
|
+
expect(minified.pageHeaderDesktop?.backgroundStyle).toEqual({
|
|
27
|
+
backgroundColor: '#112233',
|
|
28
|
+
backgroundOpacity: 65,
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('opacity 100 (default/fully opaque) is omitted to keep the payload small', () => {
|
|
33
|
+
const model = buildModel(
|
|
34
|
+
new PageHeaderDesktop({
|
|
35
|
+
backgroundStyle: { backgroundColor: '#112233', backgroundOpacity: 100 },
|
|
36
|
+
}),
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const minified = minifyBrandPageMetadata(model);
|
|
40
|
+
|
|
41
|
+
expect(minified.pageHeaderDesktop?.backgroundStyle).toEqual({
|
|
42
|
+
backgroundColor: '#112233',
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('un-constructed legacy header with backgroundOpacity absent omits it (defaults to opaque)', () => {
|
|
47
|
+
// Forced after buildModel, since BrandPageModel's constructor would re-inject the default.
|
|
48
|
+
const model = buildModel(
|
|
49
|
+
new PageHeaderDesktop({
|
|
50
|
+
backgroundStyle: { backgroundColor: '#112233' },
|
|
51
|
+
}),
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
delete model.pageHeaderDesktop.backgroundStyle.backgroundOpacity;
|
|
55
|
+
|
|
56
|
+
const minified = minifyBrandPageMetadata(model);
|
|
57
|
+
|
|
58
|
+
expect(minified.pageHeaderDesktop?.backgroundStyle).toEqual({
|
|
59
|
+
backgroundColor: '#112233',
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('header with no backgroundColor has no minified backgroundStyle at all', () => {
|
|
64
|
+
const model = buildModel(
|
|
65
|
+
new PageHeaderDesktop({ backgroundStyle: { backgroundOpacity: 40 } }),
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const minified = minifyBrandPageMetadata(model);
|
|
69
|
+
|
|
70
|
+
expect(minified.pageHeaderDesktop?.backgroundStyle).toBeUndefined();
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
5
74
|
/**
|
|
6
75
|
* BP-5886 — the outbound half of the AI colour bridge (review blocker B8).
|
|
7
76
|
*
|
|
@@ -43,7 +43,7 @@ type MinifiedHeader = {
|
|
|
43
43
|
logoImageHeight?: string;
|
|
44
44
|
logoImageWidth?: string;
|
|
45
45
|
logoAssetKey?: string;
|
|
46
|
-
backgroundStyle?: { backgroundColor?: string };
|
|
46
|
+
backgroundStyle?: { backgroundColor?: string; backgroundOpacity?: number };
|
|
47
47
|
navigation?: {
|
|
48
48
|
displayType?: string;
|
|
49
49
|
defaultButtonStyle?: { color?: string; fontFamily?: string };
|
|
@@ -552,6 +552,23 @@ const minifyFooter = (
|
|
|
552
552
|
};
|
|
553
553
|
};
|
|
554
554
|
|
|
555
|
+
// Fully opaque (100) is the default, so it's omitted to keep the AI payload small.
|
|
556
|
+
const minifyHeaderBackgroundStyle = (
|
|
557
|
+
backgroundStyle: AbstractPageHeaderStyles['backgroundStyle'] | undefined,
|
|
558
|
+
): MinifiedHeader['backgroundStyle'] => {
|
|
559
|
+
const backgroundColor = backgroundStyle?.backgroundColor;
|
|
560
|
+
|
|
561
|
+
if (!backgroundColor) return undefined;
|
|
562
|
+
|
|
563
|
+
const backgroundOpacity = backgroundStyle.backgroundOpacity;
|
|
564
|
+
|
|
565
|
+
return {
|
|
566
|
+
backgroundColor,
|
|
567
|
+
...(backgroundOpacity !== undefined &&
|
|
568
|
+
backgroundOpacity < 100 && { backgroundOpacity }),
|
|
569
|
+
};
|
|
570
|
+
};
|
|
571
|
+
|
|
555
572
|
const minifyHeader = (
|
|
556
573
|
header: AbstractPageHeaderStyles | undefined | null,
|
|
557
574
|
logoAssetKey?: string,
|
|
@@ -566,9 +583,7 @@ const minifyHeader = (
|
|
|
566
583
|
logoImageHeight: header.logoImageHeight || undefined,
|
|
567
584
|
logoImageWidth: header.logoImageWidth || undefined,
|
|
568
585
|
...(logoAssetKey && { logoAssetKey }),
|
|
569
|
-
backgroundStyle: header.backgroundStyle
|
|
570
|
-
? { backgroundColor: header.backgroundStyle.backgroundColor as string }
|
|
571
|
-
: undefined,
|
|
586
|
+
backgroundStyle: minifyHeaderBackgroundStyle(header.backgroundStyle),
|
|
572
587
|
navigation: nav
|
|
573
588
|
? {
|
|
574
589
|
displayType: nav.displayType || undefined,
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { BrandPageModel } from '../../brand-page/models';
|
|
2
|
-
import { BaseMetadataBuilder, type BuilderResult, type MetadataBuilderRequest } from '../models';
|
|
3
|
-
import type { Logger } from '../logger';
|
|
4
|
-
export interface ReorderPagesPayload {
|
|
5
|
-
pageOrder: string[];
|
|
6
|
-
}
|
|
7
|
-
export declare class ReorderPagesBuilder extends BaseMetadataBuilder<ReorderPagesPayload> {
|
|
8
|
-
constructor(request: MetadataBuilderRequest<ReorderPagesPayload>, logger?: Logger);
|
|
9
|
-
validateAsync(): Promise<{
|
|
10
|
-
errors: string[];
|
|
11
|
-
}>;
|
|
12
|
-
buildAsync(model: BrandPageModel): Promise<BuilderResult>;
|
|
13
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { BaseMetadataBuilder, } from '../models';
|
|
2
|
-
export class ReorderPagesBuilder extends BaseMetadataBuilder {
|
|
3
|
-
constructor(request, logger) {
|
|
4
|
-
super(request, logger);
|
|
5
|
-
}
|
|
6
|
-
async validateAsync() {
|
|
7
|
-
const errors = [];
|
|
8
|
-
const payload = this.request.payload;
|
|
9
|
-
if (!payload?.pageOrder?.length) {
|
|
10
|
-
errors.push('page_order is required for reorder_pages');
|
|
11
|
-
}
|
|
12
|
-
return { errors };
|
|
13
|
-
}
|
|
14
|
-
async buildAsync(model) {
|
|
15
|
-
this.log('Build called');
|
|
16
|
-
const validation = await this.validateAsync();
|
|
17
|
-
if (validation.errors.length) {
|
|
18
|
-
return { request: this.request, model, errors: validation.errors };
|
|
19
|
-
}
|
|
20
|
-
const { pageOrder } = this.request.payload;
|
|
21
|
-
// Build pageId → routeId lookup from nav.routes
|
|
22
|
-
const pageIdToRouteId = new Map();
|
|
23
|
-
for (const route of model.nav.routes) {
|
|
24
|
-
pageIdToRouteId.set(route.pageId, route.id);
|
|
25
|
-
}
|
|
26
|
-
// Build new nav.orders from the flat page_order (array of pageIds)
|
|
27
|
-
const newOrders = [];
|
|
28
|
-
const seenRouteIds = new Set();
|
|
29
|
-
for (const pageId of pageOrder) {
|
|
30
|
-
const routeId = pageIdToRouteId.get(pageId);
|
|
31
|
-
if (!routeId)
|
|
32
|
-
continue;
|
|
33
|
-
newOrders.push({ routeId, subRoutes: [] });
|
|
34
|
-
seenRouteIds.add(routeId);
|
|
35
|
-
}
|
|
36
|
-
// Safety: append any routes not mentioned (don't lose pages)
|
|
37
|
-
for (const route of model.nav.routes) {
|
|
38
|
-
const routeId = pageIdToRouteId.get(route.pageId);
|
|
39
|
-
if (routeId && !seenRouteIds.has(routeId)) {
|
|
40
|
-
newOrders.push({ routeId, subRoutes: [] });
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
model.nav.orders = newOrders;
|
|
44
|
-
this.log({
|
|
45
|
-
message: `Reordered pages: ${pageOrder.join(', ')}`,
|
|
46
|
-
});
|
|
47
|
-
return { request: this.request, model };
|
|
48
|
-
}
|
|
49
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import type { BrandPageModel, NavOrder } from '../../brand-page/models';
|
|
2
|
-
import {
|
|
3
|
-
BaseMetadataBuilder,
|
|
4
|
-
type BuilderResult,
|
|
5
|
-
type MetadataBuilderRequest,
|
|
6
|
-
} from '../models';
|
|
7
|
-
import type { Logger } from '../logger';
|
|
8
|
-
|
|
9
|
-
export interface ReorderPagesPayload {
|
|
10
|
-
pageOrder: string[];
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export class ReorderPagesBuilder extends BaseMetadataBuilder<ReorderPagesPayload> {
|
|
14
|
-
constructor(
|
|
15
|
-
request: MetadataBuilderRequest<ReorderPagesPayload>,
|
|
16
|
-
logger?: Logger,
|
|
17
|
-
) {
|
|
18
|
-
super(request, logger);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
async validateAsync(): Promise<{ errors: string[] }> {
|
|
22
|
-
const errors: string[] = [];
|
|
23
|
-
const payload = this.request.payload;
|
|
24
|
-
|
|
25
|
-
if (!payload?.pageOrder?.length) {
|
|
26
|
-
errors.push('page_order is required for reorder_pages');
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return { errors };
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async buildAsync(model: BrandPageModel): Promise<BuilderResult> {
|
|
33
|
-
this.log('Build called');
|
|
34
|
-
|
|
35
|
-
const validation = await this.validateAsync();
|
|
36
|
-
|
|
37
|
-
if (validation.errors.length) {
|
|
38
|
-
return { request: this.request, model, errors: validation.errors };
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const { pageOrder } = this.request.payload!;
|
|
42
|
-
|
|
43
|
-
// Build pageId → routeId lookup from nav.routes
|
|
44
|
-
const pageIdToRouteId = new Map<string, string>();
|
|
45
|
-
|
|
46
|
-
for (const route of model.nav.routes) {
|
|
47
|
-
pageIdToRouteId.set(route.pageId, route.id);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Build new nav.orders from the flat page_order (array of pageIds)
|
|
51
|
-
const newOrders: NavOrder[] = [];
|
|
52
|
-
const seenRouteIds = new Set<string>();
|
|
53
|
-
|
|
54
|
-
for (const pageId of pageOrder) {
|
|
55
|
-
const routeId = pageIdToRouteId.get(pageId);
|
|
56
|
-
|
|
57
|
-
if (!routeId) continue;
|
|
58
|
-
|
|
59
|
-
newOrders.push({ routeId, subRoutes: [] });
|
|
60
|
-
seenRouteIds.add(routeId);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Safety: append any routes not mentioned (don't lose pages)
|
|
64
|
-
for (const route of model.nav.routes) {
|
|
65
|
-
const routeId = pageIdToRouteId.get(route.pageId);
|
|
66
|
-
|
|
67
|
-
if (routeId && !seenRouteIds.has(routeId)) {
|
|
68
|
-
newOrders.push({ routeId, subRoutes: [] });
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
model.nav.orders = newOrders;
|
|
73
|
-
|
|
74
|
-
this.log({
|
|
75
|
-
message: `Reordered pages: ${pageOrder.join(', ')}`,
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
return { request: this.request, model };
|
|
79
|
-
}
|
|
80
|
-
}
|