@designcrowd/library.brand-page 5.18.7 → 5.18.8-ast-padding-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-CZmqvsOT.js → CreatedOnBrandCrowd-de-de-4K_VkRLQ.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-en-us-5nTR1eLT.js → CreatedOnBrandCrowd-en-us-n6Fd47e_.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-es-es-xLvoayrw.js → CreatedOnBrandCrowd-es-es-BcCANYX_.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-fr-CN5n8X8v.js → CreatedOnBrandCrowd-fr-ca-qusGdhEQ.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-ca-CN5n8X8v.js → CreatedOnBrandCrowd-fr-fr-qusGdhEQ.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-pt-CdkYC12E.js → CreatedOnBrandCrowd-pt-br-C9IBrRc_.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-br-CdkYC12E.js → CreatedOnBrandCrowd-pt-pt-C9IBrRc_.js} +1 -1
- package/lib/src/{CreatedOnDcom-de-de-B8rV2rD7.js → CreatedOnDcom-de-de-BY13DaXj.js} +1 -1
- package/lib/src/{CreatedOnDcom-en-us-C4WaRIpF.js → CreatedOnDcom-en-us-BPc5Nacx.js} +1 -1
- package/lib/src/{CreatedOnDcom-es-es-BPHPey0b.js → CreatedOnDcom-es-es-KrsweAGY.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-ca-xrMr4xlv.js → CreatedOnDcom-fr-ca-NaPJNFZH.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-fr-BEozL-o2.js → CreatedOnDcom-fr-fr-DjZKRox_.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-br-CoN-ZhjG.js → CreatedOnDcom-pt-br-xGSMGQZa.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-pt-9vfUJ3ks.js → CreatedOnDcom-pt-pt-B7CcpJ4c.js} +1 -1
- package/lib/src/brand-page/components/contact-form/ContactForm.mixin.d.ts +4 -0
- package/lib/src/brand-page/components/contact-form/ContactForm.mixin.js +8 -4
- package/lib/src/brand-page/components/image-gallery/ImageGallery.mixin.d.ts +4 -0
- package/lib/src/brand-page/components/image-gallery/ImageGallery.mixin.js +4 -0
- package/lib/src/brand-page/components/image-gallery/masonry/MasonryGallery.mixin.d.ts +1 -1
- package/lib/src/brand-page/components/mailing-list/MailingList.mixin.d.ts +4 -0
- package/lib/src/brand-page/components/mailing-list/MailingList.mixin.js +4 -1
- package/lib/src/brand-page/components/media/Media.mixin.d.ts +2 -2
- package/lib/src/brand-page/components/payment/request-payment/RequestPayment.mixin.d.ts +4 -0
- package/lib/src/brand-page/components/payment/request-payment/RequestPayment.mixin.js +4 -0
- package/lib/src/brand-page/components/payment/support-me/SupportMe.mixin.d.ts +4 -0
- package/lib/src/brand-page/components/payment/support-me/SupportMe.mixin.js +4 -1
- package/lib/src/brand-page/components/testimonial-section/TestimonialSection.mixin.d.ts +4 -0
- package/lib/src/brand-page/components/testimonial-section/TestimonialSection.mixin.js +4 -1
- package/lib/src/brand-page/components/video/Video.mixin.d.ts +4 -0
- package/lib/src/brand-page/components/video/Video.mixin.js +4 -0
- package/lib/src/brand-page/constants/misc.constants.d.ts +35 -0
- package/lib/src/brand-page/constants/misc.constants.js +51 -0
- package/lib/src/brand-page/models/content.model.d.ts +2 -0
- package/lib/src/brand-page/utils/content-padding.util.d.ts +13 -0
- package/lib/src/brand-page/utils/content-padding.util.js +10 -0
- package/lib/src/brand-page/utils/index.d.ts +1 -0
- package/lib/src/brand-page/utils/index.js +1 -0
- package/lib/src/brand-page/utils/migration/migration-17.d.ts +2 -0
- package/lib/src/brand-page/utils/migration/migration-17.js +54 -0
- package/lib/src/brand-page/utils/migration.util.js +10 -0
- package/lib/src/brand-page-maker/components/admin-logo-settings/AdminLogoSettings.mixin.d.ts +2 -0
- package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +12 -0
- package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.d.ts +14 -0
- package/lib/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.js +16 -3
- package/lib/src/brand-page-maker/constants/config-settings-constant.js +39 -0
- package/lib/src/brand-page-maker/convertors/about-display-style.convertor.js +9 -1
- package/lib/src/brand-page-maker/convertors/banner-align-display-style.convertor.js +7 -1
- package/lib/src/brand-page-maker/convertors/contact-layout-style.convertor.js +6 -1
- package/lib/src/brand-page-maker/models/content-settings.js +38 -2
- package/lib/src/brand-page-maker/stores/ai-checkpoint/ai-checkpoint-store.d.ts +12 -0
- package/lib/src/{bundled-translations-D1q3rSEz.js → bundled-translations-BUyt4scr.js} +9 -9
- package/lib/src/{bundled-translations.de-DE-BxqLLwbh.js → bundled-translations.de-DE-DEw_OVai.js} +1 -1
- package/lib/src/{bundled-translations.es-ES-uh249NlJ.js → bundled-translations.es-ES-CFKWmkgY.js} +17 -17
- package/lib/src/{bundled-translations.fr-CA-BEQmv2fX.js → bundled-translations.fr-CA-DCl5LJS4.js} +1 -1
- package/lib/src/{bundled-translations.fr-FR-BnNuCojH.js → bundled-translations.fr-FR-B5BjgllA.js} +1 -1
- package/lib/src/{bundled-translations.pt-BR-CJUOxVlq.js → bundled-translations.pt-BR-5PWpMPuM.js} +1 -1
- package/lib/src/{bundled-translations.pt-PT-Ds9cMEqK.js → bundled-translations.pt-PT-B9_wN0HR.js} +9 -9
- package/lib/src/bundles/bundled-translations.de-DE.json +2 -0
- package/lib/src/bundles/bundled-translations.es-ES.json +2 -0
- package/lib/src/bundles/bundled-translations.fr-CA.json +2 -0
- package/lib/src/bundles/bundled-translations.fr-FR.json +2 -0
- package/lib/src/bundles/bundled-translations.json +2 -0
- package/lib/src/bundles/bundled-translations.pt-BR.json +2 -0
- package/lib/src/bundles/bundled-translations.pt-PT.json +2 -0
- package/lib/src/composables/useSnapshot.js +233 -21
- package/lib/src/css/library.brand-page-brandCrowd.min.css +147 -161
- package/lib/src/css/library.brand-page-brandPage.min.css +147 -161
- package/lib/src/css/library.brand-page-designCom.min.css +147 -161
- package/lib/src/css/library.brand-page-preview.min.css +147 -161
- package/lib/src/index.mjs +1 -1
- package/lib/src/{lib-xHNclG2R.js → lib-ClPLZIYL.js} +21178 -20848
- package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.d.ts +14 -0
- package/lib/src/shared/content-settings/components/content-settings/ReplaceImageSettingsForContent.mixin.d.ts +14 -0
- package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItem.mixin.d.ts +12 -0
- package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.d.ts +12 -0
- package/lib/src/shared/content-settings/components/focused-content-item-settings/FocusedContentItemSettings.mixin.d.ts +12 -0
- package/lib/src/shared/content-settings/components/slider-field/SliderField.mixin.js +9 -0
- package/lib/src/shared/content-settings/store/content-settings-store.d.ts +12 -0
- package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +16 -0
- package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +14 -0
- package/lib/src/utils/minify-brand-page-metadata.js +11 -1
- package/package.json +1 -1
- package/src/brand-page/components/about-section/AboutSection.vue +10 -4
- package/src/brand-page/components/banner-section/BannerSection.vue +11 -4
- package/src/brand-page/components/booking-section/BookingSection.vue +11 -3
- package/src/brand-page/components/contact-form/ContactForm.mixin.ts +12 -6
- package/src/brand-page/components/contact-form/ContactForm.vue +105 -104
- package/src/brand-page/components/image-gallery/ImageGallery.mixin.ts +5 -0
- package/src/brand-page/components/image-gallery/ImageGallery.vue +1 -1
- package/src/brand-page/components/mailing-list/MailingList.mixin.ts +8 -1
- package/src/brand-page/components/mailing-list/MailingList.vue +1 -1
- package/src/brand-page/components/map/Map.vue +5 -1
- package/src/brand-page/components/payment/request-payment/RequestPayment.mixin.ts +5 -0
- package/src/brand-page/components/payment/request-payment/RequestPayment.vue +1 -0
- package/src/brand-page/components/payment/support-me/SupportMe.mixin.ts +8 -1
- package/src/brand-page/components/payment/support-me/SupportMe.vue +1 -0
- package/src/brand-page/components/shop/Shop.vue +7 -2
- package/src/brand-page/components/social-links/SocialLinks.vue +7 -3
- package/src/brand-page/components/testimonial-section/TestimonialSection.mixin.ts +7 -1
- package/src/brand-page/components/testimonial-section/TestimonialSection.vue +4 -6
- package/src/brand-page/components/text/Text.vue +12 -5
- package/src/brand-page/components/text-media-section/TextMediaSection.vue +11 -3
- package/src/brand-page/components/video/Video.mixin.ts +5 -0
- package/src/brand-page/components/video/Video.vue +1 -1
- package/src/brand-page/constants/misc.constants.ts +58 -0
- package/src/brand-page/models/content.model.ts +2 -0
- package/src/brand-page/utils/__snapshots__/migration.util.test.ts.snap +9 -3
- package/src/brand-page/utils/content-padding.util.test.ts +29 -0
- package/src/brand-page/utils/content-padding.util.ts +13 -0
- package/src/brand-page/utils/index.ts +1 -0
- package/src/brand-page/utils/migration/migration-17.ts +72 -0
- package/src/brand-page/utils/migration.util.test.ts +252 -0
- package/src/brand-page/utils/migration.util.ts +11 -0
- package/src/brand-page-maker/components/ai-website-builder/AiWebsiteBuilder.vue +3 -4
- package/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.vue +8 -1
- package/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.ts +19 -4
- package/src/brand-page-maker/constants/config-settings-constant.ts +45 -0
- package/src/brand-page-maker/convertors/about-display-style.convertor.ts +10 -0
- package/src/brand-page-maker/convertors/banner-align-display-style.convertor.ts +8 -0
- package/src/brand-page-maker/convertors/contact-layout-style.convertor.test.ts +50 -0
- package/src/brand-page-maker/convertors/contact-layout-style.convertor.ts +8 -0
- package/src/brand-page-maker/models/content-settings.ts +41 -2
- package/src/bundles/bundled-translations.de-DE.json +2 -0
- package/src/bundles/bundled-translations.es-ES.json +2 -0
- package/src/bundles/bundled-translations.fr-CA.json +2 -0
- package/src/bundles/bundled-translations.fr-FR.json +2 -0
- package/src/bundles/bundled-translations.json +2 -0
- package/src/bundles/bundled-translations.pt-BR.json +2 -0
- package/src/bundles/bundled-translations.pt-PT.json +2 -0
- package/src/composables/useSnapshot.ts +316 -31
- package/src/shared/content-settings/components/content-settings/i18n/content-settings.de-DE.json +2 -0
- package/src/shared/content-settings/components/content-settings/i18n/content-settings.es-ES.json +2 -0
- package/src/shared/content-settings/components/content-settings/i18n/content-settings.fr-CA.json +2 -0
- package/src/shared/content-settings/components/content-settings/i18n/content-settings.fr-FR.json +2 -0
- package/src/shared/content-settings/components/content-settings/i18n/content-settings.json +2 -0
- package/src/shared/content-settings/components/content-settings/i18n/content-settings.pt-BR.json +2 -0
- package/src/shared/content-settings/components/content-settings/i18n/content-settings.pt-PT.json +2 -0
- package/src/shared/content-settings/components/content-settings-item/ContentSettingsItem.vue +1 -1
- package/src/shared/content-settings/components/focused-content-item-settings/FocusedContentItemSettings.vue +2 -2
- package/src/shared/content-settings/components/slider-field/SliderField.mixin.ts +9 -0
- package/src/utils/minify-brand-page-metadata.ts +11 -1
- 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
|
@@ -5,6 +5,68 @@ const HTML2CANVAS_CDN_URL =
|
|
|
5
5
|
|
|
6
6
|
let html2canvasLoadPromise: Promise<void> | null = null;
|
|
7
7
|
|
|
8
|
+
// Capture is sized to the rendered content (scrollHeight), not the clipped
|
|
9
|
+
// viewport box. An empty page yields a short capture; a long page captures its
|
|
10
|
+
// full height up to the cap.
|
|
11
|
+
const MIN_SNAPSHOT_HEIGHT = 200;
|
|
12
|
+
// The snapshot is consumed by the AI pipeline's OpenAI vision model, which
|
|
13
|
+
// normalises the SHORT side to ~768px and tiles the long side — so token cost
|
|
14
|
+
// is driven by page HEIGHT, not absolute resolution. This cap bounds that cost
|
|
15
|
+
// AND, more importantly, the html2canvas render work (CPU/memory) — taller pages
|
|
16
|
+
// are truncated. The render is the dominant cost on a capture, so we keep the
|
|
17
|
+
// cap conservative, and lower still on mobile where memory/CPU are tighter.
|
|
18
|
+
const MAX_SNAPSHOT_HEIGHT_DESKTOP = 4096;
|
|
19
|
+
const MAX_SNAPSHOT_HEIGHT_MOBILE = 2560;
|
|
20
|
+
// OpenAI's vision short-side target. Rendering at this width avoids the
|
|
21
|
+
// down-then-up resample blur you'd get from scaling smaller (OpenAI upscales
|
|
22
|
+
// the short side back to 768 regardless) while keeping the payload tight.
|
|
23
|
+
const SNAPSHOT_TARGET_WIDTH = 768;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Extracts the YouTube video id from an embed URL, or null for non-YouTube
|
|
27
|
+
* iframes. Handles youtube.com/embed/<id> and youtube-nocookie.com variants.
|
|
28
|
+
*/
|
|
29
|
+
function getYouTubeId(src: string): string | null {
|
|
30
|
+
const match = src.match(
|
|
31
|
+
/(?:youtube(?:-nocookie)?\.com\/embed\/)([A-Za-z0-9_-]{6,})/,
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
return match ? match[1] : null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Fetches a YouTube thumbnail and returns it as a data URI, or null on failure.
|
|
39
|
+
* Inlining as a data URI avoids cross-origin canvas tainting in html2canvas.
|
|
40
|
+
* Tries maxres first, falls back to hq (maxres 404s on some videos).
|
|
41
|
+
*/
|
|
42
|
+
async function fetchYouTubeThumbnail(id: string): Promise<string | null> {
|
|
43
|
+
for (const quality of ['maxresdefault', 'hqdefault']) {
|
|
44
|
+
try {
|
|
45
|
+
const response = await fetch(
|
|
46
|
+
`https://img.youtube.com/vi/${id}/${quality}.jpg`,
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
if (!response.ok) continue;
|
|
50
|
+
|
|
51
|
+
const blob = await response.blob();
|
|
52
|
+
|
|
53
|
+
// YouTube returns a tiny placeholder for missing qualities; skip those.
|
|
54
|
+
if (blob.size < 2000) continue;
|
|
55
|
+
|
|
56
|
+
return await new Promise<string>((resolve) => {
|
|
57
|
+
const reader = new FileReader();
|
|
58
|
+
|
|
59
|
+
reader.onload = () => resolve(reader.result as string);
|
|
60
|
+
reader.readAsDataURL(blob);
|
|
61
|
+
});
|
|
62
|
+
} catch {
|
|
63
|
+
/* try next quality */
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
8
70
|
/**
|
|
9
71
|
* Dynamically loads html2canvas from CDN if not already loaded
|
|
10
72
|
*/
|
|
@@ -67,22 +129,74 @@ export function useSnapshot() {
|
|
|
67
129
|
throw new Error('html2canvas failed to load');
|
|
68
130
|
}
|
|
69
131
|
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
132
|
+
// Wait for fonts and in-flight images to finish before capturing —
|
|
133
|
+
// otherwise html2canvas snapshots a half-rendered page (missing images,
|
|
134
|
+
// fallback fonts) and the measured content height is wrong.
|
|
135
|
+
await document.fonts?.ready;
|
|
136
|
+
await Promise.all(
|
|
137
|
+
Array.from(element.querySelectorAll('img'))
|
|
138
|
+
.filter((img) => !img.complete)
|
|
139
|
+
.map(
|
|
140
|
+
(img) =>
|
|
141
|
+
new Promise<void>((resolve) => {
|
|
142
|
+
img.onload = img.onerror = () => resolve();
|
|
143
|
+
}),
|
|
144
|
+
),
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
// The maker renders the full website inside `.c-preview-container`
|
|
148
|
+
// (position: fixed + transform: scale). The page background — solid
|
|
149
|
+
// colour, gradient, or image — lives on `.brand-page-root-background`,
|
|
150
|
+
// which sits OUTSIDE the scrolling content. Capturing the content alone
|
|
151
|
+
// drops that background, so transparent sections fall back to white and
|
|
152
|
+
// their (often light) text becomes invisible. Capture the whole preview
|
|
153
|
+
// (`[data-test-tag="editor-preview"]`) so the background layer is
|
|
154
|
+
// included, and size the canvas to the real page length, which only the
|
|
155
|
+
// inner scroll container `.brand-page__inner` reports via scrollHeight.
|
|
156
|
+
const target =
|
|
157
|
+
element.querySelector<HTMLElement>(
|
|
158
|
+
'[data-test-tag="editor-preview"]',
|
|
159
|
+
) ?? element;
|
|
160
|
+
const scrollEl =
|
|
161
|
+
element.querySelector<HTMLElement>('.brand-page__inner') ?? target;
|
|
162
|
+
|
|
163
|
+
// Keep the render light on mobile (tighter CPU/memory) by truncating
|
|
164
|
+
// taller there than on desktop.
|
|
165
|
+
const isMobileViewport =
|
|
166
|
+
typeof window !== 'undefined' &&
|
|
167
|
+
window.matchMedia?.('(max-width: 768px)').matches;
|
|
168
|
+
const maxHeight = isMobileViewport
|
|
169
|
+
? MAX_SNAPSHOT_HEIGHT_MOBILE
|
|
170
|
+
: MAX_SNAPSHOT_HEIGHT_DESKTOP;
|
|
171
|
+
|
|
172
|
+
const captureHeight = Math.min(
|
|
173
|
+
Math.max(scrollEl.scrollHeight, MIN_SNAPSHOT_HEIGHT),
|
|
174
|
+
maxHeight,
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
// Render at OpenAI's short-side target width (never upscaling). Token cost
|
|
178
|
+
// is set by height/aspect ratio, not by scaling down the width, so we keep
|
|
179
|
+
// the width crisp and let MAX_SNAPSHOT_HEIGHT bound the cost.
|
|
180
|
+
const captureWidth = target.clientWidth || target.offsetWidth || 1;
|
|
181
|
+
const scale = Math.min(1, SNAPSHOT_TARGET_WIDTH / captureWidth);
|
|
182
|
+
|
|
183
|
+
// Pre-fetch blob-backed SVGs from content shape <object> elements so they
|
|
184
|
+
// can be rendered as <img> in the clone (html2canvas can't reach into the
|
|
185
|
+
// isolated DOM inside <object>). Background-shape blob <img>s are NOT
|
|
186
|
+
// converted: html2canvas renders them no differently with or without the
|
|
187
|
+
// conversion, so it'd just be wasted fetches.
|
|
188
|
+
const blobSvgUris = new Map<string, string>();
|
|
189
|
+
const blobSources = Array.from(
|
|
190
|
+
element.querySelectorAll<HTMLObjectElement>('.c-shape-object object'),
|
|
191
|
+
).map((o) => o.getAttribute('data'));
|
|
192
|
+
|
|
193
|
+
for (const src of blobSources) {
|
|
194
|
+
if (src && src.startsWith('blob:') && !blobSvgUris.has(src)) {
|
|
80
195
|
try {
|
|
81
|
-
const
|
|
82
|
-
const svgText = await response.text();
|
|
196
|
+
const svgText = await (await fetch(src)).text();
|
|
83
197
|
|
|
84
|
-
|
|
85
|
-
|
|
198
|
+
blobSvgUris.set(
|
|
199
|
+
src,
|
|
86
200
|
`data:image/svg+xml,${encodeURIComponent(svgText)}`,
|
|
87
201
|
);
|
|
88
202
|
} catch {
|
|
@@ -91,34 +205,205 @@ export function useSnapshot() {
|
|
|
91
205
|
}
|
|
92
206
|
}
|
|
93
207
|
|
|
94
|
-
|
|
95
|
-
|
|
208
|
+
// html2canvas ignores `object-fit`, so it stretches images to their box
|
|
209
|
+
// instead of cropping/containing them — section images (cover) come out
|
|
210
|
+
// distorted. Record the rendered box + fit of every object-fit image from
|
|
211
|
+
// the live (laid-out) DOM, keyed by src, so the clone can swap them for a
|
|
212
|
+
// background-size div, which html2canvas renders correctly.
|
|
213
|
+
interface ObjectFitInfo {
|
|
214
|
+
fit: string;
|
|
215
|
+
position: string;
|
|
216
|
+
width: number;
|
|
217
|
+
height: number;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const objectFitImages = new Map<string, ObjectFitInfo>();
|
|
221
|
+
|
|
222
|
+
for (const img of Array.from(
|
|
223
|
+
element.querySelectorAll<HTMLImageElement>('img'),
|
|
224
|
+
)) {
|
|
225
|
+
const { objectFit, objectPosition } = getComputedStyle(img);
|
|
226
|
+
|
|
227
|
+
if (objectFit !== 'cover' && objectFit !== 'contain') continue;
|
|
228
|
+
|
|
229
|
+
const rect = img.getBoundingClientRect();
|
|
230
|
+
const src = img.currentSrc || img.src;
|
|
231
|
+
|
|
232
|
+
if (src && rect.width > 2 && rect.height > 2) {
|
|
233
|
+
objectFitImages.set(src, {
|
|
234
|
+
fit: objectFit,
|
|
235
|
+
position: objectPosition || 'center',
|
|
236
|
+
width: Math.round(rect.width),
|
|
237
|
+
height: Math.round(rect.height),
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// html2canvas can't render <iframe> embeds (e.g. YouTube videos) — they
|
|
243
|
+
// come out blank. Pre-fetch each embed's thumbnail as a data URI, keyed by
|
|
244
|
+
// video id, so the clone can swap the iframe for a poster image.
|
|
245
|
+
interface VideoThumb {
|
|
246
|
+
dataUri: string;
|
|
247
|
+
width: number;
|
|
248
|
+
height: number;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const videoThumbs = new Map<string, VideoThumb>();
|
|
252
|
+
|
|
253
|
+
for (const iframe of Array.from(
|
|
254
|
+
element.querySelectorAll<HTMLIFrameElement>('iframe'),
|
|
255
|
+
)) {
|
|
256
|
+
const id = getYouTubeId(iframe.src);
|
|
257
|
+
|
|
258
|
+
if (!id || videoThumbs.has(id)) continue;
|
|
259
|
+
|
|
260
|
+
const dataUri = await fetchYouTubeThumbnail(id);
|
|
261
|
+
|
|
262
|
+
if (!dataUri) continue;
|
|
263
|
+
|
|
264
|
+
const rect = iframe.getBoundingClientRect();
|
|
265
|
+
|
|
266
|
+
videoThumbs.set(id, {
|
|
267
|
+
dataUri,
|
|
268
|
+
width: Math.round(rect.width) || iframe.clientWidth,
|
|
269
|
+
height: Math.round(rect.height) || iframe.clientHeight,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const canvas = await window.html2canvas(target, {
|
|
274
|
+
// Must stay false: with allowTaint, html2canvas draws cross-origin
|
|
275
|
+
// media that lacks CORS (e.g. a Getty background <video>), which taints
|
|
276
|
+
// the canvas and makes toDataURL throw — failing the whole snapshot.
|
|
277
|
+
// With it off, such content is skipped (rendered blank) instead, so the
|
|
278
|
+
// snapshot always exports. CORS-enabled images (Unsplash, user uploads)
|
|
279
|
+
// still render via useCORS.
|
|
280
|
+
allowTaint: false,
|
|
96
281
|
useCORS: true,
|
|
97
|
-
scale
|
|
282
|
+
scale, // fit the full page into the vision model's image budget
|
|
98
283
|
logging: false,
|
|
99
284
|
backgroundColor: '#ffffff',
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
285
|
+
// Capture the full page, not just the scroll viewport.
|
|
286
|
+
height: captureHeight,
|
|
287
|
+
windowHeight: captureHeight,
|
|
288
|
+
scrollX: 0,
|
|
289
|
+
scrollY: 0,
|
|
290
|
+
x: 0,
|
|
291
|
+
y: 0,
|
|
292
|
+
onclone: (_doc: Document) => {
|
|
293
|
+
const setStyle = (
|
|
294
|
+
selector: string,
|
|
295
|
+
styles: Partial<CSSStyleDeclaration>,
|
|
296
|
+
) => {
|
|
297
|
+
const el = _doc.querySelector<HTMLElement>(selector);
|
|
298
|
+
|
|
299
|
+
if (el) Object.assign(el.style, styles);
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
// Neutralise the fixed-position + scale wrapper and the scroll clip so
|
|
303
|
+
// the clone lays out at full content height; otherwise html2canvas
|
|
304
|
+
// honours the viewport-height box and the page is cut off.
|
|
305
|
+
setStyle('.c-preview-container', {
|
|
306
|
+
position: 'static',
|
|
307
|
+
transform: 'none',
|
|
308
|
+
height: 'auto',
|
|
309
|
+
overflow: 'visible',
|
|
310
|
+
});
|
|
311
|
+
setStyle('[data-test-tag="editor-preview"]', {
|
|
312
|
+
position: 'relative',
|
|
313
|
+
height: `${captureHeight}px`,
|
|
314
|
+
overflow: 'visible',
|
|
315
|
+
});
|
|
316
|
+
// Stretch the (normally fixed, viewport-height) background and shape
|
|
317
|
+
// layers to cover the full page so transparent sections render their
|
|
318
|
+
// intended background instead of the white fallback.
|
|
319
|
+
setStyle('.brand-page-root-background', {
|
|
320
|
+
position: 'absolute',
|
|
321
|
+
top: '0',
|
|
322
|
+
left: '0',
|
|
323
|
+
width: '100%',
|
|
324
|
+
height: '100%',
|
|
325
|
+
});
|
|
326
|
+
setStyle('.brand-page__background-shapes-layer', {
|
|
327
|
+
position: 'absolute',
|
|
328
|
+
top: '0',
|
|
329
|
+
left: '0',
|
|
330
|
+
width: '100%',
|
|
331
|
+
height: '100%',
|
|
332
|
+
});
|
|
333
|
+
setStyle('.brand-page-root', {
|
|
334
|
+
position: 'relative',
|
|
335
|
+
height: 'auto',
|
|
336
|
+
});
|
|
337
|
+
setStyle('.brand-page', { height: 'auto' });
|
|
338
|
+
setStyle('.brand-page__inner', {
|
|
339
|
+
height: 'auto',
|
|
340
|
+
overflow: 'visible',
|
|
341
|
+
});
|
|
104
342
|
|
|
105
|
-
|
|
106
|
-
|
|
343
|
+
// Swap content shape <object>s for <img> using the pre-fetched data
|
|
344
|
+
// URIs (html2canvas can't render <object> SVGs directly).
|
|
345
|
+
_doc
|
|
346
|
+
.querySelectorAll<HTMLObjectElement>('.c-shape-object object')
|
|
347
|
+
.forEach((obj) => {
|
|
348
|
+
const dataUrl = obj.getAttribute('data');
|
|
107
349
|
|
|
108
|
-
|
|
109
|
-
|
|
350
|
+
if (dataUrl && blobSvgUris.has(dataUrl)) {
|
|
351
|
+
const img = _doc.createElement('img');
|
|
110
352
|
|
|
111
|
-
|
|
353
|
+
img.src = blobSvgUris.get(dataUrl)!;
|
|
112
354
|
|
|
113
|
-
|
|
355
|
+
const style = obj.getAttribute('style');
|
|
114
356
|
|
|
115
|
-
|
|
116
|
-
img.setAttribute('style', style);
|
|
117
|
-
}
|
|
357
|
+
if (style) img.setAttribute('style', style);
|
|
118
358
|
|
|
119
|
-
|
|
359
|
+
obj.parentNode?.replaceChild(img, obj);
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
// Replace object-fit images with a background-size div so they
|
|
364
|
+
// render cropped/contained instead of stretched.
|
|
365
|
+
_doc.querySelectorAll<HTMLImageElement>('img').forEach((img) => {
|
|
366
|
+
const src = img.currentSrc || img.src;
|
|
367
|
+
const fitInfo = objectFitImages.get(src);
|
|
368
|
+
|
|
369
|
+
if (fitInfo) {
|
|
370
|
+
const div = _doc.createElement('div');
|
|
371
|
+
|
|
372
|
+
div.className = img.className;
|
|
373
|
+
div.style.cssText = img.getAttribute('style') || '';
|
|
374
|
+
div.style.backgroundImage = `url("${img.src}")`;
|
|
375
|
+
div.style.backgroundSize = fitInfo.fit;
|
|
376
|
+
div.style.backgroundPosition = fitInfo.position;
|
|
377
|
+
div.style.backgroundRepeat = 'no-repeat';
|
|
378
|
+
div.style.width = `${fitInfo.width}px`;
|
|
379
|
+
div.style.height = `${fitInfo.height}px`;
|
|
380
|
+
|
|
381
|
+
img.parentNode?.replaceChild(div, img);
|
|
120
382
|
}
|
|
121
383
|
});
|
|
384
|
+
|
|
385
|
+
// Replace video iframes (blank in html2canvas) with their poster
|
|
386
|
+
// thumbnail rendered as a background-size div.
|
|
387
|
+
_doc
|
|
388
|
+
.querySelectorAll<HTMLIFrameElement>('iframe')
|
|
389
|
+
.forEach((iframe) => {
|
|
390
|
+
const id = getYouTubeId(iframe.src);
|
|
391
|
+
const thumb = id ? videoThumbs.get(id) : undefined;
|
|
392
|
+
|
|
393
|
+
if (!thumb) return;
|
|
394
|
+
|
|
395
|
+
const div = _doc.createElement('div');
|
|
396
|
+
|
|
397
|
+
div.style.cssText = iframe.getAttribute('style') || '';
|
|
398
|
+
div.style.width = `${thumb.width}px`;
|
|
399
|
+
div.style.height = `${thumb.height}px`;
|
|
400
|
+
div.style.backgroundImage = `url("${thumb.dataUri}")`;
|
|
401
|
+
div.style.backgroundSize = 'cover';
|
|
402
|
+
div.style.backgroundPosition = 'center';
|
|
403
|
+
div.style.backgroundColor = '#000000';
|
|
404
|
+
|
|
405
|
+
iframe.parentNode?.replaceChild(div, iframe);
|
|
406
|
+
});
|
|
122
407
|
},
|
|
123
408
|
});
|
|
124
409
|
|
package/src/shared/content-settings/components/content-settings/i18n/content-settings.de-DE.json
CHANGED
|
@@ -113,6 +113,8 @@
|
|
|
113
113
|
"iconFillColorTitle": "Farbe der Symbolfüllung",
|
|
114
114
|
"iconBackgroundColorTitle": "Symbolhintergrundfarbe",
|
|
115
115
|
"paddingTitle": "Polsterung",
|
|
116
|
+
"topPaddingTitle": "Polsterung oben",
|
|
117
|
+
"bottomPaddingTitle": "Polsterung unten",
|
|
116
118
|
"dividerStyleTitle": "Trennlinienstil",
|
|
117
119
|
"dividerColorTitle": "Trennlinienfarbe",
|
|
118
120
|
"addItemTitle": "Artikel hinzufügen",
|
package/src/shared/content-settings/components/content-settings/i18n/content-settings.es-ES.json
CHANGED
|
@@ -113,6 +113,8 @@
|
|
|
113
113
|
"iconFillColorTitle": "Color de relleno del icono",
|
|
114
114
|
"iconBackgroundColorTitle": "Color de fondo del icono",
|
|
115
115
|
"paddingTitle": "Relleno",
|
|
116
|
+
"topPaddingTitle": "Relleno superior",
|
|
117
|
+
"bottomPaddingTitle": "Relleno inferior",
|
|
116
118
|
"dividerStyleTitle": "Estilo del divisor",
|
|
117
119
|
"dividerColorTitle": "Color del separador",
|
|
118
120
|
"addItemTitle": "Añadir artículo",
|
package/src/shared/content-settings/components/content-settings/i18n/content-settings.fr-CA.json
CHANGED
|
@@ -113,6 +113,8 @@
|
|
|
113
113
|
"iconFillColorTitle": "Couleur de remplissage de l’icône",
|
|
114
114
|
"iconBackgroundColorTitle": "Couleur d’arrière-plan de l’icône",
|
|
115
115
|
"paddingTitle": "Marges intérieures",
|
|
116
|
+
"topPaddingTitle": "Marge intérieure supérieure",
|
|
117
|
+
"bottomPaddingTitle": "Marges intérieures inférieures",
|
|
116
118
|
"dividerStyleTitle": "Style de séparateur",
|
|
117
119
|
"dividerColorTitle": "Couleur du séparateur",
|
|
118
120
|
"addItemTitle": "Ajouter un article",
|
package/src/shared/content-settings/components/content-settings/i18n/content-settings.fr-FR.json
CHANGED
|
@@ -113,6 +113,8 @@
|
|
|
113
113
|
"iconFillColorTitle": "Couleur de remplissage de l’icône",
|
|
114
114
|
"iconBackgroundColorTitle": "Couleur d’arrière-plan de l’icône",
|
|
115
115
|
"paddingTitle": "Marges intérieures",
|
|
116
|
+
"topPaddingTitle": "Marges intérieures supérieures",
|
|
117
|
+
"bottomPaddingTitle": "Marges intérieures inférieures",
|
|
116
118
|
"dividerStyleTitle": "Style de séparateur",
|
|
117
119
|
"dividerColorTitle": "Couleur du séparateur",
|
|
118
120
|
"addItemTitle": "Ajouter un article",
|
|
@@ -113,6 +113,8 @@
|
|
|
113
113
|
"iconFillColorTitle": "Icon fill color",
|
|
114
114
|
"iconBackgroundColorTitle": "Icon background color",
|
|
115
115
|
"paddingTitle": "Padding",
|
|
116
|
+
"topPaddingTitle": "Top padding",
|
|
117
|
+
"bottomPaddingTitle": "Bottom padding",
|
|
116
118
|
"dividerStyleTitle": "Divider Style",
|
|
117
119
|
"dividerColorTitle": "Divider color",
|
|
118
120
|
"addItemTitle": "Add Item",
|
package/src/shared/content-settings/components/content-settings/i18n/content-settings.pt-BR.json
CHANGED
|
@@ -113,6 +113,8 @@
|
|
|
113
113
|
"iconFillColorTitle": "Cor de preenchimento do ícone",
|
|
114
114
|
"iconBackgroundColorTitle": "Cor de fundo do ícone",
|
|
115
115
|
"paddingTitle": "Preenchimento",
|
|
116
|
+
"topPaddingTitle": "Preenchimento superior",
|
|
117
|
+
"bottomPaddingTitle": "Preenchimento inferior",
|
|
116
118
|
"dividerStyleTitle": "Estilo do separador",
|
|
117
119
|
"dividerColorTitle": "Cor do separador",
|
|
118
120
|
"addItemTitle": "Adicionar item",
|
package/src/shared/content-settings/components/content-settings/i18n/content-settings.pt-PT.json
CHANGED
|
@@ -113,6 +113,8 @@
|
|
|
113
113
|
"iconFillColorTitle": "Cor de preenchimento do ícone",
|
|
114
114
|
"iconBackgroundColorTitle": "Cor de fundo do ícone",
|
|
115
115
|
"paddingTitle": "Preenchimento",
|
|
116
|
+
"topPaddingTitle": "Preenchimento superior",
|
|
117
|
+
"bottomPaddingTitle": "Preenchimento inferior",
|
|
116
118
|
"dividerStyleTitle": "Estilo do separador",
|
|
117
119
|
"dividerColorTitle": "Cor do separador",
|
|
118
120
|
"addItemTitle": "Adicionar Item",
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
>
|
|
26
26
|
<ul>
|
|
27
27
|
<li
|
|
28
|
-
v-for="
|
|
29
|
-
:key="`content-settings-item-group-field-${id}-${group.id}-${
|
|
28
|
+
v-for="field in group.fields"
|
|
29
|
+
:key="`content-settings-item-group-field-${id}-${group.id}-${field.name}`"
|
|
30
30
|
>
|
|
31
31
|
<ContentSettingsItemField
|
|
32
32
|
v-if="
|
|
@@ -45,6 +45,15 @@ export default defineComponent({
|
|
|
45
45
|
sliderValue: value,
|
|
46
46
|
};
|
|
47
47
|
},
|
|
48
|
+
watch: {
|
|
49
|
+
// The value can change outside the slider (e.g. a layout switch
|
|
50
|
+
// re-applying default padding, or undo) — keep the display in sync.
|
|
51
|
+
value(newValue: string | undefined) {
|
|
52
|
+
this.sliderValue = this.valueConvertor
|
|
53
|
+
? this.valueConvertor.convert(newValue as any)
|
|
54
|
+
: newValue;
|
|
55
|
+
},
|
|
56
|
+
},
|
|
48
57
|
computed: {
|
|
49
58
|
innerMinLength(): number {
|
|
50
59
|
return this.minLength ?? 0;
|
|
@@ -205,7 +205,17 @@ const minifyContent = (
|
|
|
205
205
|
editor: Editor,
|
|
206
206
|
assets?: AssetEntry[],
|
|
207
207
|
): MinifiedContent => {
|
|
208
|
-
const base: MinifiedContent = {
|
|
208
|
+
const base: MinifiedContent = {
|
|
209
|
+
id: content.id,
|
|
210
|
+
type: content.type,
|
|
211
|
+
// Padding Control (BP-5687) — shared across every block, persist when set.
|
|
212
|
+
...(typeof content.topPadding === 'number' && {
|
|
213
|
+
topPadding: content.topPadding,
|
|
214
|
+
}),
|
|
215
|
+
...(typeof content.bottomPadding === 'number' && {
|
|
216
|
+
bottomPadding: content.bottomPadding,
|
|
217
|
+
}),
|
|
218
|
+
};
|
|
209
219
|
const c = content as any;
|
|
210
220
|
|
|
211
221
|
// Helper: include imageUrl and its asset key if available
|
|
@@ -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
|
-
}
|