@xiwebdesign/xicms-core 7.31.0 → 8.16.0
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/dist/XiCMSProvider.js +23 -7
- package/dist/XiCMSProvider.js.map +1 -1
- package/dist/admin/AdminApp.js +175 -93
- package/dist/admin/AdminApp.js.map +1 -1
- package/dist/admin/AdminHelp.d.ts +1 -1
- package/dist/admin/AdminHelp.js +44 -36
- package/dist/admin/AdminHelp.js.map +1 -1
- package/dist/admin/CoreVersion.d.ts +6 -3
- package/dist/admin/CoreVersion.js +60 -24
- package/dist/admin/CoreVersion.js.map +1 -1
- package/dist/admin/NewsEditor.d.ts +7 -0
- package/dist/admin/NewsEditor.js +117 -75
- package/dist/admin/NewsEditor.js.map +1 -1
- package/dist/admin/SuperadminPanel.js +1 -1
- package/dist/admin/SuperadminPanel.js.map +1 -1
- package/dist/admin/ZoekPalet.d.ts +15 -0
- package/dist/admin/ZoekPalet.js +123 -0
- package/dist/admin/ZoekPalet.js.map +1 -0
- package/dist/admin/blockEditors/GenericEditor.js +36 -18
- package/dist/admin/blockEditors/GenericEditor.js.map +1 -1
- package/dist/admin/blockEditors/StyleEditor.js +37 -3
- package/dist/admin/blockEditors/StyleEditor.js.map +1 -1
- package/dist/admin/blockEditors/SymbolRefEditor.js +44 -2
- package/dist/admin/blockEditors/SymbolRefEditor.js.map +1 -1
- package/dist/admin/blockEditors/index.js +154 -73
- package/dist/admin/blockEditors/index.js.map +1 -1
- package/dist/admin/customBlocks/CustomBlocksPanel.js +18 -7
- package/dist/admin/customBlocks/CustomBlocksPanel.js.map +1 -1
- package/dist/admin/dashboard/DashboardPanel.js +26 -16
- package/dist/admin/dashboard/DashboardPanel.js.map +1 -1
- package/dist/admin/editors/RichTextEditor.d.ts +4 -3
- package/dist/admin/editors/RichTextEditor.js +19 -60
- package/dist/admin/editors/RichTextEditor.js.map +1 -1
- package/dist/admin/editors/fontSizeExtension.js +7 -1
- package/dist/admin/editors/fontSizeExtension.js.map +1 -1
- package/dist/admin/editors/slashMenu.d.ts +18 -0
- package/dist/admin/editors/slashMenu.js +34 -0
- package/dist/admin/editors/slashMenu.js.map +1 -0
- package/dist/admin/editors/useRijkeTekst.d.ts +48 -0
- package/dist/admin/editors/useRijkeTekst.js +310 -0
- package/dist/admin/editors/useRijkeTekst.js.map +1 -0
- package/dist/admin/forms/FormsPanel.d.ts +11 -2
- package/dist/admin/forms/FormsPanel.js +218 -65
- package/dist/admin/forms/FormsPanel.js.map +1 -1
- package/dist/admin/lib/blokInfo.d.ts +17 -0
- package/dist/admin/lib/blokInfo.js +80 -0
- package/dist/admin/lib/blokInfo.js.map +1 -0
- package/dist/admin/lib/foutNaarMens.d.ts +1 -0
- package/dist/admin/lib/foutNaarMens.js +22 -0
- package/dist/admin/lib/foutNaarMens.js.map +1 -0
- package/dist/admin/lib/gewisteVelden.d.ts +13 -0
- package/dist/admin/lib/gewisteVelden.js +31 -0
- package/dist/admin/lib/gewisteVelden.js.map +1 -0
- package/dist/admin/lib/heroHulp.d.ts +11 -0
- package/dist/admin/lib/heroHulp.js +18 -0
- package/dist/admin/lib/heroHulp.js.map +1 -0
- package/dist/admin/lib/kleur.js +2 -3
- package/dist/admin/lib/kleur.js.map +1 -1
- package/dist/admin/lib/leesbaarheid.d.ts +34 -0
- package/dist/admin/lib/leesbaarheid.js +82 -0
- package/dist/admin/lib/leesbaarheid.js.map +1 -0
- package/dist/admin/lib/lengteAdvies.d.ts +1 -0
- package/dist/admin/lib/lengteAdvies.js +16 -0
- package/dist/admin/lib/lengteAdvies.js.map +1 -0
- package/dist/admin/lib/mediaLijst.d.ts +26 -0
- package/dist/admin/lib/mediaLijst.js +42 -0
- package/dist/admin/lib/mediaLijst.js.map +1 -0
- package/dist/admin/lib/mediaUsage.d.ts +12 -1
- package/dist/admin/lib/mediaUsage.js +43 -33
- package/dist/admin/lib/mediaUsage.js.map +1 -1
- package/dist/admin/lib/menuHulp.d.ts +16 -0
- package/dist/admin/lib/menuHulp.js +41 -0
- package/dist/admin/lib/menuHulp.js.map +1 -0
- package/dist/admin/lib/onopgeslagen.d.ts +10 -0
- package/dist/admin/lib/onopgeslagen.js +35 -0
- package/dist/admin/lib/onopgeslagen.js.map +1 -0
- package/dist/admin/lib/pagesClient.d.ts +55 -2
- package/dist/admin/lib/pagesClient.js +126 -28
- package/dist/admin/lib/pagesClient.js.map +1 -1
- package/dist/admin/lib/paginaGebruik.d.ts +56 -0
- package/dist/admin/lib/paginaGebruik.js +109 -0
- package/dist/admin/lib/paginaGebruik.js.map +1 -0
- package/dist/admin/lib/publicatieCheck.d.ts +25 -0
- package/dist/admin/lib/publicatieCheck.js +114 -0
- package/dist/admin/lib/publicatieCheck.js.map +1 -0
- package/dist/admin/lib/scrollNaarDetail.d.ts +6 -0
- package/dist/admin/lib/scrollNaarDetail.js +20 -0
- package/dist/admin/lib/scrollNaarDetail.js.map +1 -0
- package/dist/admin/lib/symbolsClient.d.ts +10 -0
- package/dist/admin/lib/symbolsClient.js +14 -1
- package/dist/admin/lib/symbolsClient.js.map +1 -1
- package/dist/admin/lib/tekstVeld.d.ts +8 -0
- package/dist/admin/lib/tekstVeld.js +10 -0
- package/dist/admin/lib/tekstVeld.js.map +1 -0
- package/dist/admin/lib/useIsSuperadmin.d.ts +2 -0
- package/dist/admin/lib/useIsSuperadmin.js +13 -0
- package/dist/admin/lib/useIsSuperadmin.js.map +1 -0
- package/dist/admin/lib/veldTaal.d.ts +14 -0
- package/dist/admin/lib/veldTaal.js +248 -0
- package/dist/admin/lib/veldTaal.js.map +1 -0
- package/dist/admin/lib/versieVergelijk.d.ts +17 -0
- package/dist/admin/lib/versieVergelijk.js +24 -0
- package/dist/admin/lib/versieVergelijk.js.map +1 -0
- package/dist/admin/lib/zodFields.js +22 -4
- package/dist/admin/lib/zodFields.js.map +1 -1
- package/dist/admin/lib/zodFout.d.ts +9 -0
- package/dist/admin/lib/zodFout.js +63 -0
- package/dist/admin/lib/zodFout.js.map +1 -0
- package/dist/admin/media/MediaManager.js +162 -32
- package/dist/admin/media/MediaManager.js.map +1 -1
- package/dist/admin/media/MediaPicker.d.ts +3 -0
- package/dist/admin/media/MediaPicker.js +84 -31
- package/dist/admin/media/MediaPicker.js.map +1 -1
- package/dist/admin/menu/MenuPanel.js +144 -83
- package/dist/admin/menu/MenuPanel.js.map +1 -1
- package/dist/admin/navContext.d.ts +3 -0
- package/dist/admin/navContext.js +13 -1
- package/dist/admin/navContext.js.map +1 -1
- package/dist/admin/pages/PageEditor.js +146 -30
- package/dist/admin/pages/PageEditor.js.map +1 -1
- package/dist/admin/pages/PagesPanel.js +215 -61
- package/dist/admin/pages/PagesPanel.js.map +1 -1
- package/dist/admin/pages/StarterKiezer.d.ts +12 -0
- package/dist/admin/pages/StarterKiezer.js +113 -0
- package/dist/admin/pages/StarterKiezer.js.map +1 -0
- package/dist/admin/panelRegistry.d.ts +1 -1
- package/dist/admin/panelRegistry.js +2 -1
- package/dist/admin/panelRegistry.js.map +1 -1
- package/dist/admin/patterns/PatternInserter.js +1 -1
- package/dist/admin/patterns/PatternInserter.js.map +1 -1
- package/dist/admin/settings/BedrijfInstellingen.d.ts +20 -0
- package/dist/admin/settings/BedrijfInstellingen.js +159 -0
- package/dist/admin/settings/BedrijfInstellingen.js.map +1 -0
- package/dist/admin/settings/MailTemplates.js +5 -4
- package/dist/admin/settings/MailTemplates.js.map +1 -1
- package/dist/admin/settings/SettingsPanel.js +120 -78
- package/dist/admin/settings/SettingsPanel.js.map +1 -1
- package/dist/admin/settings/SnapshotsCard.js +4 -4
- package/dist/admin/settings/SnapshotsCard.js.map +1 -1
- package/dist/admin/settings/VorigeTerugzetten.d.ts +15 -0
- package/dist/admin/settings/VorigeTerugzetten.js +89 -0
- package/dist/admin/settings/VorigeTerugzetten.js.map +1 -0
- package/dist/admin/shop/CategoriesTab.js +6 -4
- package/dist/admin/shop/CategoriesTab.js.map +1 -1
- package/dist/admin/shop/DiscountsTab.js +4 -2
- package/dist/admin/shop/DiscountsTab.js.map +1 -1
- package/dist/admin/shop/OrdersTab.js +57 -19
- package/dist/admin/shop/OrdersTab.js.map +1 -1
- package/dist/admin/shop/ProductPaneel.js +2 -1
- package/dist/admin/shop/ProductPaneel.js.map +1 -1
- package/dist/admin/shop/ProductsTab.js +40 -19
- package/dist/admin/shop/ProductsTab.js.map +1 -1
- package/dist/admin/shop/ShopPanel.js +13 -1
- package/dist/admin/shop/ShopPanel.js.map +1 -1
- package/dist/admin/shop/ShopSettingsTab.js +56 -17
- package/dist/admin/shop/ShopSettingsTab.js.map +1 -1
- package/dist/admin/shop/productClient.js +7 -3
- package/dist/admin/shop/productClient.js.map +1 -1
- package/dist/admin/stats/NietGevonden.d.ts +10 -0
- package/dist/admin/stats/NietGevonden.js +78 -0
- package/dist/admin/stats/NietGevonden.js.map +1 -0
- package/dist/admin/stats/StatsPanel.js +6 -1
- package/dist/admin/stats/StatsPanel.js.map +1 -1
- package/dist/admin/stats/lib.d.ts +2 -0
- package/dist/admin/stats/lib.js.map +1 -1
- package/dist/admin/ui/ConfirmDialog.js +10 -1
- package/dist/admin/ui/ConfirmDialog.js.map +1 -1
- package/dist/admin/ui/Icon.d.ts +34 -0
- package/dist/admin/ui/Icon.js +44 -0
- package/dist/admin/ui/Icon.js.map +1 -0
- package/dist/admin/ui/ImageField.d.ts +15 -0
- package/dist/admin/ui/ImageField.js +67 -0
- package/dist/admin/ui/ImageField.js.map +1 -0
- package/dist/admin/ui/KleurVeld.d.ts +25 -0
- package/dist/admin/ui/KleurVeld.js +76 -0
- package/dist/admin/ui/KleurVeld.js.map +1 -0
- package/dist/admin/ui/LaadStaat.d.ts +21 -0
- package/dist/admin/ui/LaadStaat.js +43 -0
- package/dist/admin/ui/LaadStaat.js.map +1 -0
- package/dist/admin/ui/LeesbaarheidPaneel.d.ts +10 -0
- package/dist/admin/ui/LeesbaarheidPaneel.js +68 -0
- package/dist/admin/ui/LeesbaarheidPaneel.js.map +1 -0
- package/dist/admin/ui/LinkDialog.d.ts +22 -0
- package/dist/admin/ui/LinkDialog.js +131 -0
- package/dist/admin/ui/LinkDialog.js.map +1 -0
- package/dist/admin/ui/LinkVeld.d.ts +14 -0
- package/dist/admin/ui/LinkVeld.js +49 -0
- package/dist/admin/ui/LinkVeld.js.map +1 -0
- package/dist/admin/ui/PreviewLinkResultaat.d.ts +14 -0
- package/dist/admin/ui/PreviewLinkResultaat.js +52 -0
- package/dist/admin/ui/PreviewLinkResultaat.js.map +1 -0
- package/dist/admin/ui/charts.js +5 -5
- package/dist/admin/ui/charts.js.map +1 -1
- package/dist/admin/ui/primitives.d.ts +16 -6
- package/dist/admin/ui/primitives.js +98 -26
- package/dist/admin/ui/primitives.js.map +1 -1
- package/dist/admin/ui/select.js +5 -2
- package/dist/admin/ui/select.js.map +1 -1
- package/dist/admin/ui/switch.d.ts +3 -1
- package/dist/admin/ui/switch.js +6 -3
- package/dist/admin/ui/switch.js.map +1 -1
- package/dist/admin/ui/toast.d.ts +2 -1
- package/dist/admin/ui/toast.js +17 -11
- package/dist/admin/ui/toast.js.map +1 -1
- package/dist/admin/visual/BlokKiezer.d.ts +34 -0
- package/dist/admin/visual/BlokKiezer.js +225 -0
- package/dist/admin/visual/BlokKiezer.js.map +1 -0
- package/dist/admin/visual/InlineGereedschap.d.ts +52 -0
- package/dist/admin/visual/InlineGereedschap.js +356 -0
- package/dist/admin/visual/InlineGereedschap.js.map +1 -0
- package/dist/admin/visual/InlineRijkeTekst.d.ts +23 -0
- package/dist/admin/visual/InlineRijkeTekst.js +152 -0
- package/dist/admin/visual/InlineRijkeTekst.js.map +1 -0
- package/dist/admin/visual/VisualEditor.js +273 -125
- package/dist/admin/visual/VisualEditor.js.map +1 -1
- package/dist/admin.css +1 -1
- package/dist/auth/AuthProvider.js +1 -0
- package/dist/auth/AuthProvider.js.map +1 -1
- package/dist/bedrijf/bedrijf.d.ts +44 -0
- package/dist/bedrijf/bedrijf.js +125 -0
- package/dist/bedrijf/bedrijf.js.map +1 -0
- package/dist/blocks/BlockRenderer.js +11 -1
- package/dist/blocks/BlockRenderer.js.map +1 -1
- package/dist/blocks/blockStyle.d.ts +7 -0
- package/dist/blocks/blockStyle.js +16 -3
- package/dist/blocks/blockStyle.js.map +1 -1
- package/dist/blocks/builtinSchemas.js +6 -0
- package/dist/blocks/builtinSchemas.js.map +1 -1
- package/dist/blocks/builtins/AgendaRender.d.ts +22 -0
- package/dist/blocks/builtins/AgendaRender.js +69 -9
- package/dist/blocks/builtins/AgendaRender.js.map +1 -1
- package/dist/blocks/builtins/Availability.d.ts +1 -0
- package/dist/blocks/builtins/Availability.js +23 -0
- package/dist/blocks/builtins/Availability.js.map +1 -0
- package/dist/blocks/builtins/AvailabilityRender.d.ts +12 -0
- package/dist/blocks/builtins/AvailabilityRender.js +105 -0
- package/dist/blocks/builtins/AvailabilityRender.js.map +1 -0
- package/dist/blocks/builtins/BeforeAfterRender.js +4 -3
- package/dist/blocks/builtins/BeforeAfterRender.js.map +1 -1
- package/dist/blocks/builtins/Booking.js +1 -1
- package/dist/blocks/builtins/Booking.js.map +1 -1
- package/dist/blocks/builtins/BreadcrumbsRender.d.ts +4 -3
- package/dist/blocks/builtins/BreadcrumbsRender.js +14 -8
- package/dist/blocks/builtins/BreadcrumbsRender.js.map +1 -1
- package/dist/blocks/builtins/ColumnsRender.d.ts +1 -1
- package/dist/blocks/builtins/ColumnsRender.js +3 -1
- package/dist/blocks/builtins/ColumnsRender.js.map +1 -1
- package/dist/blocks/builtins/Contact.js +1 -1
- package/dist/blocks/builtins/Contact.js.map +1 -1
- package/dist/blocks/builtins/ContactRender.d.ts +1 -1
- package/dist/blocks/builtins/ContactRender.js +37 -8
- package/dist/blocks/builtins/ContactRender.js.map +1 -1
- package/dist/blocks/builtins/Cta.js +1 -1
- package/dist/blocks/builtins/Cta.js.map +1 -1
- package/dist/blocks/builtins/CtaRender.d.ts +1 -1
- package/dist/blocks/builtins/CtaRender.js +5 -4
- package/dist/blocks/builtins/CtaRender.js.map +1 -1
- package/dist/blocks/builtins/Embed.js +1 -1
- package/dist/blocks/builtins/Embed.js.map +1 -1
- package/dist/blocks/builtins/FormBlock.js +1 -0
- package/dist/blocks/builtins/FormBlock.js.map +1 -1
- package/dist/blocks/builtins/FormBlockRender.d.ts +6 -0
- package/dist/blocks/builtins/FormBlockRender.js +88 -16
- package/dist/blocks/builtins/FormBlockRender.js.map +1 -1
- package/dist/blocks/builtins/GalleryRender.js +3 -2
- package/dist/blocks/builtins/GalleryRender.js.map +1 -1
- package/dist/blocks/builtins/Hero.js +1 -1
- package/dist/blocks/builtins/Hero.js.map +1 -1
- package/dist/blocks/builtins/HeroRender.js +7 -3
- package/dist/blocks/builtins/HeroRender.js.map +1 -1
- package/dist/blocks/builtins/OpenNowBadge.d.ts +4 -2
- package/dist/blocks/builtins/OpenNowBadge.js +6 -6
- package/dist/blocks/builtins/OpenNowBadge.js.map +1 -1
- package/dist/blocks/builtins/OpeningHoursRender.d.ts +8 -2
- package/dist/blocks/builtins/OpeningHoursRender.js +27 -6
- package/dist/blocks/builtins/OpeningHoursRender.js.map +1 -1
- package/dist/blocks/builtins/PriceList.d.ts +1 -0
- package/dist/blocks/builtins/PriceList.js +25 -0
- package/dist/blocks/builtins/PriceList.js.map +1 -0
- package/dist/blocks/builtins/PriceListRender.d.ts +17 -0
- package/dist/blocks/builtins/PriceListRender.js +87 -0
- package/dist/blocks/builtins/PriceListRender.js.map +1 -0
- package/dist/blocks/builtins/RichTextBlockRender.js +6 -1
- package/dist/blocks/builtins/RichTextBlockRender.js.map +1 -1
- package/dist/blocks/builtins/SectionHeading.d.ts +1 -0
- package/dist/blocks/builtins/SectionHeading.js +14 -0
- package/dist/blocks/builtins/SectionHeading.js.map +1 -0
- package/dist/blocks/builtins/SectionHeadingRender.d.ts +7 -0
- package/dist/blocks/builtins/SectionHeadingRender.js +14 -0
- package/dist/blocks/builtins/SectionHeadingRender.js.map +1 -0
- package/dist/blocks/builtins/SliderRender.js +3 -2
- package/dist/blocks/builtins/SliderRender.js.map +1 -1
- package/dist/blocks/builtins/SymbolRef.js +1 -1
- package/dist/blocks/builtins/SymbolRef.js.map +1 -1
- package/dist/blocks/builtins/TeamRender.js +6 -1
- package/dist/blocks/builtins/TeamRender.js.map +1 -1
- package/dist/blocks/builtins/Testimonials.js +3 -1
- package/dist/blocks/builtins/Testimonials.js.map +1 -1
- package/dist/blocks/builtins/TestimonialsRender.js +4 -0
- package/dist/blocks/builtins/TestimonialsRender.js.map +1 -1
- package/dist/blocks/builtins/WhatsappChatRender.d.ts +1 -1
- package/dist/blocks/builtins/WhatsappChatRender.js +3 -1
- package/dist/blocks/builtins/WhatsappChatRender.js.map +1 -1
- package/dist/blocks/builtins/Widgets.js +2 -2
- package/dist/blocks/builtins/Widgets.js.map +1 -1
- package/dist/blocks/builtins/WidgetsRender.js +3 -1
- package/dist/blocks/builtins/WidgetsRender.js.map +1 -1
- package/dist/blocks/builtins/consentEmbed.js +26 -9
- package/dist/blocks/builtins/consentEmbed.js.map +1 -1
- package/dist/blocks/builtins/index.d.ts +3 -0
- package/dist/blocks/builtins/index.js +6 -0
- package/dist/blocks/builtins/index.js.map +1 -1
- package/dist/blocks/builtins/lib/autoComplete.d.ts +7 -0
- package/dist/blocks/builtins/lib/autoComplete.js +20 -0
- package/dist/blocks/builtins/lib/autoComplete.js.map +1 -0
- package/dist/blocks/builtins/lib/bookingUrl.d.ts +2 -2
- package/dist/blocks/builtins/lib/bookingUrl.js +1 -1
- package/dist/blocks/builtins/lib/bookingUrl.js.map +1 -1
- package/dist/blocks/builtins/lib/ical.d.ts +24 -0
- package/dist/blocks/builtins/lib/ical.js +82 -0
- package/dist/blocks/builtins/lib/ical.js.map +1 -0
- package/dist/blocks/builtins/lib/vandaag.d.ts +8 -0
- package/dist/blocks/builtins/lib/vandaag.js +22 -0
- package/dist/blocks/builtins/lib/vandaag.js.map +1 -0
- package/dist/blocks/registry.d.ts +6 -0
- package/dist/blocks/registry.js.map +1 -1
- package/dist/blocks/ui/index.js +1 -0
- package/dist/blocks/ui/index.js.map +1 -1
- package/dist/components/EditBar.js +2 -4
- package/dist/components/EditBar.js.map +1 -1
- package/dist/components/News.js +26 -18
- package/dist/components/News.js.map +1 -1
- package/dist/components/PageView.d.ts +7 -1
- package/dist/components/PageView.js +17 -0
- package/dist/components/PageView.js.map +1 -1
- package/dist/components/ResponsiveImage.js +18 -20
- package/dist/components/ResponsiveImage.js.map +1 -1
- package/dist/components/onderOffset.d.ts +13 -0
- package/dist/components/onderOffset.js +74 -0
- package/dist/components/onderOffset.js.map +1 -0
- package/dist/components/site/Bedrijfsnummers.d.ts +9 -0
- package/dist/components/site/Bedrijfsnummers.js +12 -0
- package/dist/components/site/Bedrijfsnummers.js.map +1 -0
- package/dist/components/site/SiteFooter.d.ts +6 -1
- package/dist/components/site/SiteFooter.js +62 -9
- package/dist/components/site/SiteFooter.js.map +1 -1
- package/dist/components/site/SiteHeader.d.ts +15 -0
- package/dist/components/site/SiteHeader.js +70 -0
- package/dist/components/site/SiteHeader.js.map +1 -0
- package/dist/components/site/SiteNav.d.ts +2 -0
- package/dist/components/site/SiteNav.js +29 -5
- package/dist/components/site/SiteNav.js.map +1 -1
- package/dist/demo/DemoStudio.js +2 -2
- package/dist/demo/DemoStudio.js.map +1 -1
- package/dist/demo/demoStore.d.ts +13 -0
- package/dist/demo/demoStore.js +36 -0
- package/dist/demo/demoStore.js.map +1 -1
- package/dist/editor/EditorProvider.js +15 -4
- package/dist/editor/EditorProvider.js.map +1 -1
- package/dist/firebase/functionsClient.js +5 -1
- package/dist/firebase/functionsClient.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +20 -1
- package/dist/index.js.map +1 -1
- package/dist/media/ImageContext.d.ts +17 -0
- package/dist/media/ImageContext.js +65 -20
- package/dist/media/ImageContext.js.map +1 -1
- package/dist/media/altInHtml.d.ts +9 -0
- package/dist/media/altInHtml.js +17 -0
- package/dist/media/altInHtml.js.map +1 -0
- package/dist/media/srcset.d.ts +12 -0
- package/dist/media/srcset.js +13 -0
- package/dist/media/srcset.js.map +1 -0
- package/dist/modules/builtins.d.ts +1 -0
- package/dist/modules/builtins.js +22 -9
- package/dist/modules/builtins.js.map +1 -1
- package/dist/modules/consent/ConsentBanner.d.ts +5 -0
- package/dist/modules/consent/ConsentBanner.js +8 -7
- package/dist/modules/consent/ConsentBanner.js.map +1 -1
- package/dist/modules/consent/standaard.d.ts +12 -0
- package/dist/modules/consent/standaard.js +10 -0
- package/dist/modules/consent/standaard.js.map +1 -0
- package/dist/modules/consent/useConsent.d.ts +4 -0
- package/dist/modules/consent/useConsent.js +30 -1
- package/dist/modules/consent/useConsent.js.map +1 -1
- package/dist/modules/shop/blocks/Cart.js +5 -3
- package/dist/modules/shop/blocks/Cart.js.map +1 -1
- package/dist/modules/shop/blocks/Checkout.js +56 -35
- package/dist/modules/shop/blocks/Checkout.js.map +1 -1
- package/dist/modules/shop/blocks/Donation.js +4 -4
- package/dist/modules/shop/blocks/Donation.js.map +1 -1
- package/dist/modules/shop/blocks/ProductDetail.js +19 -3
- package/dist/modules/shop/blocks/ProductDetail.js.map +1 -1
- package/dist/modules/shop/blocks/ProductGrid.d.ts +3 -1
- package/dist/modules/shop/blocks/ProductGrid.js +8 -4
- package/dist/modules/shop/blocks/ProductGrid.js.map +1 -1
- package/dist/modules/shop/blocks/ShopCategories.js +4 -3
- package/dist/modules/shop/blocks/ShopCategories.js.map +1 -1
- package/dist/modules/shop/lib.d.ts +4 -0
- package/dist/modules/shop/lib.js +14 -0
- package/dist/modules/shop/lib.js.map +1 -1
- package/dist/modules/shop/settings.d.ts +3 -0
- package/dist/modules/shop/settings.js +14 -0
- package/dist/modules/shop/settings.js.map +1 -1
- package/dist/modules/shop/strings.d.ts +4 -0
- package/dist/modules/shop/strings.js +8 -0
- package/dist/modules/shop/strings.js.map +1 -1
- package/dist/next/envCheck.d.ts +42 -0
- package/dist/next/envCheck.js +106 -0
- package/dist/next/envCheck.js.map +1 -0
- package/dist/next/index.cjs +185 -2
- package/dist/next/index.d.ts +2 -0
- package/dist/next/index.js +8 -1
- package/dist/next/index.js.map +1 -1
- package/dist/next/securityHeaders.d.ts +36 -0
- package/dist/next/securityHeaders.js +74 -0
- package/dist/next/securityHeaders.js.map +1 -0
- package/dist/patterns/builtins.d.ts +7 -0
- package/dist/patterns/builtins.js +138 -3
- package/dist/patterns/builtins.js.map +1 -1
- package/dist/releaseNotes.js +148 -0
- package/dist/releaseNotes.js.map +1 -1
- package/dist/render/PublishedPage.d.ts +2 -1
- package/dist/render/PublishedPage.js +10 -4
- package/dist/render/PublishedPage.js.map +1 -1
- package/dist/schema/blocks.d.ts +473 -0
- package/dist/schema/blocks.js +105 -5
- package/dist/schema/blocks.js.map +1 -1
- package/dist/schema/business.d.ts +118 -0
- package/dist/schema/business.js +75 -0
- package/dist/schema/business.js.map +1 -0
- package/dist/schema/fields.d.ts +2 -1
- package/dist/schema/fields.js.map +1 -1
- package/dist/schema/forms.d.ts +18 -0
- package/dist/schema/forms.js +18 -3
- package/dist/schema/forms.js.map +1 -1
- package/dist/schema/mail.d.ts +10 -0
- package/dist/schema/mail.js +7 -0
- package/dist/schema/mail.js.map +1 -1
- package/dist/schema/settings.d.ts +103 -0
- package/dist/schema/settings.js +24 -3
- package/dist/schema/settings.js.map +1 -1
- package/dist/schema/shop.d.ts +2 -0
- package/dist/schema/shop.js +4 -0
- package/dist/schema/shop.js.map +1 -1
- package/dist/schema/shopGeld.d.ts +1 -0
- package/dist/schema/shopGeld.js +1 -0
- package/dist/schema/shopGeld.js.map +1 -1
- package/dist/schema/siteConfig.d.ts +69 -1
- package/dist/schema/siteConfig.js +7 -2
- package/dist/schema/siteConfig.js.map +1 -1
- package/dist/server/blockData.d.ts +1 -1
- package/dist/server/blockData.js +74 -18
- package/dist/server/blockData.js.map +1 -1
- package/dist/server/cspRapport.d.ts +20 -0
- package/dist/server/cspRapport.js +56 -0
- package/dist/server/cspRapport.js.map +1 -0
- package/dist/server/fonts.d.ts +3 -0
- package/dist/server/fonts.js +39 -0
- package/dist/server/fonts.js.map +1 -0
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.js +8 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/sanitize.js +2 -0
- package/dist/server/sanitize.js.map +1 -1
- package/dist/settings/useSiteSettings.js +4 -1
- package/dist/settings/useSiteSettings.js.map +1 -1
- package/dist/starters/starters.d.ts +69 -0
- package/dist/starters/starters.js +171 -0
- package/dist/starters/starters.js.map +1 -0
- package/dist/styles.css +1181 -212
- package/dist/theme/color.d.ts +10 -0
- package/dist/theme/color.js +17 -1
- package/dist/theme/color.js.map +1 -1
- package/dist/theme/fontFallback.d.ts +23 -0
- package/dist/theme/fontFallback.js +58 -0
- package/dist/theme/fontFallback.js.map +1 -0
- package/dist/theme/fonts.d.ts +11 -0
- package/dist/theme/fonts.js +16 -0
- package/dist/theme/fonts.js.map +1 -0
- package/dist/version.js +1 -1
- package/package.json +5 -4
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
* `@xiwebdesign/xicms-core` bevat hier nooit klantgegevens — die staan in de klantrepo.
|
|
7
7
|
*/
|
|
8
8
|
import * as z from "zod/mini";
|
|
9
|
-
import { type NavItem, type ThemeTokens, type FormsSender } from "./settings.js";
|
|
9
|
+
import { type NavItem, type ThemeTokens, type FormsSender, type HeaderSettings } from "./settings.js";
|
|
10
10
|
import { type Seo } from "./seo.js";
|
|
11
|
+
import { type Business } from "./business.js";
|
|
11
12
|
import type { ModulesSiteConfig } from "../modules/types.js";
|
|
12
13
|
export type SiteMode = "demo" | "live";
|
|
13
14
|
export interface SiteConfig {
|
|
@@ -25,6 +26,17 @@ export interface SiteConfig {
|
|
|
25
26
|
* niets naar Firebase. Alleen bedoeld voor een demo-site; laat dit `false`/weg op echte klantsites.
|
|
26
27
|
*/
|
|
27
28
|
demoEditing?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* 8.3: Google-lettertypen via de eigen site laden (`/api/fonts`), zodat bezoekers geen verbinding
|
|
31
|
+
* met Google maken. Vereist de route `app/api/fonts/route.ts` (`export { fontsGET as GET }`).
|
|
32
|
+
*/
|
|
33
|
+
selfHostFonts?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* 8.8: rustige overgang tussen pagina's (View Transitions API, via React `<ViewTransition>`).
|
|
36
|
+
* "vervagen" = korte crossfade, "schuiven" = fade met een kleine verschuiving. Leeg = geen.
|
|
37
|
+
* Browsers zonder ondersteuning wisselen gewoon direct; `prefers-reduced-motion` zet hem uit.
|
|
38
|
+
*/
|
|
39
|
+
paginaOvergang?: "vervagen" | "schuiven";
|
|
28
40
|
/** Taalcode voor `<html lang>` en a11y (default "nl"). */
|
|
29
41
|
lang?: string;
|
|
30
42
|
/**
|
|
@@ -58,6 +70,13 @@ export interface SiteConfig {
|
|
|
58
70
|
* Afwezig = de standaard van de module (`defaultEnabled`, doorgaans aan).
|
|
59
71
|
*/
|
|
60
72
|
modules?: ModulesSiteConfig;
|
|
73
|
+
/**
|
|
74
|
+
* 8.13: het bedrijfsprofiel als startwaarde (adres, telefoon, openingstijden). Wat de klant in
|
|
75
|
+
* /beheer → Instellingen → Bedrijf opslaat, wint. Zie `schema/business.ts`.
|
|
76
|
+
*/
|
|
77
|
+
business?: Business;
|
|
78
|
+
/** 8.13: startwaarde voor de kop (`SiteHeader`): logo, actieknop, mobiele balk. */
|
|
79
|
+
header?: HeaderSettings;
|
|
61
80
|
}
|
|
62
81
|
/** Helper met type-inferentie voor `site.config.ts`. */
|
|
63
82
|
export declare function defineSiteConfig(config: SiteConfig): SiteConfig;
|
|
@@ -75,6 +94,11 @@ export declare const SiteConfigSchema: z.ZodMiniObject<{
|
|
|
75
94
|
live: "live";
|
|
76
95
|
}>;
|
|
77
96
|
demoEditing: z.ZodMiniOptional<z.ZodMiniBoolean<boolean>>;
|
|
97
|
+
selfHostFonts: z.ZodMiniOptional<z.ZodMiniBoolean<boolean>>;
|
|
98
|
+
paginaOvergang: z.ZodMiniOptional<z.ZodMiniEnum<{
|
|
99
|
+
schuiven: "schuiven";
|
|
100
|
+
vervagen: "vervagen";
|
|
101
|
+
}>>;
|
|
78
102
|
lang: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
79
103
|
locales: z.ZodMiniOptional<z.ZodMiniArray<z.ZodMiniString<string>>>;
|
|
80
104
|
defaultLocale: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
@@ -155,6 +179,50 @@ export declare const SiteConfigSchema: z.ZodMiniObject<{
|
|
|
155
179
|
enabled: z.ZodMiniOptional<z.ZodMiniBoolean<boolean>>;
|
|
156
180
|
config: z.ZodMiniOptional<z.ZodMiniRecord<z.ZodMiniString<string>, z.ZodMiniUnknown>>;
|
|
157
181
|
}, z.core.$strip>]>>>;
|
|
182
|
+
business: z.ZodMiniOptional<z.ZodMiniObject<{
|
|
183
|
+
type: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
184
|
+
name: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
185
|
+
description: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
186
|
+
phone: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
187
|
+
email: z.ZodMiniOptional<z.ZodMiniUnion<readonly [z.ZodMiniEmail, z.ZodMiniLiteral<"">]>>;
|
|
188
|
+
whatsapp: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
189
|
+
address: z.ZodMiniOptional<z.ZodMiniObject<{
|
|
190
|
+
street: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
191
|
+
postalCode: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
192
|
+
city: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
193
|
+
country: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
194
|
+
}, z.core.$strip>>;
|
|
195
|
+
geo: z.ZodMiniOptional<z.ZodMiniObject<{
|
|
196
|
+
lat: z.ZodMiniNumber<number>;
|
|
197
|
+
lng: z.ZodMiniNumber<number>;
|
|
198
|
+
}, z.core.$strip>>;
|
|
199
|
+
hours: z.ZodMiniOptional<z.ZodMiniArray<z.ZodMiniObject<{
|
|
200
|
+
day: z.ZodMiniDefault<z.ZodMiniString<string>>;
|
|
201
|
+
hours: z.ZodMiniDefault<z.ZodMiniString<string>>;
|
|
202
|
+
}, z.core.$strip>>>;
|
|
203
|
+
hoursNote: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
204
|
+
exceptions: z.ZodMiniOptional<z.ZodMiniArray<z.ZodMiniObject<{
|
|
205
|
+
date: z.ZodMiniString<string>;
|
|
206
|
+
until: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
207
|
+
hours: z.ZodMiniDefault<z.ZodMiniString<string>>;
|
|
208
|
+
label: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
209
|
+
}, z.core.$strip>>>;
|
|
210
|
+
priceRange: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
211
|
+
kvk: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
212
|
+
vat: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
213
|
+
reviewUrl: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
214
|
+
sameAs: z.ZodMiniOptional<z.ZodMiniArray<z.ZodMiniString<string>>>;
|
|
215
|
+
}, z.core.$strip>>;
|
|
216
|
+
header: z.ZodMiniOptional<z.ZodMiniObject<{
|
|
217
|
+
logo: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
218
|
+
logoHoogte: z.ZodMiniOptional<z.ZodMiniNumber<number>>;
|
|
219
|
+
cta: z.ZodMiniOptional<z.ZodMiniObject<{
|
|
220
|
+
label: z.ZodMiniString<string>;
|
|
221
|
+
href: z.ZodMiniString<string>;
|
|
222
|
+
}, z.core.$strip>>;
|
|
223
|
+
sticky: z.ZodMiniOptional<z.ZodMiniBoolean<boolean>>;
|
|
224
|
+
mobielBalk: z.ZodMiniOptional<z.ZodMiniBoolean<boolean>>;
|
|
225
|
+
}, z.core.$strip>>;
|
|
158
226
|
}, z.core.$loose>;
|
|
159
227
|
/**
|
|
160
228
|
* Valideert een SiteConfig en geeft leesbare issues terug (leeg = geldig). Wordt in development
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as z from "zod/mini";
|
|
2
|
-
import { NavItemSchema, ThemeSchema, FormsSenderSchema, ModulesSettingsSchema } from "./settings.js";
|
|
2
|
+
import { NavItemSchema, ThemeSchema, FormsSenderSchema, ModulesSettingsSchema, HeaderSettingsSchema } from "./settings.js";
|
|
3
3
|
import { SeoSchema } from "./seo.js";
|
|
4
|
+
import { BusinessSchema } from "./business.js";
|
|
4
5
|
function defineSiteConfig(config) {
|
|
5
6
|
return config;
|
|
6
7
|
}
|
|
@@ -12,6 +13,8 @@ const SiteConfigSchema = z.looseObject({
|
|
|
12
13
|
domain: z.union([z.optional(z.string().check(z.maxLength(255)).check(z.regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/i, "alleen een hostname, bv. 'www.klant.nl' (zonder https:// of pad)"))), z.literal("")]),
|
|
13
14
|
mode: z.enum(["demo", "live"]),
|
|
14
15
|
demoEditing: z.optional(z.boolean()),
|
|
16
|
+
selfHostFonts: z.optional(z.boolean()),
|
|
17
|
+
paginaOvergang: z.optional(z.enum(["vervagen", "schuiven"])),
|
|
15
18
|
lang: z.optional(z.string().check(z.maxLength(10))),
|
|
16
19
|
locales: z.optional(z.array(z.string().check(z.minLength(2)).check(z.maxLength(10))).check(z.maxLength(10))),
|
|
17
20
|
defaultLocale: z.optional(z.string().check(z.minLength(2)).check(z.maxLength(10))),
|
|
@@ -20,7 +23,9 @@ const SiteConfigSchema = z.looseObject({
|
|
|
20
23
|
contactEmail: z.optional(z.email()),
|
|
21
24
|
seoDefaults: z.optional(SeoSchema),
|
|
22
25
|
forms: z.optional(FormsSenderSchema),
|
|
23
|
-
modules: z.optional(ModulesSettingsSchema)
|
|
26
|
+
modules: z.optional(ModulesSettingsSchema),
|
|
27
|
+
business: z.optional(BusinessSchema),
|
|
28
|
+
header: z.optional(HeaderSettingsSchema)
|
|
24
29
|
});
|
|
25
30
|
function validateSiteConfig(config) {
|
|
26
31
|
const res = SiteConfigSchema.safeParse(config);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/schema/siteConfig.ts"],"sourcesContent":["/**\n * SiteConfig — de statische basisconfig in een klantrepo (`content/site.config.ts`).\n * Dit is de fallback/identiteit van de site; bewerkbare instellingen kunnen in Firebase\n * (`sites/{siteId}/settings/site`) staan en deze waarden overschrijven.\n *\n * `@xiwebdesign/xicms-core` bevat hier nooit klantgegevens — die staan in de klantrepo.\n */\nimport * as z from \"zod/mini\";\nimport { NavItemSchema, ThemeSchema, FormsSenderSchema, ModulesSettingsSchema, type NavItem, type ThemeTokens, type FormsSender } from \"./settings\";\nimport { SeoSchema, type Seo } from \"./seo\";\nimport type { ModulesSiteConfig } from \"../modules/types\";\n\nexport type SiteMode = \"demo\" | \"live\";\n\nexport interface SiteConfig {\n /** Uniek tenant-id; bepaalt het pad `sites/{siteId}/...` in Firebase. */\n siteId: string;\n /** Weergavenaam van het bedrijf. */\n name: string;\n /** Productiedomein, bv. \"www.bakkerijjansen.nl\". */\n domain?: string;\n /** \"demo\" toont de lock/CTA; \"live\" is de productiestand. */\n mode: SiteMode;\n /**\n * Demo-bewerkmodus: toont een pseudo-login zodat bezoekers het beheer (teksten + blokken) kunnen\n * uitproberen. Alle \"bewerkingen\" blijven UITSLUITEND lokaal in de browser (localStorage) — er gaat\n * niets naar Firebase. Alleen bedoeld voor een demo-site; laat dit `false`/weg op echte klantsites.\n */\n demoEditing?: boolean;\n /** Taalcode voor `<html lang>` en a11y (default \"nl\"). */\n lang?: string;\n /**\n * Extra talen naast de default (5.0), bv. `[\"de\", \"en\"]`. Vertalingen leven als overlay op\n * pagina's/symbolen; niet-default talen krijgen een URL-prefix (`/de/over-ons`). Weglaten =\n * ééntalige site (geen overhead).\n */\n locales?: string[];\n /** Default-taal van de canonieke content (default: `lang` ?? \"nl\"). */\n defaultLocale?: string;\n /** Huisstijl. */\n theme: ThemeTokens;\n /** Hoofdnavigatie. */\n nav?: NavItem[];\n /** Contact-e-mailadres voor de demo-CTA en formulieren. */\n contactEmail?: string;\n /** Standaard-SEO als een pagina niets opgeeft. */\n seoDefaults?: Seo;\n /** Afzender-instelling voor formulier-e-mail (fallback voor settings/site). */\n forms?: FormsSender;\n /**\n * Modules per site aan/uit zetten en configureren (XiCMS 3.0). Een uitgezette module verbergt\n * zowel zijn blocks als zijn beheer-paneel. Per module kun je ook `config` meegeven (gevalideerd\n * tegen het `settingsSchema` van de module) zodat de agent gedrag instelt zonder core te forken:\n *\n * modules: {\n * gallery: { enabled: true, config: { lightbox: true, columns: 3 } },\n * stats: false,\n * }\n *\n * Afwezig = de standaard van de module (`defaultEnabled`, doorgaans aan).\n */\n modules?: ModulesSiteConfig;\n}\n\n/** Helper met type-inferentie voor `site.config.ts`. */\nexport function defineSiteConfig(config: SiteConfig): SiteConfig {\n return config;\n}\n\n/**\n * Runtime-spiegel van de `SiteConfig`-interface. TypeScript vangt fouten bij compileren, maar een\n * (agent-)geschreven `site.config.ts` kan alsnog ongeldige waarden bevatten (verkeerd siteId-formaat,\n * te lange strings, kapotte nav-items). `.passthrough()` zodat extra klant-velden nooit een fout zijn.\n */\nexport const SiteConfigSchema = z.looseObject({\n siteId: z.string().check(z.minLength(1)).check(z.maxLength(64)).check(z.regex(/^[a-z0-9][a-z0-9-]*$/, \"gebruik kleine letters/cijfers/koppeltekens (bv. 'bakkerij-jansen')\")),\n name: z.string().check(z.minLength(1)).check(z.maxLength(160)),\n // Alleen een hostname (www.klant.nl) — géén protocol/pad: \"https://…/x\" glipte er eerst\n // doorheen en werd maar half genormaliseerd in siteOrigin/buildMetadata.\n domain: z.union([z.optional(z.string().check(z.maxLength(255)).check(z.regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/i, \"alleen een hostname, bv. 'www.klant.nl' (zonder https:// of pad)\"))), z.literal(\"\")]),\n mode: z.enum([\"demo\", \"live\"]),\n demoEditing: z.optional(z.boolean()),\n lang: z.optional(z.string().check(z.maxLength(10))),\n locales: z.optional(z.array(z.string().check(z.minLength(2)).check(z.maxLength(10))).check(z.maxLength(10))),\n defaultLocale: z.optional(z.string().check(z.minLength(2)).check(z.maxLength(10))),\n theme: ThemeSchema,\n nav: z.optional(z.array(NavItemSchema).check(z.maxLength(50))),\n contactEmail: z.optional(z.email()),\n seoDefaults: z.optional(SeoSchema),\n forms: z.optional(FormsSenderSchema),\n modules: z.optional(ModulesSettingsSchema),\n });\n\n/**\n * Valideert een SiteConfig en geeft leesbare issues terug (leeg = geldig). Wordt in development\n * door de provider aangeroepen zodat een agent config-fouten direct in de console ziet, in plaats\n * van een subtiel kapotte site. Gooit bewust niet — een issue mag een site nooit platleggen.\n */\nexport function validateSiteConfig(config: unknown): string[] {\n const res = SiteConfigSchema.safeParse(config);\n if (res.success) return [];\n return res.error.issues.map((i) => `${i.path.join(\".\") || \"(root)\"}: ${i.message}`);\n}\n"],"mappings":"AAOA,YAAY,OAAO;AACnB,SAAS,eAAe,aAAa,mBAAmB,
|
|
1
|
+
{"version":3,"sources":["../../src/schema/siteConfig.ts"],"sourcesContent":["/**\n * SiteConfig — de statische basisconfig in een klantrepo (`content/site.config.ts`).\n * Dit is de fallback/identiteit van de site; bewerkbare instellingen kunnen in Firebase\n * (`sites/{siteId}/settings/site`) staan en deze waarden overschrijven.\n *\n * `@xiwebdesign/xicms-core` bevat hier nooit klantgegevens — die staan in de klantrepo.\n */\nimport * as z from \"zod/mini\";\nimport { NavItemSchema, ThemeSchema, FormsSenderSchema, ModulesSettingsSchema, HeaderSettingsSchema, type NavItem, type ThemeTokens, type FormsSender, type HeaderSettings } from \"./settings\";\nimport { SeoSchema, type Seo } from \"./seo\";\nimport { BusinessSchema, type Business } from \"./business\";\nimport type { ModulesSiteConfig } from \"../modules/types\";\n\nexport type SiteMode = \"demo\" | \"live\";\n\nexport interface SiteConfig {\n /** Uniek tenant-id; bepaalt het pad `sites/{siteId}/...` in Firebase. */\n siteId: string;\n /** Weergavenaam van het bedrijf. */\n name: string;\n /** Productiedomein, bv. \"www.bakkerijjansen.nl\". */\n domain?: string;\n /** \"demo\" toont de lock/CTA; \"live\" is de productiestand. */\n mode: SiteMode;\n /**\n * Demo-bewerkmodus: toont een pseudo-login zodat bezoekers het beheer (teksten + blokken) kunnen\n * uitproberen. Alle \"bewerkingen\" blijven UITSLUITEND lokaal in de browser (localStorage) — er gaat\n * niets naar Firebase. Alleen bedoeld voor een demo-site; laat dit `false`/weg op echte klantsites.\n */\n demoEditing?: boolean;\n /**\n * 8.3: Google-lettertypen via de eigen site laden (`/api/fonts`), zodat bezoekers geen verbinding\n * met Google maken. Vereist de route `app/api/fonts/route.ts` (`export { fontsGET as GET }`).\n */\n selfHostFonts?: boolean;\n /**\n * 8.8: rustige overgang tussen pagina's (View Transitions API, via React `<ViewTransition>`).\n * \"vervagen\" = korte crossfade, \"schuiven\" = fade met een kleine verschuiving. Leeg = geen.\n * Browsers zonder ondersteuning wisselen gewoon direct; `prefers-reduced-motion` zet hem uit.\n */\n paginaOvergang?: \"vervagen\" | \"schuiven\";\n /** Taalcode voor `<html lang>` en a11y (default \"nl\"). */\n lang?: string;\n /**\n * Extra talen naast de default (5.0), bv. `[\"de\", \"en\"]`. Vertalingen leven als overlay op\n * pagina's/symbolen; niet-default talen krijgen een URL-prefix (`/de/over-ons`). Weglaten =\n * ééntalige site (geen overhead).\n */\n locales?: string[];\n /** Default-taal van de canonieke content (default: `lang` ?? \"nl\"). */\n defaultLocale?: string;\n /** Huisstijl. */\n theme: ThemeTokens;\n /** Hoofdnavigatie. */\n nav?: NavItem[];\n /** Contact-e-mailadres voor de demo-CTA en formulieren. */\n contactEmail?: string;\n /** Standaard-SEO als een pagina niets opgeeft. */\n seoDefaults?: Seo;\n /** Afzender-instelling voor formulier-e-mail (fallback voor settings/site). */\n forms?: FormsSender;\n /**\n * Modules per site aan/uit zetten en configureren (XiCMS 3.0). Een uitgezette module verbergt\n * zowel zijn blocks als zijn beheer-paneel. Per module kun je ook `config` meegeven (gevalideerd\n * tegen het `settingsSchema` van de module) zodat de agent gedrag instelt zonder core te forken:\n *\n * modules: {\n * gallery: { enabled: true, config: { lightbox: true, columns: 3 } },\n * stats: false,\n * }\n *\n * Afwezig = de standaard van de module (`defaultEnabled`, doorgaans aan).\n */\n modules?: ModulesSiteConfig;\n /**\n * 8.13: het bedrijfsprofiel als startwaarde (adres, telefoon, openingstijden). Wat de klant in\n * /beheer → Instellingen → Bedrijf opslaat, wint. Zie `schema/business.ts`.\n */\n business?: Business;\n /** 8.13: startwaarde voor de kop (`SiteHeader`): logo, actieknop, mobiele balk. */\n header?: HeaderSettings;\n}\n\n/** Helper met type-inferentie voor `site.config.ts`. */\nexport function defineSiteConfig(config: SiteConfig): SiteConfig {\n return config;\n}\n\n/**\n * Runtime-spiegel van de `SiteConfig`-interface. TypeScript vangt fouten bij compileren, maar een\n * (agent-)geschreven `site.config.ts` kan alsnog ongeldige waarden bevatten (verkeerd siteId-formaat,\n * te lange strings, kapotte nav-items). `.passthrough()` zodat extra klant-velden nooit een fout zijn.\n */\nexport const SiteConfigSchema = z.looseObject({\n siteId: z.string().check(z.minLength(1)).check(z.maxLength(64)).check(z.regex(/^[a-z0-9][a-z0-9-]*$/, \"gebruik kleine letters/cijfers/koppeltekens (bv. 'bakkerij-jansen')\")),\n name: z.string().check(z.minLength(1)).check(z.maxLength(160)),\n // Alleen een hostname (www.klant.nl) — géén protocol/pad: \"https://…/x\" glipte er eerst\n // doorheen en werd maar half genormaliseerd in siteOrigin/buildMetadata.\n domain: z.union([z.optional(z.string().check(z.maxLength(255)).check(z.regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/i, \"alleen een hostname, bv. 'www.klant.nl' (zonder https:// of pad)\"))), z.literal(\"\")]),\n mode: z.enum([\"demo\", \"live\"]),\n demoEditing: z.optional(z.boolean()),\n selfHostFonts: z.optional(z.boolean()),\n paginaOvergang: z.optional(z.enum([\"vervagen\", \"schuiven\"])),\n lang: z.optional(z.string().check(z.maxLength(10))),\n locales: z.optional(z.array(z.string().check(z.minLength(2)).check(z.maxLength(10))).check(z.maxLength(10))),\n defaultLocale: z.optional(z.string().check(z.minLength(2)).check(z.maxLength(10))),\n theme: ThemeSchema,\n nav: z.optional(z.array(NavItemSchema).check(z.maxLength(50))),\n contactEmail: z.optional(z.email()),\n seoDefaults: z.optional(SeoSchema),\n forms: z.optional(FormsSenderSchema),\n modules: z.optional(ModulesSettingsSchema),\n business: z.optional(BusinessSchema),\n header: z.optional(HeaderSettingsSchema),\n });\n\n/**\n * Valideert een SiteConfig en geeft leesbare issues terug (leeg = geldig). Wordt in development\n * door de provider aangeroepen zodat een agent config-fouten direct in de console ziet, in plaats\n * van een subtiel kapotte site. Gooit bewust niet — een issue mag een site nooit platleggen.\n */\nexport function validateSiteConfig(config: unknown): string[] {\n const res = SiteConfigSchema.safeParse(config);\n if (res.success) return [];\n return res.error.issues.map((i) => `${i.path.join(\".\") || \"(root)\"}: ${i.message}`);\n}\n"],"mappings":"AAOA,YAAY,OAAO;AACnB,SAAS,eAAe,aAAa,mBAAmB,uBAAuB,4BAAmG;AAClL,SAAS,iBAA2B;AACpC,SAAS,sBAAqC;AA0EvC,SAAS,iBAAiB,QAAgC;AAC/D,SAAO;AACT;AAOO,MAAM,mBAAmB,EAAE,YAAY;AAAA,EAC1C,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,wBAAwB,qEAAqE,CAAC;AAAA,EAC5K,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,UAAU,GAAG,CAAC;AAAA;AAAA;AAAA,EAG7D,QAAQ,EAAE,MAAM,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,GAAG,CAAC,EAAE,MAAM,EAAE,MAAM,sEAAsE,kEAAkE,CAAC,CAAC,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC;AAAA,EACxO,MAAM,EAAE,KAAK,CAAC,QAAQ,MAAM,CAAC;AAAA,EAC7B,aAAa,EAAE,SAAS,EAAE,QAAQ,CAAC;AAAA,EACnC,eAAe,EAAE,SAAS,EAAE,QAAQ,CAAC;AAAA,EACrC,gBAAgB,EAAE,SAAS,EAAE,KAAK,CAAC,YAAY,UAAU,CAAC,CAAC;AAAA,EAC3D,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;AAAA,EAClD,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;AAAA,EAC3G,eAAe,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;AAAA,EACjF,OAAO;AAAA,EACP,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;AAAA,EAC7D,cAAc,EAAE,SAAS,EAAE,MAAM,CAAC;AAAA,EAClC,aAAa,EAAE,SAAS,SAAS;AAAA,EACjC,OAAO,EAAE,SAAS,iBAAiB;AAAA,EACnC,SAAS,EAAE,SAAS,qBAAqB;AAAA,EACzC,UAAU,EAAE,SAAS,cAAc;AAAA,EACnC,QAAQ,EAAE,SAAS,oBAAoB;AACzC,CAAC;AAOI,SAAS,mBAAmB,QAA2B;AAC5D,QAAM,MAAM,iBAAiB,UAAU,MAAM;AAC7C,MAAI,IAAI,QAAS,QAAO,CAAC;AACzB,SAAO,IAAI,MAAM,OAAO,IAAI,CAAC,MAAM,GAAG,EAAE,KAAK,KAAK,GAAG,KAAK,QAAQ,KAAK,EAAE,OAAO,EAAE;AACpF;","names":[]}
|
|
@@ -14,4 +14,4 @@ export interface PageBlockDataOptions {
|
|
|
14
14
|
* Geef het resultaat door als `<PublishedPage blockData={...}>`. `undefined`-namespaces blijven
|
|
15
15
|
* weg zodat de blokken hun eigen fallback houden waar deze functie niets voor verzamelde.
|
|
16
16
|
*/
|
|
17
|
-
export declare function getPageBlockData(siteId: string, page: Pick<Page, "blocks"
|
|
17
|
+
export declare function getPageBlockData(siteId: string, page: Pick<Page, "blocks"> & Partial<Pick<Page, "path">>, opts?: PageBlockDataOptions): Promise<Record<string, unknown> | undefined>;
|
package/dist/server/blockData.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import "server-only";
|
|
2
2
|
import { listPublishedPages, listPublishedNews, listFormDefinitions, listSiteMedia } from "./pages.js";
|
|
3
3
|
import { blocksToText } from "../blocks/searchText.js";
|
|
4
|
+
import { parseIcalBezet, veiligeIcalUrl } from "../blocks/builtins/lib/ical.js";
|
|
4
5
|
const SHOP_TYPES = /* @__PURE__ */ new Set(["productGrid", "shopCategories", "productDetail", "cart", "checkout", "donation", "orderStatus"]);
|
|
5
6
|
const NEWS_TYPES = /* @__PURE__ */ new Set(["newsList", "newsWidget"]);
|
|
6
7
|
function collectTypes(v, out, depth) {
|
|
@@ -23,6 +24,9 @@ function collectFormIds(v, out, depth) {
|
|
|
23
24
|
if (o._type === "form" && typeof o.formId === "string" && o.formId) out.add(o.formId);
|
|
24
25
|
for (const val of Object.values(o)) collectFormIds(val, out, depth + 1);
|
|
25
26
|
}
|
|
27
|
+
function heeftOpslagUrl(tekst) {
|
|
28
|
+
return tekst.includes("firebasestorage") || tekst.includes("storage.googleapis");
|
|
29
|
+
}
|
|
26
30
|
function searchWantsNews(v, depth) {
|
|
27
31
|
if (depth > 6 || v == null || typeof v !== "object") return false;
|
|
28
32
|
if (Array.isArray(v)) return v.some((item) => searchWantsNews(item, depth + 1));
|
|
@@ -30,15 +34,45 @@ function searchWantsNews(v, depth) {
|
|
|
30
34
|
if (o._type === "search" && o.includeNews === true) return true;
|
|
31
35
|
return Object.values(o).some((val) => searchWantsNews(val, depth + 1));
|
|
32
36
|
}
|
|
37
|
+
function collectIcalUrls(v, out, depth) {
|
|
38
|
+
if (depth > 6 || v == null || typeof v !== "object") return;
|
|
39
|
+
if (Array.isArray(v)) return v.forEach((x) => collectIcalUrls(x, out, depth + 1));
|
|
40
|
+
const o = v;
|
|
41
|
+
if (o._type === "availability" && Array.isArray(o.units)) {
|
|
42
|
+
for (const u of o.units) if (typeof u?.icalUrl === "string" && u.icalUrl.trim()) out.add(u.icalUrl.trim());
|
|
43
|
+
}
|
|
44
|
+
Object.values(o).forEach((x) => collectIcalUrls(x, out, depth + 1));
|
|
45
|
+
}
|
|
46
|
+
async function haalBezetting(url) {
|
|
47
|
+
if (!veiligeIcalUrl(url)) return null;
|
|
48
|
+
try {
|
|
49
|
+
const res = await fetch(url.replace(/^webcal:\/\//i, "https://"), {
|
|
50
|
+
signal: AbortSignal.timeout(6e3),
|
|
51
|
+
headers: { accept: "text/calendar, text/plain;q=0.8" },
|
|
52
|
+
next: { revalidate: 3600 }
|
|
53
|
+
});
|
|
54
|
+
if (!res.ok) return null;
|
|
55
|
+
const tekst = await res.text();
|
|
56
|
+
if (tekst.length > 2e6 || !tekst.includes("BEGIN:VCALENDAR")) return null;
|
|
57
|
+
return parseIcalBezet(tekst);
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
33
62
|
async function getPageBlockData(siteId, page, opts) {
|
|
34
63
|
const types = /* @__PURE__ */ new Set();
|
|
35
64
|
collectTypes(page.blocks ?? [], types, 0);
|
|
36
65
|
const wilShop = [...SHOP_TYPES].some((t) => types.has(t));
|
|
37
66
|
const wilNews = [...NEWS_TYPES].some((t) => types.has(t));
|
|
38
67
|
const wilSearch = types.has("search");
|
|
68
|
+
const wilKruimels = types.has("breadcrumbs") && typeof page.path === "string";
|
|
39
69
|
const formIds = /* @__PURE__ */ new Set();
|
|
40
70
|
collectFormIds(page.blocks ?? [], formIds, 0);
|
|
41
|
-
|
|
71
|
+
const icalUrls = /* @__PURE__ */ new Set();
|
|
72
|
+
collectIcalUrls(page.blocks ?? [], icalUrls, 0);
|
|
73
|
+
const blokTekst = JSON.stringify(page.blocks ?? []);
|
|
74
|
+
const wilMedia = heeftOpslagUrl(blokTekst) || wilShop || wilNews;
|
|
75
|
+
if (!wilShop && !wilNews && !wilSearch && formIds.size === 0 && !wilMedia && !wilKruimels && icalUrls.size === 0) return void 0;
|
|
42
76
|
const data = {};
|
|
43
77
|
const zacht = async (naam, fn) => {
|
|
44
78
|
try {
|
|
@@ -53,6 +87,14 @@ async function getPageBlockData(siteId, page, opts) {
|
|
|
53
87
|
const { getShopPageData } = await import("./shop.js");
|
|
54
88
|
data.shop = await getShopPageData(siteId, opts?.searchParams);
|
|
55
89
|
}),
|
|
90
|
+
zacht("beschikbaarheid", async () => {
|
|
91
|
+
if (icalUrls.size === 0) return;
|
|
92
|
+
const map = {};
|
|
93
|
+
await Promise.all([...icalUrls].slice(0, 10).map(async (u) => {
|
|
94
|
+
map[u] = await haalBezetting(u);
|
|
95
|
+
}));
|
|
96
|
+
data.beschikbaarheid = map;
|
|
97
|
+
}),
|
|
56
98
|
zacht("news", async () => {
|
|
57
99
|
if (!wilNews) return;
|
|
58
100
|
data.news = await listPublishedNews(siteId);
|
|
@@ -61,25 +103,18 @@ async function getPageBlockData(siteId, page, opts) {
|
|
|
61
103
|
if (formIds.size === 0) return;
|
|
62
104
|
const alle = await listFormDefinitions(siteId);
|
|
63
105
|
const map = {};
|
|
106
|
+
for (const id of formIds) map[id] = null;
|
|
64
107
|
for (const f of alle) if (formIds.has(f.id)) map[f.id] = f;
|
|
65
108
|
data.forms = map;
|
|
66
109
|
}),
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const focal = m.focal;
|
|
76
|
-
const heeftFocal = typeof focal?.x === "number" && typeof focal?.y === "number";
|
|
77
|
-
if (!url || m.status === "processing" || !varianten?.sizes?.length && !heeftFocal) continue;
|
|
78
|
-
if (blokTekst.includes(url)) {
|
|
79
|
-
map[url] = { variants: m.variants, width: m.width, height: m.height, ...heeftFocal ? { focal } : {} };
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
if (Object.keys(map).length > 0) data.media = map;
|
|
110
|
+
// 8.3: het kruimelpad server-side, met paginatitels. Eerst rendeerde het pas na mount (niet in
|
|
111
|
+
// de HTML, layout-verschuiving, JSON-LD alleen client-side) en met labels uit het pad.
|
|
112
|
+
zacht("breadcrumbs", async () => {
|
|
113
|
+
if (!wilKruimels) return;
|
|
114
|
+
const pages = await listPublishedPages(siteId);
|
|
115
|
+
const titels = {};
|
|
116
|
+
for (const p of pages) if (typeof p.path === "string" && p.title) titels[p.path] = String(p.title);
|
|
117
|
+
data.breadcrumbs = { path: page.path, titels };
|
|
83
118
|
}),
|
|
84
119
|
zacht("search", async () => {
|
|
85
120
|
if (!wilSearch) return;
|
|
@@ -102,7 +137,28 @@ async function getPageBlockData(siteId, page, opts) {
|
|
|
102
137
|
data.searchIndex = index;
|
|
103
138
|
})
|
|
104
139
|
]);
|
|
105
|
-
|
|
140
|
+
await zacht("media", async () => {
|
|
141
|
+
const corpus = `${blokTekst}
|
|
142
|
+
${data.shop ? JSON.stringify(data.shop) : ""}
|
|
143
|
+
${data.news ? JSON.stringify(data.news) : ""}`;
|
|
144
|
+
if (!heeftOpslagUrl(corpus)) return;
|
|
145
|
+
const alle = await listSiteMedia(siteId);
|
|
146
|
+
const map = {};
|
|
147
|
+
for (const m of alle) {
|
|
148
|
+
const url = typeof m.url === "string" ? m.url : "";
|
|
149
|
+
const varianten = m.variants;
|
|
150
|
+
const focal = m.focal;
|
|
151
|
+
const heeftFocal = typeof focal?.x === "number" && typeof focal?.y === "number";
|
|
152
|
+
const heeftAlt = typeof m.alt === "string" && m.alt.trim() !== "";
|
|
153
|
+
if (!url || m.status === "processing" || !varianten?.sizes?.length && !heeftFocal && !heeftAlt) continue;
|
|
154
|
+
if (corpus.includes(url) || corpus.includes(url.replace(/&/g, "&"))) {
|
|
155
|
+
const alt = typeof m.alt === "string" && m.alt.trim() ? m.alt.trim() : void 0;
|
|
156
|
+
map[url] = { variants: m.variants, width: m.width, height: m.height, ...heeftFocal ? { focal } : {}, ...alt ? { alt } : {} };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (Object.keys(map).length > 0) data.media = map;
|
|
160
|
+
});
|
|
161
|
+
return Object.keys(data).length > 0 ? data : void 0;
|
|
106
162
|
}
|
|
107
163
|
export {
|
|
108
164
|
getPageBlockData
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/server/blockData.ts"],"sourcesContent":["import \"server-only\";\n\nimport type { Page } from \"../schema/pages\";\nimport { listPublishedPages, listPublishedNews, listFormDefinitions, listSiteMedia } from \"./pages\";\nimport { blocksToText, type SearchEntry } from \"../blocks/searchText\";\n\n/**\n * SSR-seeds voor blokken die data buiten de pagina zelf nodig hebben (7.12).\n *\n * Het duurste patroon in XiCMS was niet een gemiste servercache maar de BROWSER: blokken als de\n * productgrid, het zoekblok en het formulier haalden hun data client-side uit Firestore — per\n * bezoeker, buiten elke cache om. Eén winkelpagina kostte zo honderden reads per bezoeker, en het\n * zoekblok las de complete pagina-collectie. De server-readers bestonden (server/shop.ts), maar\n * elke site moest er zelf aan denken ze aan te roepen — en geen enkele deed dat. SITE-AUDIT noemt\n * ontbrekende SSR-seeds \"de #1 prestatieval\"; dit bestand maakt die val structureel onmogelijk.\n *\n * `getPageBlockData` kijkt naar de blóktypes van de pagina en verzamelt precies de data die deze\n * pagina nodig heeft — niets meer. Een pagina zonder shop-blokken doet nul product-reads; een\n * pagina zonder zoekblok bouwt geen index. Alle onderliggende readers zitten in de tagged\n * servercache (server/cached.ts), dus dit kost Firestore éénmaal per interval, niet per bezoeker.\n *\n * Gebruik in de catch-all van de klantsite (ná symbol-expansie, zodat blokken ín symbolen meetellen):\n *\n * const page = await getPublishedPageByPathExpanded(siteId, path, locale);\n * const blockData = page ? await getPageBlockData(siteId, page, { searchParams }) : undefined;\n * <PublishedPage page={page} siteId={siteId} blockData={blockData} />\n *\n * De blokken zelf houden hun client-fallback: een site die dit (nog) niet doorgeeft blijft werken,\n * alleen duurder en trager — precies het oude gedrag.\n */\n\n/** Bloktypes die de shop-seed nodig hebben (productDetail leest daarnaast `?p=` voor het product). */\nconst SHOP_TYPES = new Set([\"productGrid\", \"shopCategories\", \"productDetail\", \"cart\", \"checkout\", \"donation\", \"orderStatus\"]);\n/** Bloktypes die de nieuws-seed nodig hebben. */\nconst NEWS_TYPES = new Set([\"newsList\", \"newsWidget\"]);\n\n/** Verzamel alle `_type`-waarden, recursief — kolommen/tabs/secties kunnen blokken bevatten. */\nfunction collectTypes(v: unknown, out: Set<string>, depth: number): void {\n if (depth > 6 || v == null || typeof v !== \"object\") return;\n if (Array.isArray(v)) {\n for (const item of v) collectTypes(item, out, depth + 1);\n return;\n }\n const o = v as Record<string, unknown>;\n if (typeof o._type === \"string\") out.add(o._type);\n for (const val of Object.values(o)) collectTypes(val, out, depth + 1);\n}\n\n/** Verzamel de `formId`s van alle formulierblokken (ook genest). */\nfunction collectFormIds(v: unknown, out: Set<string>, depth: number): void {\n if (depth > 6 || v == null || typeof v !== \"object\") return;\n if (Array.isArray(v)) {\n for (const item of v) collectFormIds(item, out, depth + 1);\n return;\n }\n const o = v as Record<string, unknown>;\n if (o._type === \"form\" && typeof o.formId === \"string\" && o.formId) out.add(o.formId);\n for (const val of Object.values(o)) collectFormIds(val, out, depth + 1);\n}\n\n/** Wil één van de zoekblokken op de pagina ook nieuws doorzoeken? */\nfunction searchWantsNews(v: unknown, depth: number): boolean {\n if (depth > 6 || v == null || typeof v !== \"object\") return false;\n if (Array.isArray(v)) return v.some((item) => searchWantsNews(item, depth + 1));\n const o = v as Record<string, unknown>;\n if (o._type === \"search\" && o.includeNews === true) return true;\n return Object.values(o).some((val) => searchWantsNews(val, depth + 1));\n}\n\nexport interface PageBlockDataOptions {\n /** `searchParams` van de route — alleen nodig voor productdetailpagina's (`?p=<slug>`). */\n searchParams?: { p?: string | string[]; [k: string]: unknown };\n /** De taal van de render — de zoekindex indexeert dan de vertaalde paden. (Nog ongebruikt.) */\n locale?: string;\n}\n\n/**\n * Server-side blockData voor één pagina, op basis van zijn bloktypes.\n * Geef het resultaat door als `<PublishedPage blockData={...}>`. `undefined`-namespaces blijven\n * weg zodat de blokken hun eigen fallback houden waar deze functie niets voor verzamelde.\n */\nexport async function getPageBlockData(\n siteId: string,\n page: Pick<Page, \"blocks\">,\n opts?: PageBlockDataOptions,\n): Promise<Record<string, unknown> | undefined> {\n const types = new Set<string>();\n collectTypes(page.blocks ?? [], types, 0);\n\n const wilShop = [...SHOP_TYPES].some((t) => types.has(t));\n const wilNews = [...NEWS_TYPES].some((t) => types.has(t));\n const wilSearch = types.has(\"search\");\n const formIds = new Set<string>();\n collectFormIds(page.blocks ?? [], formIds, 0);\n\n if (!wilShop && !wilNews && !wilSearch && formIds.size === 0) return undefined;\n\n const data: Record<string, unknown> = {};\n\n // Elke seed faalt ZACHT: bij een fout blijft die namespace weg en neemt de client-fallback van\n // het blok het over — hetzelfde gedrag als een site die (nog) geen seeds doorgeeft. De readers\n // gooien bewust bij een hik (uitkomsten worden gecached, fouten niet — zie server/cached.ts),\n // dus hier opvangen is wat \"één mislukte read\" scheidt van \"één mislukte pagina\".\n const zacht = async (naam: string, fn: () => Promise<void>) => {\n try {\n await fn();\n } catch (e) {\n console.warn(`[xicms] blockData-seed \"${naam}\" overgeslagen:`, (e as Error)?.message ?? e);\n }\n };\n\n // Parallel: elke seed leest uit de tagged servercache, dus dit zijn meestal nul echte reads.\n await Promise.all([\n zacht(\"shop\", async () => {\n if (!wilShop) return;\n // Dynamische import: shop-servercode alleen in de graph van pagina's/sites die hem gebruiken.\n const { getShopPageData } = await import(\"./shop\");\n data.shop = await getShopPageData(siteId, opts?.searchParams);\n }),\n zacht(\"news\", async () => {\n if (!wilNews) return;\n data.news = await listPublishedNews(siteId);\n }),\n zacht(\"forms\", async () => {\n if (formIds.size === 0) return;\n const alle = await listFormDefinitions(siteId);\n const map: Record<string, unknown> = {};\n for (const f of alle) if (formIds.has(f.id)) map[f.id] = f;\n data.forms = map;\n }),\n zacht(\"media\", async () => {\n // Beeld-ladder-seed: alleen de media-docs waarvan de URL werkelijk op deze pagina staat,\n // en alleen als de pipeline varianten heeft gemaakt. Eén stringify-scan is goedkoper en\n // robuuster dan per bloktype weten welke velden afbeeldingen zijn.\n const blokTekst = JSON.stringify(page.blocks ?? []);\n if (!blokTekst.includes(\"firebasestorage\") && !blokTekst.includes(\"storage.googleapis\")) return;\n const alle = await listSiteMedia(siteId);\n const map: Record<string, unknown> = {};\n for (const m of alle) {\n const url = typeof m.url === \"string\" ? m.url : \"\";\n const varianten = m.variants as { sizes?: unknown[] } | undefined;\n // 7.24: ook een foto zónder ladder maar mét focuspunt gaat mee — anders zou een focuspunt\n // op een oudere upload (van vóór de pipeline) nooit aankomen.\n const focal = m.focal as { x?: unknown; y?: unknown } | undefined;\n const heeftFocal = typeof focal?.x === \"number\" && typeof focal?.y === \"number\";\n if (!url || m.status === \"processing\" || (!varianten?.sizes?.length && !heeftFocal)) continue;\n if (blokTekst.includes(url)) {\n map[url] = { variants: m.variants, width: m.width, height: m.height, ...(heeftFocal ? { focal } : {}) };\n }\n }\n if (Object.keys(map).length > 0) data.media = map;\n }),\n zacht(\"search\", async () => {\n if (!wilSearch) return;\n const [pages, news] = await Promise.all([\n listPublishedPages(siteId),\n searchWantsNews(page.blocks ?? [], 0) ? listPublishedNews(siteId) : Promise.resolve([]),\n ]);\n const index: SearchEntry[] = pages.map((p) => ({\n title: String(p.title ?? \"\"),\n path: String(p.path ?? \"/\"),\n text: blocksToText(p.blocks),\n }));\n for (const n of news) {\n index.push({\n title: n.title,\n path: `/nieuws/${n.slug || n.id}`,\n text: `${n.excerpt ?? \"\"} ${(n.bodyHtml ?? n.body ?? \"\").replace(/<[^>]*>/g, \" \")}`,\n });\n }\n data.searchIndex = index;\n }),\n ]);\n\n return data;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/server/blockData.ts"],"sourcesContent":["import \"server-only\";\n\nimport type { Page } from \"../schema/pages\";\nimport { listPublishedPages, listPublishedNews, listFormDefinitions, listSiteMedia } from \"./pages\";\nimport { blocksToText, type SearchEntry } from \"../blocks/searchText\";\nimport { parseIcalBezet, veiligeIcalUrl, type Bezet } from \"../blocks/builtins/lib/ical\";\n\n/**\n * SSR-seeds voor blokken die data buiten de pagina zelf nodig hebben (7.12).\n *\n * Het duurste patroon in XiCMS was niet een gemiste servercache maar de BROWSER: blokken als de\n * productgrid, het zoekblok en het formulier haalden hun data client-side uit Firestore — per\n * bezoeker, buiten elke cache om. Eén winkelpagina kostte zo honderden reads per bezoeker, en het\n * zoekblok las de complete pagina-collectie. De server-readers bestonden (server/shop.ts), maar\n * elke site moest er zelf aan denken ze aan te roepen — en geen enkele deed dat. SITE-AUDIT noemt\n * ontbrekende SSR-seeds \"de #1 prestatieval\"; dit bestand maakt die val structureel onmogelijk.\n *\n * `getPageBlockData` kijkt naar de blóktypes van de pagina en verzamelt precies de data die deze\n * pagina nodig heeft — niets meer. Een pagina zonder shop-blokken doet nul product-reads; een\n * pagina zonder zoekblok bouwt geen index. Alle onderliggende readers zitten in de tagged\n * servercache (server/cached.ts), dus dit kost Firestore éénmaal per interval, niet per bezoeker.\n *\n * Gebruik in de catch-all van de klantsite (ná symbol-expansie, zodat blokken ín symbolen meetellen):\n *\n * const page = await getPublishedPageByPathExpanded(siteId, path, locale);\n * const blockData = page ? await getPageBlockData(siteId, page, { searchParams }) : undefined;\n * <PublishedPage page={page} siteId={siteId} blockData={blockData} />\n *\n * De blokken zelf houden hun client-fallback: een site die dit (nog) niet doorgeeft blijft werken,\n * alleen duurder en trager — precies het oude gedrag.\n */\n\n/** Bloktypes die de shop-seed nodig hebben (productDetail leest daarnaast `?p=` voor het product). */\nconst SHOP_TYPES = new Set([\"productGrid\", \"shopCategories\", \"productDetail\", \"cart\", \"checkout\", \"donation\", \"orderStatus\"]);\n/** Bloktypes die de nieuws-seed nodig hebben. */\nconst NEWS_TYPES = new Set([\"newsList\", \"newsWidget\"]);\n\n/** Verzamel alle `_type`-waarden, recursief — kolommen/tabs/secties kunnen blokken bevatten. */\nfunction collectTypes(v: unknown, out: Set<string>, depth: number): void {\n if (depth > 6 || v == null || typeof v !== \"object\") return;\n if (Array.isArray(v)) {\n for (const item of v) collectTypes(item, out, depth + 1);\n return;\n }\n const o = v as Record<string, unknown>;\n if (typeof o._type === \"string\") out.add(o._type);\n for (const val of Object.values(o)) collectTypes(val, out, depth + 1);\n}\n\n/** Verzamel de `formId`s van alle formulierblokken (ook genest). */\nfunction collectFormIds(v: unknown, out: Set<string>, depth: number): void {\n if (depth > 6 || v == null || typeof v !== \"object\") return;\n if (Array.isArray(v)) {\n for (const item of v) collectFormIds(item, out, depth + 1);\n return;\n }\n const o = v as Record<string, unknown>;\n if (o._type === \"form\" && typeof o.formId === \"string\" && o.formId) out.add(o.formId);\n for (const val of Object.values(o)) collectFormIds(val, out, depth + 1);\n}\n\n/** Staat er een eigen (Firebase Storage-)beeld in deze tekst? */\nfunction heeftOpslagUrl(tekst: string): boolean {\n return tekst.includes(\"firebasestorage\") || tekst.includes(\"storage.googleapis\");\n}\n\n/** Wil één van de zoekblokken op de pagina ook nieuws doorzoeken? */\nfunction searchWantsNews(v: unknown, depth: number): boolean {\n if (depth > 6 || v == null || typeof v !== \"object\") return false;\n if (Array.isArray(v)) return v.some((item) => searchWantsNews(item, depth + 1));\n const o = v as Record<string, unknown>;\n if (o._type === \"search\" && o.includeNews === true) return true;\n return Object.values(o).some((val) => searchWantsNews(val, depth + 1));\n}\n\n/** iCal-links van beschikbaarheidsblokken op de pagina (8.15). */\nfunction collectIcalUrls(v: unknown, out: Set<string>, depth: number): void {\n if (depth > 6 || v == null || typeof v !== \"object\") return;\n if (Array.isArray(v)) return v.forEach((x) => collectIcalUrls(x, out, depth + 1));\n const o = v as Record<string, unknown>;\n if (o._type === \"availability\" && Array.isArray(o.units)) {\n for (const u of o.units as { icalUrl?: unknown }[]) if (typeof u?.icalUrl === \"string\" && u.icalUrl.trim()) out.add(u.icalUrl.trim());\n }\n Object.values(o).forEach((x) => collectIcalUrls(x, out, depth + 1));\n}\n\n/**\n * Eén iCal-agenda ophalen (8.15): alleen https naar een gewone hostnaam, max 6 s en 2 MB, en een\n * uur gecachet (Next-fetchcache). Een externe agenda die hapert mag de pagina niet laten hangen;\n * bij een fout geeft de seed `null` en toont de kalender een eerlijke melding.\n */\nasync function haalBezetting(url: string): Promise<Bezet[] | null> {\n if (!veiligeIcalUrl(url)) return null;\n try {\n const res = await fetch(url.replace(/^webcal:\\/\\//i, \"https://\"), {\n signal: AbortSignal.timeout(6000),\n headers: { accept: \"text/calendar, text/plain;q=0.8\" },\n next: { revalidate: 3600 },\n } as RequestInit);\n if (!res.ok) return null;\n const tekst = await res.text();\n if (tekst.length > 2_000_000 || !tekst.includes(\"BEGIN:VCALENDAR\")) return null;\n return parseIcalBezet(tekst);\n } catch {\n return null;\n }\n}\n\nexport interface PageBlockDataOptions {\n /** `searchParams` van de route — alleen nodig voor productdetailpagina's (`?p=<slug>`). */\n searchParams?: { p?: string | string[]; [k: string]: unknown };\n /** De taal van de render — de zoekindex indexeert dan de vertaalde paden. (Nog ongebruikt.) */\n locale?: string;\n}\n\n/**\n * Server-side blockData voor één pagina, op basis van zijn bloktypes.\n * Geef het resultaat door als `<PublishedPage blockData={...}>`. `undefined`-namespaces blijven\n * weg zodat de blokken hun eigen fallback houden waar deze functie niets voor verzamelde.\n */\nexport async function getPageBlockData(\n siteId: string,\n page: Pick<Page, \"blocks\"> & Partial<Pick<Page, \"path\">>,\n opts?: PageBlockDataOptions,\n): Promise<Record<string, unknown> | undefined> {\n const types = new Set<string>();\n collectTypes(page.blocks ?? [], types, 0);\n\n const wilShop = [...SHOP_TYPES].some((t) => types.has(t));\n const wilNews = [...NEWS_TYPES].some((t) => types.has(t));\n const wilSearch = types.has(\"search\");\n const wilKruimels = types.has(\"breadcrumbs\") && typeof page.path === \"string\";\n const formIds = new Set<string>();\n collectFormIds(page.blocks ?? [], formIds, 0);\n const icalUrls = new Set<string>();\n collectIcalUrls(page.blocks ?? [], icalUrls, 0);\n\n // 8.3: ook pagina's met alleen eigen foto's krijgen een seed (de beeld-ladder). Eerst stond\n // \"media\" niet in deze voorwaarde, waardoor een gewone pagina met een hero of galerij hier al\n // stopte en de eigen srcset-ladder van 7.13 daar nooit werd gebruikt.\n const blokTekst = JSON.stringify(page.blocks ?? []);\n const wilMedia = heeftOpslagUrl(blokTekst) || wilShop || wilNews;\n if (!wilShop && !wilNews && !wilSearch && formIds.size === 0 && !wilMedia && !wilKruimels && icalUrls.size === 0) return undefined;\n\n const data: Record<string, unknown> = {};\n\n // Elke seed faalt ZACHT: bij een fout blijft die namespace weg en neemt de client-fallback van\n // het blok het over — hetzelfde gedrag als een site die (nog) geen seeds doorgeeft. De readers\n // gooien bewust bij een hik (uitkomsten worden gecached, fouten niet — zie server/cached.ts),\n // dus hier opvangen is wat \"één mislukte read\" scheidt van \"één mislukte pagina\".\n const zacht = async (naam: string, fn: () => Promise<void>) => {\n try {\n await fn();\n } catch (e) {\n console.warn(`[xicms] blockData-seed \"${naam}\" overgeslagen:`, (e as Error)?.message ?? e);\n }\n };\n\n // Parallel: elke seed leest uit de tagged servercache, dus dit zijn meestal nul echte reads.\n await Promise.all([\n zacht(\"shop\", async () => {\n if (!wilShop) return;\n // Dynamische import: shop-servercode alleen in de graph van pagina's/sites die hem gebruiken.\n const { getShopPageData } = await import(\"./shop\");\n data.shop = await getShopPageData(siteId, opts?.searchParams);\n }),\n zacht(\"beschikbaarheid\", async () => {\n if (icalUrls.size === 0) return;\n const map: Record<string, Bezet[] | null> = {};\n await Promise.all([...icalUrls].slice(0, 10).map(async (u) => { map[u] = await haalBezetting(u); }));\n data.beschikbaarheid = map;\n }),\n zacht(\"news\", async () => {\n if (!wilNews) return;\n data.news = await listPublishedNews(siteId);\n }),\n zacht(\"forms\", async () => {\n if (formIds.size === 0) return;\n const alle = await listFormDefinitions(siteId);\n // 8.9: een formulier dat niet bestaat expliciet als `null` meegeven. Zonder die markering\n // haalde de browser de hele Firestore-SDK (±170 kB gzip) binnen om hetzelfde vast te stellen.\n const map: Record<string, unknown> = {};\n for (const id of formIds) map[id] = null;\n for (const f of alle) if (formIds.has(f.id)) map[f.id] = f;\n data.forms = map;\n }),\n // 8.3: het kruimelpad server-side, met paginatitels. Eerst rendeerde het pas na mount (niet in\n // de HTML, layout-verschuiving, JSON-LD alleen client-side) en met labels uit het pad.\n zacht(\"breadcrumbs\", async () => {\n if (!wilKruimels) return;\n const pages = await listPublishedPages(siteId);\n const titels: Record<string, string> = {};\n for (const p of pages) if (typeof p.path === \"string\" && p.title) titels[p.path] = String(p.title);\n data.breadcrumbs = { path: page.path, titels };\n }),\n zacht(\"search\", async () => {\n if (!wilSearch) return;\n const [pages, news] = await Promise.all([\n listPublishedPages(siteId),\n searchWantsNews(page.blocks ?? [], 0) ? listPublishedNews(siteId) : Promise.resolve([]),\n ]);\n const index: SearchEntry[] = pages.map((p) => ({\n title: String(p.title ?? \"\"),\n path: String(p.path ?? \"/\"),\n text: blocksToText(p.blocks),\n }));\n for (const n of news) {\n index.push({\n title: n.title,\n path: `/nieuws/${n.slug || n.id}`,\n text: `${n.excerpt ?? \"\"} ${(n.bodyHtml ?? n.body ?? \"\").replace(/<[^>]*>/g, \" \")}`,\n });\n }\n data.searchIndex = index;\n }),\n ]);\n\n // Na de andere seeds (8.3): productfoto's en nieuwscovers staan niet in de blokken maar in de\n // winkel- en nieuwsdata, en verdienen dezelfde ladder.\n await zacht(\"media\", async () => {\n const corpus = `${blokTekst}\\n${data.shop ? JSON.stringify(data.shop) : \"\"}\\n${data.news ? JSON.stringify(data.news) : \"\"}`;\n if (!heeftOpslagUrl(corpus)) return;\n const alle = await listSiteMedia(siteId);\n const map: Record<string, unknown> = {};\n for (const m of alle) {\n const url = typeof m.url === \"string\" ? m.url : \"\";\n const varianten = m.variants as { sizes?: unknown[] } | undefined;\n // 7.24: ook een foto zónder ladder maar mét focuspunt gaat mee — anders zou een focuspunt\n // op een oudere upload (van vóór de pipeline) nooit aankomen.\n const focal = m.focal as { x?: unknown; y?: unknown } | undefined;\n const heeftFocal = typeof focal?.x === \"number\" && typeof focal?.y === \"number\";\n const heeftAlt = typeof m.alt === \"string\" && m.alt.trim() !== \"\";\n if (!url || m.status === \"processing\" || (!varianten?.sizes?.length && !heeftFocal && !heeftAlt)) continue;\n // In rich text staat de url HTML-ge-escaped (`&token=`) — die telt ook (8.10).\n if (corpus.includes(url) || corpus.includes(url.replace(/&/g, \"&\"))) {\n // 8.4: ook de alt-tekst uit de bibliotheek — een blok met een leeg alt valt daarop terug.\n const alt = typeof m.alt === \"string\" && m.alt.trim() ? m.alt.trim() : undefined;\n map[url] = { variants: m.variants, width: m.width, height: m.height, ...(heeftFocal ? { focal } : {}), ...(alt ? { alt } : {}) };\n }\n }\n if (Object.keys(map).length > 0) data.media = map;\n });\n\n return Object.keys(data).length > 0 ? data : undefined;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ontvangt CSP-overtredingen van browsers (8.7) en zet ze als één leesbare regel in de serverlog
|
|
3
|
+
* (Vercel → Logs). Zo weet je, voordat je de policy afdwingt, wat er op de echte site geladen wordt.
|
|
4
|
+
*
|
|
5
|
+
* Twee formaten: het oude `report-uri` (`{"csp-report": {...}}`, application/csp-report) en de
|
|
6
|
+
* Reporting API (`[{type:"csp-violation", body:{...}}]`, application/reports+json).
|
|
7
|
+
*
|
|
8
|
+
* Bewust zuinig: hoogstens 64 kB per verzoek, en elke unieke overtreding (directive + bron + pad)
|
|
9
|
+
* maar één keer per serverinstantie gelogd — één kapotte embed op een drukke pagina mag de log
|
|
10
|
+
* niet vullen.
|
|
11
|
+
*/
|
|
12
|
+
export interface CspOvertreding {
|
|
13
|
+
directive: string;
|
|
14
|
+
bron: string;
|
|
15
|
+
pagina: string;
|
|
16
|
+
}
|
|
17
|
+
/** Puur: haal de overtredingen uit een rapport (beide formaten). Onbekend = lege lijst. */
|
|
18
|
+
export declare function leesCspRapport(data: unknown): CspOvertreding[];
|
|
19
|
+
/** Route-handler voor `app/api/csp-rapport/route.ts`. */
|
|
20
|
+
export declare function cspRapportPOST(req: Request): Promise<Response>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const MAX_BYTES = 64 * 1024;
|
|
2
|
+
const MAX_GEZIEN = 500;
|
|
3
|
+
const gezien = /* @__PURE__ */ new Set();
|
|
4
|
+
function leesCspRapport(data) {
|
|
5
|
+
const lijst = [];
|
|
6
|
+
if (Array.isArray(data)) {
|
|
7
|
+
for (const r of data) {
|
|
8
|
+
const rr = r;
|
|
9
|
+
if (rr?.type === "csp-violation" && rr.body) lijst.push(rr.body);
|
|
10
|
+
}
|
|
11
|
+
} else if (data && typeof data === "object" && "csp-report" in data) {
|
|
12
|
+
const r = data["csp-report"];
|
|
13
|
+
if (r && typeof r === "object") lijst.push(r);
|
|
14
|
+
}
|
|
15
|
+
const tekst = (v) => typeof v === "string" ? v : "";
|
|
16
|
+
return lijst.map((r) => {
|
|
17
|
+
const directive = tekst(r.effectiveDirective) || tekst(r["effective-directive"]) || tekst(r["violated-directive"]) || "?";
|
|
18
|
+
const bron = tekst(r.blockedURL) || tekst(r["blocked-uri"]) || "?";
|
|
19
|
+
let pagina = tekst(r.documentURL) || tekst(r["document-uri"]);
|
|
20
|
+
try {
|
|
21
|
+
pagina = new URL(pagina).pathname;
|
|
22
|
+
} catch {
|
|
23
|
+
}
|
|
24
|
+
let b = bron;
|
|
25
|
+
try {
|
|
26
|
+
b = new URL(bron).origin;
|
|
27
|
+
} catch {
|
|
28
|
+
}
|
|
29
|
+
return { directive: directive.split(" ")[0], bron: b.slice(0, 200), pagina: pagina.slice(0, 200) };
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
async function cspRapportPOST(req) {
|
|
33
|
+
const lengte = Number(req.headers.get("content-length") ?? "0");
|
|
34
|
+
if (lengte > MAX_BYTES) return new Response(null, { status: 413 });
|
|
35
|
+
let data;
|
|
36
|
+
try {
|
|
37
|
+
const tekst = await req.text();
|
|
38
|
+
if (tekst.length > MAX_BYTES) return new Response(null, { status: 413 });
|
|
39
|
+
data = JSON.parse(tekst);
|
|
40
|
+
} catch {
|
|
41
|
+
return new Response(null, { status: 400 });
|
|
42
|
+
}
|
|
43
|
+
for (const o of leesCspRapport(data)) {
|
|
44
|
+
const sleutel = `${o.directive}|${o.bron}|${o.pagina}`;
|
|
45
|
+
if (gezien.has(sleutel)) continue;
|
|
46
|
+
if (gezien.size >= MAX_GEZIEN) gezien.clear();
|
|
47
|
+
gezien.add(sleutel);
|
|
48
|
+
console.warn(`[xicms-csp] ${o.directive} weigert ${o.bron} op ${o.pagina || "?"}`);
|
|
49
|
+
}
|
|
50
|
+
return new Response(null, { status: 204 });
|
|
51
|
+
}
|
|
52
|
+
export {
|
|
53
|
+
cspRapportPOST,
|
|
54
|
+
leesCspRapport
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=cspRapport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/server/cspRapport.ts"],"sourcesContent":["/**\n * Ontvangt CSP-overtredingen van browsers (8.7) en zet ze als één leesbare regel in de serverlog\n * (Vercel → Logs). Zo weet je, voordat je de policy afdwingt, wat er op de echte site geladen wordt.\n *\n * Twee formaten: het oude `report-uri` (`{\"csp-report\": {...}}`, application/csp-report) en de\n * Reporting API (`[{type:\"csp-violation\", body:{...}}]`, application/reports+json).\n *\n * Bewust zuinig: hoogstens 64 kB per verzoek, en elke unieke overtreding (directive + bron + pad)\n * maar één keer per serverinstantie gelogd — één kapotte embed op een drukke pagina mag de log\n * niet vullen.\n */\n\nconst MAX_BYTES = 64 * 1024;\nconst MAX_GEZIEN = 500;\nconst gezien = new Set<string>();\n\nexport interface CspOvertreding {\n directive: string;\n bron: string;\n pagina: string;\n}\n\n/** Puur: haal de overtredingen uit een rapport (beide formaten). Onbekend = lege lijst. */\nexport function leesCspRapport(data: unknown): CspOvertreding[] {\n const lijst: Record<string, unknown>[] = [];\n if (Array.isArray(data)) {\n for (const r of data) {\n const rr = r as { type?: string; body?: Record<string, unknown> };\n if (rr?.type === \"csp-violation\" && rr.body) lijst.push(rr.body);\n }\n } else if (data && typeof data === \"object\" && \"csp-report\" in data) {\n const r = (data as Record<string, unknown>)[\"csp-report\"];\n if (r && typeof r === \"object\") lijst.push(r as Record<string, unknown>);\n }\n const tekst = (v: unknown) => (typeof v === \"string\" ? v : \"\");\n return lijst.map((r) => {\n const directive = tekst(r.effectiveDirective) || tekst(r[\"effective-directive\"]) || tekst(r[\"violated-directive\"]) || \"?\";\n const bron = tekst(r.blockedURL) || tekst(r[\"blocked-uri\"]) || \"?\";\n let pagina = tekst(r.documentURL) || tekst(r[\"document-uri\"]);\n try {\n pagina = new URL(pagina).pathname;\n } catch {\n /* geen geldige URL: laat staan */\n }\n // Alleen de origin van de geblokkeerde bron: query's kunnen tokens bevatten.\n let b = bron;\n try {\n b = new URL(bron).origin;\n } catch {\n /* \"inline\", \"eval\", \"data\" … */\n }\n return { directive: directive.split(\" \")[0], bron: b.slice(0, 200), pagina: pagina.slice(0, 200) };\n });\n}\n\n/** Route-handler voor `app/api/csp-rapport/route.ts`. */\nexport async function cspRapportPOST(req: Request): Promise<Response> {\n const lengte = Number(req.headers.get(\"content-length\") ?? \"0\");\n if (lengte > MAX_BYTES) return new Response(null, { status: 413 });\n let data: unknown;\n try {\n const tekst = await req.text();\n if (tekst.length > MAX_BYTES) return new Response(null, { status: 413 });\n data = JSON.parse(tekst);\n } catch {\n return new Response(null, { status: 400 });\n }\n for (const o of leesCspRapport(data)) {\n const sleutel = `${o.directive}|${o.bron}|${o.pagina}`;\n if (gezien.has(sleutel)) continue;\n if (gezien.size >= MAX_GEZIEN) gezien.clear();\n gezien.add(sleutel);\n console.warn(`[xicms-csp] ${o.directive} weigert ${o.bron} op ${o.pagina || \"?\"}`);\n }\n return new Response(null, { status: 204 });\n}\n"],"mappings":"AAYA,MAAM,YAAY,KAAK;AACvB,MAAM,aAAa;AACnB,MAAM,SAAS,oBAAI,IAAY;AASxB,SAAS,eAAe,MAAiC;AAC9D,QAAM,QAAmC,CAAC;AAC1C,MAAI,MAAM,QAAQ,IAAI,GAAG;AACvB,eAAW,KAAK,MAAM;AACpB,YAAM,KAAK;AACX,UAAI,IAAI,SAAS,mBAAmB,GAAG,KAAM,OAAM,KAAK,GAAG,IAAI;AAAA,IACjE;AAAA,EACF,WAAW,QAAQ,OAAO,SAAS,YAAY,gBAAgB,MAAM;AACnE,UAAM,IAAK,KAAiC,YAAY;AACxD,QAAI,KAAK,OAAO,MAAM,SAAU,OAAM,KAAK,CAA4B;AAAA,EACzE;AACA,QAAM,QAAQ,CAAC,MAAgB,OAAO,MAAM,WAAW,IAAI;AAC3D,SAAO,MAAM,IAAI,CAAC,MAAM;AACtB,UAAM,YAAY,MAAM,EAAE,kBAAkB,KAAK,MAAM,EAAE,qBAAqB,CAAC,KAAK,MAAM,EAAE,oBAAoB,CAAC,KAAK;AACtH,UAAM,OAAO,MAAM,EAAE,UAAU,KAAK,MAAM,EAAE,aAAa,CAAC,KAAK;AAC/D,QAAI,SAAS,MAAM,EAAE,WAAW,KAAK,MAAM,EAAE,cAAc,CAAC;AAC5D,QAAI;AACF,eAAS,IAAI,IAAI,MAAM,EAAE;AAAA,IAC3B,QAAQ;AAAA,IAER;AAEA,QAAI,IAAI;AACR,QAAI;AACF,UAAI,IAAI,IAAI,IAAI,EAAE;AAAA,IACpB,QAAQ;AAAA,IAER;AACA,WAAO,EAAE,WAAW,UAAU,MAAM,GAAG,EAAE,CAAC,GAAG,MAAM,EAAE,MAAM,GAAG,GAAG,GAAG,QAAQ,OAAO,MAAM,GAAG,GAAG,EAAE;AAAA,EACnG,CAAC;AACH;AAGA,eAAsB,eAAe,KAAiC;AACpE,QAAM,SAAS,OAAO,IAAI,QAAQ,IAAI,gBAAgB,KAAK,GAAG;AAC9D,MAAI,SAAS,UAAW,QAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;AACjE,MAAI;AACJ,MAAI;AACF,UAAM,QAAQ,MAAM,IAAI,KAAK;AAC7B,QAAI,MAAM,SAAS,UAAW,QAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;AACvE,WAAO,KAAK,MAAM,KAAK;AAAA,EACzB,QAAQ;AACN,WAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC3C;AACA,aAAW,KAAK,eAAe,IAAI,GAAG;AACpC,UAAM,UAAU,GAAG,EAAE,SAAS,IAAI,EAAE,IAAI,IAAI,EAAE,MAAM;AACpD,QAAI,OAAO,IAAI,OAAO,EAAG;AACzB,QAAI,OAAO,QAAQ,WAAY,QAAO,MAAM;AAC5C,WAAO,IAAI,OAAO;AAClB,YAAQ,KAAK,eAAe,EAAE,SAAS,YAAY,EAAE,IAAI,OAAO,EAAE,UAAU,GAAG,EAAE;AAAA,EACnF;AACA,SAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;AAC3C;","names":[]}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import "server-only";
|
|
2
|
+
import { FONT_ROUTE, isGoogleFontUrl } from "../theme/fonts.js";
|
|
3
|
+
const GSTATIC = "https://fonts.gstatic.com/";
|
|
4
|
+
const MODERNE_UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0 Safari/537.36";
|
|
5
|
+
const BESTAND = /^[a-z0-9][a-z0-9/_.-]*\.(woff2|woff|ttf|otf)$/i;
|
|
6
|
+
function herschrijfFontCss(css, route = FONT_ROUTE) {
|
|
7
|
+
return css.replace(/https:\/\/fonts\.gstatic\.com\/([^)\s'"]+)/g, (_, pad) => `${route}?f=${encodeURIComponent(pad)}`);
|
|
8
|
+
}
|
|
9
|
+
async function fontsGET(req) {
|
|
10
|
+
const url = new URL(req.url);
|
|
11
|
+
const bestand = url.searchParams.get("f");
|
|
12
|
+
if (bestand !== null) {
|
|
13
|
+
if (!BESTAND.test(bestand) || bestand.includes("..")) return new Response("Ongeldig lettertype", { status: 400 });
|
|
14
|
+
const res2 = await fetch(GSTATIC + bestand);
|
|
15
|
+
if (!res2.ok || !res2.body) return new Response("Niet gevonden", { status: 404 });
|
|
16
|
+
return new Response(res2.body, {
|
|
17
|
+
headers: {
|
|
18
|
+
"content-type": res2.headers.get("content-type") ?? "font/woff2",
|
|
19
|
+
"cache-control": "public, max-age=31536000, immutable",
|
|
20
|
+
"access-control-allow-origin": "*"
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
const css = url.searchParams.get("css") ?? "";
|
|
25
|
+
if (!isGoogleFontUrl(css)) return new Response("Alleen Google-Fonts-CSS", { status: 400 });
|
|
26
|
+
const res = await fetch(css, { headers: { "user-agent": MODERNE_UA } });
|
|
27
|
+
if (!res.ok) return new Response("Lettertype-CSS niet beschikbaar", { status: 502 });
|
|
28
|
+
return new Response(herschrijfFontCss(await res.text()), {
|
|
29
|
+
headers: {
|
|
30
|
+
"content-type": "text/css; charset=utf-8",
|
|
31
|
+
"cache-control": "public, max-age=86400, s-maxage=604800, stale-while-revalidate=2592000"
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
export {
|
|
36
|
+
fontsGET,
|
|
37
|
+
herschrijfFontCss
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=fonts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/server/fonts.ts"],"sourcesContent":["import \"server-only\";\nimport { FONT_ROUTE, isGoogleFontUrl } from \"../theme/fonts\";\n\n/**\n * Route-handler voor `app/api/fonts/route.ts` (8.3): `export { fontsGET as GET } from\n * \"@xiwebdesign/xicms-core/server\"`. Haalt de Google-Fonts-CSS server-side op, herschrijft de\n * bestandsadressen naar deze route, en serveert de bestanden zelf met een jaar cache. De bezoeker\n * praat zo alleen met de eigen site.\n *\n * Bewust smal (geen open proxy): alleen fonts.googleapis.com-CSS en fonts.gstatic.com-bestanden\n * met een lettertype-extensie.\n */\nconst GSTATIC = \"https://fonts.gstatic.com/\";\n// Google kiest het formaat op de user-agent; met een moderne browser-UA krijgen we woff2.\nconst MODERNE_UA =\n \"Mozilla/5.0 (Macintosh; Intel Mac OS X 14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0 Safari/537.36\";\nconst BESTAND = /^[a-z0-9][a-z0-9/_.-]*\\.(woff2|woff|ttf|otf)$/i;\n\nexport function herschrijfFontCss(css: string, route = FONT_ROUTE): string {\n return css.replace(/https:\\/\\/fonts\\.gstatic\\.com\\/([^)\\s'\"]+)/g, (_, pad: string) => `${route}?f=${encodeURIComponent(pad)}`);\n}\n\nexport async function fontsGET(req: Request): Promise<Response> {\n const url = new URL(req.url);\n const bestand = url.searchParams.get(\"f\");\n if (bestand !== null) {\n if (!BESTAND.test(bestand) || bestand.includes(\"..\")) return new Response(\"Ongeldig lettertype\", { status: 400 });\n const res = await fetch(GSTATIC + bestand);\n if (!res.ok || !res.body) return new Response(\"Niet gevonden\", { status: 404 });\n return new Response(res.body, {\n headers: {\n \"content-type\": res.headers.get(\"content-type\") ?? \"font/woff2\",\n \"cache-control\": \"public, max-age=31536000, immutable\",\n \"access-control-allow-origin\": \"*\",\n },\n });\n }\n const css = url.searchParams.get(\"css\") ?? \"\";\n if (!isGoogleFontUrl(css)) return new Response(\"Alleen Google-Fonts-CSS\", { status: 400 });\n const res = await fetch(css, { headers: { \"user-agent\": MODERNE_UA } });\n if (!res.ok) return new Response(\"Lettertype-CSS niet beschikbaar\", { status: 502 });\n return new Response(herschrijfFontCss(await res.text()), {\n headers: {\n \"content-type\": \"text/css; charset=utf-8\",\n \"cache-control\": \"public, max-age=86400, s-maxage=604800, stale-while-revalidate=2592000\",\n },\n });\n}\n"],"mappings":"AAAA,OAAO;AACP,SAAS,YAAY,uBAAuB;AAW5C,MAAM,UAAU;AAEhB,MAAM,aACJ;AACF,MAAM,UAAU;AAET,SAAS,kBAAkB,KAAa,QAAQ,YAAoB;AACzE,SAAO,IAAI,QAAQ,+CAA+C,CAAC,GAAG,QAAgB,GAAG,KAAK,MAAM,mBAAmB,GAAG,CAAC,EAAE;AAC/H;AAEA,eAAsB,SAAS,KAAiC;AAC9D,QAAM,MAAM,IAAI,IAAI,IAAI,GAAG;AAC3B,QAAM,UAAU,IAAI,aAAa,IAAI,GAAG;AACxC,MAAI,YAAY,MAAM;AACpB,QAAI,CAAC,QAAQ,KAAK,OAAO,KAAK,QAAQ,SAAS,IAAI,EAAG,QAAO,IAAI,SAAS,uBAAuB,EAAE,QAAQ,IAAI,CAAC;AAChH,UAAMA,OAAM,MAAM,MAAM,UAAU,OAAO;AACzC,QAAI,CAACA,KAAI,MAAM,CAACA,KAAI,KAAM,QAAO,IAAI,SAAS,iBAAiB,EAAE,QAAQ,IAAI,CAAC;AAC9E,WAAO,IAAI,SAASA,KAAI,MAAM;AAAA,MAC5B,SAAS;AAAA,QACP,gBAAgBA,KAAI,QAAQ,IAAI,cAAc,KAAK;AAAA,QACnD,iBAAiB;AAAA,QACjB,+BAA+B;AAAA,MACjC;AAAA,IACF,CAAC;AAAA,EACH;AACA,QAAM,MAAM,IAAI,aAAa,IAAI,KAAK,KAAK;AAC3C,MAAI,CAAC,gBAAgB,GAAG,EAAG,QAAO,IAAI,SAAS,2BAA2B,EAAE,QAAQ,IAAI,CAAC;AACzF,QAAM,MAAM,MAAM,MAAM,KAAK,EAAE,SAAS,EAAE,cAAc,WAAW,EAAE,CAAC;AACtE,MAAI,CAAC,IAAI,GAAI,QAAO,IAAI,SAAS,mCAAmC,EAAE,QAAQ,IAAI,CAAC;AACnF,SAAO,IAAI,SAAS,kBAAkB,MAAM,IAAI,KAAK,CAAC,GAAG;AAAA,IACvD,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,iBAAiB;AAAA,IACnB;AAAA,EACF,CAAC;AACH;","names":["res"]}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -15,6 +15,9 @@ export { draftPOST, maakDraftGET } from "./draft.js";
|
|
|
15
15
|
export { signPreviewToken, verifyPreviewToken, type PreviewClaims } from "./previewToken.js";
|
|
16
16
|
export { resolveRedirect } from "./redirects.js";
|
|
17
17
|
export { versionGET } from "./version.js";
|
|
18
|
+
export { fontsGET, herschrijfFontCss } from "./fonts.js";
|
|
19
|
+
export { cspRapportPOST, leesCspRapport, type CspOvertreding } from "./cspRapport.js";
|
|
18
20
|
export { pathFromSlugs, listPublishedPages, listPublishedNews, getPublishedPageByPath, getPublishedPageByPathExpanded, getSiteSymbols, expandSymbols, getSiteCustomBlocks, getPublicSiteSettings, getDraftPageById, getDraftPageByPath, type StoredPage, } from "./pages.js";
|
|
19
21
|
export { getBundle, getBundlePage, getBundleNews, getNewsPageData, type PublishedBundle, type BundleItem, } from "./bundle.js";
|
|
20
22
|
export { siteLocales, parseLocaleSlugs, localizedPath, localizePage, localizeBlocks, pruneTranslations, TranslationsSchema, type Translations, type LocaleConfig, } from "../i18n/overlay.js";
|
|
23
|
+
export { bedrijfJsonLd } from "../bedrijf/bedrijf.js";
|
package/dist/server/index.js
CHANGED
|
@@ -24,6 +24,8 @@ import { draftPOST, maakDraftGET } from "./draft.js";
|
|
|
24
24
|
import { signPreviewToken, verifyPreviewToken } from "./previewToken.js";
|
|
25
25
|
import { resolveRedirect } from "./redirects.js";
|
|
26
26
|
import { versionGET } from "./version.js";
|
|
27
|
+
import { fontsGET, herschrijfFontCss } from "./fonts.js";
|
|
28
|
+
import { cspRapportPOST, leesCspRapport } from "./cspRapport.js";
|
|
27
29
|
import {
|
|
28
30
|
pathFromSlugs,
|
|
29
31
|
listPublishedPages,
|
|
@@ -52,18 +54,22 @@ import {
|
|
|
52
54
|
pruneTranslations,
|
|
53
55
|
TranslationsSchema
|
|
54
56
|
} from "../i18n/overlay.js";
|
|
57
|
+
import { bedrijfJsonLd } from "../bedrijf/bedrijf.js";
|
|
55
58
|
export {
|
|
56
59
|
TranslationsSchema,
|
|
57
60
|
articleJsonLd,
|
|
58
61
|
assertEnv,
|
|
62
|
+
bedrijfJsonLd,
|
|
59
63
|
breadcrumbListJsonLd,
|
|
60
64
|
buildJsonLd,
|
|
61
65
|
buildMetadata,
|
|
62
66
|
buildRobots,
|
|
63
67
|
buildSitemap,
|
|
64
68
|
businessJsonLd,
|
|
69
|
+
cspRapportPOST,
|
|
65
70
|
draftPOST,
|
|
66
71
|
expandSymbols,
|
|
72
|
+
fontsGET,
|
|
67
73
|
galleryJsonLd,
|
|
68
74
|
getAdminApp,
|
|
69
75
|
getAdminDb,
|
|
@@ -79,7 +85,9 @@ export {
|
|
|
79
85
|
getPublishedPageByPathExpanded,
|
|
80
86
|
getSiteCustomBlocks,
|
|
81
87
|
getSiteSymbols,
|
|
88
|
+
herschrijfFontCss,
|
|
82
89
|
jsonLdHtml,
|
|
90
|
+
leesCspRapport,
|
|
83
91
|
listFormDefinitions,
|
|
84
92
|
listPublishedNews,
|
|
85
93
|
listPublishedPages,
|