@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
|
@@ -1529,6 +1529,8 @@
|
|
|
1529
1529
|
"iconFillColorTitle": "Color de relleno del icono",
|
|
1530
1530
|
"iconBackgroundColorTitle": "Color de fondo del icono",
|
|
1531
1531
|
"paddingTitle": "Relleno",
|
|
1532
|
+
"topPaddingTitle": "Relleno superior",
|
|
1533
|
+
"bottomPaddingTitle": "Relleno inferior",
|
|
1532
1534
|
"dividerStyleTitle": "Estilo del divisor",
|
|
1533
1535
|
"dividerColorTitle": "Color del separador",
|
|
1534
1536
|
"addItemTitle": "Añadir artículo",
|
|
@@ -1529,6 +1529,8 @@
|
|
|
1529
1529
|
"iconFillColorTitle": "Couleur de remplissage de l’icône",
|
|
1530
1530
|
"iconBackgroundColorTitle": "Couleur d’arrière-plan de l’icône",
|
|
1531
1531
|
"paddingTitle": "Marges intérieures",
|
|
1532
|
+
"topPaddingTitle": "Marge intérieure supérieure",
|
|
1533
|
+
"bottomPaddingTitle": "Marges intérieures inférieures",
|
|
1532
1534
|
"dividerStyleTitle": "Style de séparateur",
|
|
1533
1535
|
"dividerColorTitle": "Couleur du séparateur",
|
|
1534
1536
|
"addItemTitle": "Ajouter un article",
|
|
@@ -1529,6 +1529,8 @@
|
|
|
1529
1529
|
"iconFillColorTitle": "Couleur de remplissage de l’icône",
|
|
1530
1530
|
"iconBackgroundColorTitle": "Couleur d’arrière-plan de l’icône",
|
|
1531
1531
|
"paddingTitle": "Marges intérieures",
|
|
1532
|
+
"topPaddingTitle": "Marges intérieures supérieures",
|
|
1533
|
+
"bottomPaddingTitle": "Marges intérieures inférieures",
|
|
1532
1534
|
"dividerStyleTitle": "Style de séparateur",
|
|
1533
1535
|
"dividerColorTitle": "Couleur du séparateur",
|
|
1534
1536
|
"addItemTitle": "Ajouter un article",
|
|
@@ -1529,6 +1529,8 @@
|
|
|
1529
1529
|
"iconFillColorTitle": "Icon fill color",
|
|
1530
1530
|
"iconBackgroundColorTitle": "Icon background color",
|
|
1531
1531
|
"paddingTitle": "Padding",
|
|
1532
|
+
"topPaddingTitle": "Top padding",
|
|
1533
|
+
"bottomPaddingTitle": "Bottom padding",
|
|
1532
1534
|
"dividerStyleTitle": "Divider Style",
|
|
1533
1535
|
"dividerColorTitle": "Divider color",
|
|
1534
1536
|
"addItemTitle": "Add Item",
|
|
@@ -1529,6 +1529,8 @@
|
|
|
1529
1529
|
"iconFillColorTitle": "Cor de preenchimento do ícone",
|
|
1530
1530
|
"iconBackgroundColorTitle": "Cor de fundo do ícone",
|
|
1531
1531
|
"paddingTitle": "Preenchimento",
|
|
1532
|
+
"topPaddingTitle": "Preenchimento superior",
|
|
1533
|
+
"bottomPaddingTitle": "Preenchimento inferior",
|
|
1532
1534
|
"dividerStyleTitle": "Estilo do separador",
|
|
1533
1535
|
"dividerColorTitle": "Cor do separador",
|
|
1534
1536
|
"addItemTitle": "Adicionar item",
|
|
@@ -1529,6 +1529,8 @@
|
|
|
1529
1529
|
"iconFillColorTitle": "Cor de preenchimento do ícone",
|
|
1530
1530
|
"iconBackgroundColorTitle": "Cor de fundo do ícone",
|
|
1531
1531
|
"paddingTitle": "Preenchimento",
|
|
1532
|
+
"topPaddingTitle": "Preenchimento superior",
|
|
1533
|
+
"bottomPaddingTitle": "Preenchimento inferior",
|
|
1532
1534
|
"dividerStyleTitle": "Estilo do separador",
|
|
1533
1535
|
"dividerColorTitle": "Cor do separador",
|
|
1534
1536
|
"addItemTitle": "Adicionar Item",
|
|
@@ -1,6 +1,57 @@
|
|
|
1
1
|
import { ref } from 'vue';
|
|
2
2
|
const HTML2CANVAS_CDN_URL = 'https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js';
|
|
3
3
|
let html2canvasLoadPromise = null;
|
|
4
|
+
// Capture is sized to the rendered content (scrollHeight), not the clipped
|
|
5
|
+
// viewport box. An empty page yields a short capture; a long page captures its
|
|
6
|
+
// full height up to the cap.
|
|
7
|
+
const MIN_SNAPSHOT_HEIGHT = 200;
|
|
8
|
+
// The snapshot is consumed by the AI pipeline's OpenAI vision model, which
|
|
9
|
+
// normalises the SHORT side to ~768px and tiles the long side — so token cost
|
|
10
|
+
// is driven by page HEIGHT, not absolute resolution. This cap bounds that cost
|
|
11
|
+
// AND, more importantly, the html2canvas render work (CPU/memory) — taller pages
|
|
12
|
+
// are truncated. The render is the dominant cost on a capture, so we keep the
|
|
13
|
+
// cap conservative, and lower still on mobile where memory/CPU are tighter.
|
|
14
|
+
const MAX_SNAPSHOT_HEIGHT_DESKTOP = 4096;
|
|
15
|
+
const MAX_SNAPSHOT_HEIGHT_MOBILE = 2560;
|
|
16
|
+
// OpenAI's vision short-side target. Rendering at this width avoids the
|
|
17
|
+
// down-then-up resample blur you'd get from scaling smaller (OpenAI upscales
|
|
18
|
+
// the short side back to 768 regardless) while keeping the payload tight.
|
|
19
|
+
const SNAPSHOT_TARGET_WIDTH = 768;
|
|
20
|
+
/**
|
|
21
|
+
* Extracts the YouTube video id from an embed URL, or null for non-YouTube
|
|
22
|
+
* iframes. Handles youtube.com/embed/<id> and youtube-nocookie.com variants.
|
|
23
|
+
*/
|
|
24
|
+
function getYouTubeId(src) {
|
|
25
|
+
const match = src.match(/(?:youtube(?:-nocookie)?\.com\/embed\/)([A-Za-z0-9_-]{6,})/);
|
|
26
|
+
return match ? match[1] : null;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Fetches a YouTube thumbnail and returns it as a data URI, or null on failure.
|
|
30
|
+
* Inlining as a data URI avoids cross-origin canvas tainting in html2canvas.
|
|
31
|
+
* Tries maxres first, falls back to hq (maxres 404s on some videos).
|
|
32
|
+
*/
|
|
33
|
+
async function fetchYouTubeThumbnail(id) {
|
|
34
|
+
for (const quality of ['maxresdefault', 'hqdefault']) {
|
|
35
|
+
try {
|
|
36
|
+
const response = await fetch(`https://img.youtube.com/vi/${id}/${quality}.jpg`);
|
|
37
|
+
if (!response.ok)
|
|
38
|
+
continue;
|
|
39
|
+
const blob = await response.blob();
|
|
40
|
+
// YouTube returns a tiny placeholder for missing qualities; skip those.
|
|
41
|
+
if (blob.size < 2000)
|
|
42
|
+
continue;
|
|
43
|
+
return await new Promise((resolve) => {
|
|
44
|
+
const reader = new FileReader();
|
|
45
|
+
reader.onload = () => resolve(reader.result);
|
|
46
|
+
reader.readAsDataURL(blob);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
/* try next quality */
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
4
55
|
/**
|
|
5
56
|
* Dynamically loads html2canvas from CDN if not already loaded
|
|
6
57
|
*/
|
|
@@ -47,44 +98,205 @@ export function useSnapshot() {
|
|
|
47
98
|
if (typeof window.html2canvas === 'undefined') {
|
|
48
99
|
throw new Error('html2canvas failed to load');
|
|
49
100
|
}
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
101
|
+
// Wait for fonts and in-flight images to finish before capturing —
|
|
102
|
+
// otherwise html2canvas snapshots a half-rendered page (missing images,
|
|
103
|
+
// fallback fonts) and the measured content height is wrong.
|
|
104
|
+
await document.fonts?.ready;
|
|
105
|
+
await Promise.all(Array.from(element.querySelectorAll('img'))
|
|
106
|
+
.filter((img) => !img.complete)
|
|
107
|
+
.map((img) => new Promise((resolve) => {
|
|
108
|
+
img.onload = img.onerror = () => resolve();
|
|
109
|
+
})));
|
|
110
|
+
// The maker renders the full website inside `.c-preview-container`
|
|
111
|
+
// (position: fixed + transform: scale). The page background — solid
|
|
112
|
+
// colour, gradient, or image — lives on `.brand-page-root-background`,
|
|
113
|
+
// which sits OUTSIDE the scrolling content. Capturing the content alone
|
|
114
|
+
// drops that background, so transparent sections fall back to white and
|
|
115
|
+
// their (often light) text becomes invisible. Capture the whole preview
|
|
116
|
+
// (`[data-test-tag="editor-preview"]`) so the background layer is
|
|
117
|
+
// included, and size the canvas to the real page length, which only the
|
|
118
|
+
// inner scroll container `.brand-page__inner` reports via scrollHeight.
|
|
119
|
+
const target = element.querySelector('[data-test-tag="editor-preview"]') ?? element;
|
|
120
|
+
const scrollEl = element.querySelector('.brand-page__inner') ?? target;
|
|
121
|
+
// Keep the render light on mobile (tighter CPU/memory) by truncating
|
|
122
|
+
// taller there than on desktop.
|
|
123
|
+
const isMobileViewport = typeof window !== 'undefined' &&
|
|
124
|
+
window.matchMedia?.('(max-width: 768px)').matches;
|
|
125
|
+
const maxHeight = isMobileViewport
|
|
126
|
+
? MAX_SNAPSHOT_HEIGHT_MOBILE
|
|
127
|
+
: MAX_SNAPSHOT_HEIGHT_DESKTOP;
|
|
128
|
+
const captureHeight = Math.min(Math.max(scrollEl.scrollHeight, MIN_SNAPSHOT_HEIGHT), maxHeight);
|
|
129
|
+
// Render at OpenAI's short-side target width (never upscaling). Token cost
|
|
130
|
+
// is set by height/aspect ratio, not by scaling down the width, so we keep
|
|
131
|
+
// the width crisp and let MAX_SNAPSHOT_HEIGHT bound the cost.
|
|
132
|
+
const captureWidth = target.clientWidth || target.offsetWidth || 1;
|
|
133
|
+
const scale = Math.min(1, SNAPSHOT_TARGET_WIDTH / captureWidth);
|
|
134
|
+
// Pre-fetch blob-backed SVGs from content shape <object> elements so they
|
|
135
|
+
// can be rendered as <img> in the clone (html2canvas can't reach into the
|
|
136
|
+
// isolated DOM inside <object>). Background-shape blob <img>s are NOT
|
|
137
|
+
// converted: html2canvas renders them no differently with or without the
|
|
138
|
+
// conversion, so it'd just be wasted fetches.
|
|
139
|
+
const blobSvgUris = new Map();
|
|
140
|
+
const blobSources = Array.from(element.querySelectorAll('.c-shape-object object')).map((o) => o.getAttribute('data'));
|
|
141
|
+
for (const src of blobSources) {
|
|
142
|
+
if (src && src.startsWith('blob:') && !blobSvgUris.has(src)) {
|
|
58
143
|
try {
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
shapeDataUris.set(dataUrl, `data:image/svg+xml,${encodeURIComponent(svgText)}`);
|
|
144
|
+
const svgText = await (await fetch(src)).text();
|
|
145
|
+
blobSvgUris.set(src, `data:image/svg+xml,${encodeURIComponent(svgText)}`);
|
|
62
146
|
}
|
|
63
147
|
catch {
|
|
64
148
|
/* skip – shape will render as blank, same as before */
|
|
65
149
|
}
|
|
66
150
|
}
|
|
67
151
|
}
|
|
68
|
-
const
|
|
69
|
-
|
|
152
|
+
const objectFitImages = new Map();
|
|
153
|
+
for (const img of Array.from(element.querySelectorAll('img'))) {
|
|
154
|
+
const { objectFit, objectPosition } = getComputedStyle(img);
|
|
155
|
+
if (objectFit !== 'cover' && objectFit !== 'contain')
|
|
156
|
+
continue;
|
|
157
|
+
const rect = img.getBoundingClientRect();
|
|
158
|
+
const src = img.currentSrc || img.src;
|
|
159
|
+
if (src && rect.width > 2 && rect.height > 2) {
|
|
160
|
+
objectFitImages.set(src, {
|
|
161
|
+
fit: objectFit,
|
|
162
|
+
position: objectPosition || 'center',
|
|
163
|
+
width: Math.round(rect.width),
|
|
164
|
+
height: Math.round(rect.height),
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const videoThumbs = new Map();
|
|
169
|
+
for (const iframe of Array.from(element.querySelectorAll('iframe'))) {
|
|
170
|
+
const id = getYouTubeId(iframe.src);
|
|
171
|
+
if (!id || videoThumbs.has(id))
|
|
172
|
+
continue;
|
|
173
|
+
const dataUri = await fetchYouTubeThumbnail(id);
|
|
174
|
+
if (!dataUri)
|
|
175
|
+
continue;
|
|
176
|
+
const rect = iframe.getBoundingClientRect();
|
|
177
|
+
videoThumbs.set(id, {
|
|
178
|
+
dataUri,
|
|
179
|
+
width: Math.round(rect.width) || iframe.clientWidth,
|
|
180
|
+
height: Math.round(rect.height) || iframe.clientHeight,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
const canvas = await window.html2canvas(target, {
|
|
184
|
+
// Must stay false: with allowTaint, html2canvas draws cross-origin
|
|
185
|
+
// media that lacks CORS (e.g. a Getty background <video>), which taints
|
|
186
|
+
// the canvas and makes toDataURL throw — failing the whole snapshot.
|
|
187
|
+
// With it off, such content is skipped (rendered blank) instead, so the
|
|
188
|
+
// snapshot always exports. CORS-enabled images (Unsplash, user uploads)
|
|
189
|
+
// still render via useCORS.
|
|
190
|
+
allowTaint: false,
|
|
70
191
|
useCORS: true,
|
|
71
|
-
scale
|
|
192
|
+
scale, // fit the full page into the vision model's image budget
|
|
72
193
|
logging: false,
|
|
73
194
|
backgroundColor: '#ffffff',
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
195
|
+
// Capture the full page, not just the scroll viewport.
|
|
196
|
+
height: captureHeight,
|
|
197
|
+
windowHeight: captureHeight,
|
|
198
|
+
scrollX: 0,
|
|
199
|
+
scrollY: 0,
|
|
200
|
+
x: 0,
|
|
201
|
+
y: 0,
|
|
202
|
+
onclone: (_doc) => {
|
|
203
|
+
const setStyle = (selector, styles) => {
|
|
204
|
+
const el = _doc.querySelector(selector);
|
|
205
|
+
if (el)
|
|
206
|
+
Object.assign(el.style, styles);
|
|
207
|
+
};
|
|
208
|
+
// Neutralise the fixed-position + scale wrapper and the scroll clip so
|
|
209
|
+
// the clone lays out at full content height; otherwise html2canvas
|
|
210
|
+
// honours the viewport-height box and the page is cut off.
|
|
211
|
+
setStyle('.c-preview-container', {
|
|
212
|
+
position: 'static',
|
|
213
|
+
transform: 'none',
|
|
214
|
+
height: 'auto',
|
|
215
|
+
overflow: 'visible',
|
|
216
|
+
});
|
|
217
|
+
setStyle('[data-test-tag="editor-preview"]', {
|
|
218
|
+
position: 'relative',
|
|
219
|
+
height: `${captureHeight}px`,
|
|
220
|
+
overflow: 'visible',
|
|
221
|
+
});
|
|
222
|
+
// Stretch the (normally fixed, viewport-height) background and shape
|
|
223
|
+
// layers to cover the full page so transparent sections render their
|
|
224
|
+
// intended background instead of the white fallback.
|
|
225
|
+
setStyle('.brand-page-root-background', {
|
|
226
|
+
position: 'absolute',
|
|
227
|
+
top: '0',
|
|
228
|
+
left: '0',
|
|
229
|
+
width: '100%',
|
|
230
|
+
height: '100%',
|
|
231
|
+
});
|
|
232
|
+
setStyle('.brand-page__background-shapes-layer', {
|
|
233
|
+
position: 'absolute',
|
|
234
|
+
top: '0',
|
|
235
|
+
left: '0',
|
|
236
|
+
width: '100%',
|
|
237
|
+
height: '100%',
|
|
238
|
+
});
|
|
239
|
+
setStyle('.brand-page-root', {
|
|
240
|
+
position: 'relative',
|
|
241
|
+
height: 'auto',
|
|
242
|
+
});
|
|
243
|
+
setStyle('.brand-page', { height: 'auto' });
|
|
244
|
+
setStyle('.brand-page__inner', {
|
|
245
|
+
height: 'auto',
|
|
246
|
+
overflow: 'visible',
|
|
247
|
+
});
|
|
248
|
+
// Swap content shape <object>s for <img> using the pre-fetched data
|
|
249
|
+
// URIs (html2canvas can't render <object> SVGs directly).
|
|
250
|
+
_doc
|
|
251
|
+
.querySelectorAll('.c-shape-object object')
|
|
252
|
+
.forEach((obj) => {
|
|
77
253
|
const dataUrl = obj.getAttribute('data');
|
|
78
|
-
if (dataUrl &&
|
|
254
|
+
if (dataUrl && blobSvgUris.has(dataUrl)) {
|
|
79
255
|
const img = _doc.createElement('img');
|
|
80
|
-
img.src =
|
|
256
|
+
img.src = blobSvgUris.get(dataUrl);
|
|
81
257
|
const style = obj.getAttribute('style');
|
|
82
|
-
if (style)
|
|
258
|
+
if (style)
|
|
83
259
|
img.setAttribute('style', style);
|
|
84
|
-
}
|
|
85
260
|
obj.parentNode?.replaceChild(img, obj);
|
|
86
261
|
}
|
|
87
262
|
});
|
|
263
|
+
// Replace object-fit images with a background-size div so they
|
|
264
|
+
// render cropped/contained instead of stretched.
|
|
265
|
+
_doc.querySelectorAll('img').forEach((img) => {
|
|
266
|
+
const src = img.currentSrc || img.src;
|
|
267
|
+
const fitInfo = objectFitImages.get(src);
|
|
268
|
+
if (fitInfo) {
|
|
269
|
+
const div = _doc.createElement('div');
|
|
270
|
+
div.className = img.className;
|
|
271
|
+
div.style.cssText = img.getAttribute('style') || '';
|
|
272
|
+
div.style.backgroundImage = `url("${img.src}")`;
|
|
273
|
+
div.style.backgroundSize = fitInfo.fit;
|
|
274
|
+
div.style.backgroundPosition = fitInfo.position;
|
|
275
|
+
div.style.backgroundRepeat = 'no-repeat';
|
|
276
|
+
div.style.width = `${fitInfo.width}px`;
|
|
277
|
+
div.style.height = `${fitInfo.height}px`;
|
|
278
|
+
img.parentNode?.replaceChild(div, img);
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
// Replace video iframes (blank in html2canvas) with their poster
|
|
282
|
+
// thumbnail rendered as a background-size div.
|
|
283
|
+
_doc
|
|
284
|
+
.querySelectorAll('iframe')
|
|
285
|
+
.forEach((iframe) => {
|
|
286
|
+
const id = getYouTubeId(iframe.src);
|
|
287
|
+
const thumb = id ? videoThumbs.get(id) : undefined;
|
|
288
|
+
if (!thumb)
|
|
289
|
+
return;
|
|
290
|
+
const div = _doc.createElement('div');
|
|
291
|
+
div.style.cssText = iframe.getAttribute('style') || '';
|
|
292
|
+
div.style.width = `${thumb.width}px`;
|
|
293
|
+
div.style.height = `${thumb.height}px`;
|
|
294
|
+
div.style.backgroundImage = `url("${thumb.dataUri}")`;
|
|
295
|
+
div.style.backgroundSize = 'cover';
|
|
296
|
+
div.style.backgroundPosition = 'center';
|
|
297
|
+
div.style.backgroundColor = '#000000';
|
|
298
|
+
iframe.parentNode?.replaceChild(div, iframe);
|
|
299
|
+
});
|
|
88
300
|
},
|
|
89
301
|
});
|
|
90
302
|
const base64Image = canvas.toDataURL('image/jpeg', 0.8); // Use JPEG with 80% quality for smaller size
|