@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
|
@@ -57,6 +57,12 @@ export interface BlockDefinition<S extends $ZodType = $ZodType> {
|
|
|
57
57
|
* Zie `introspectToFields` (admin/lib/zodFields.ts) en `schema/fields.ts`.
|
|
58
58
|
*/
|
|
59
59
|
fields?: FieldHints;
|
|
60
|
+
/**
|
|
61
|
+
* 8.10: een blok waar de werking van de site op leunt (winkelwagen, afrekenen, formulier).
|
|
62
|
+
* Verwijderen in de editor vraagt dan eerst om bevestiging met deze uitleg; gewone blokken gaan
|
|
63
|
+
* met één klik weg (er is ongedaan maken). De tekst zegt wat er stuk gaat, in klanttaal.
|
|
64
|
+
*/
|
|
65
|
+
essentieel?: string;
|
|
60
66
|
}
|
|
61
67
|
/**
|
|
62
68
|
* Registreert een block-type.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/blocks/registry.ts"],"sourcesContent":["/**\n * Block-registry: koppelt een `_type` aan zijn Zod-schema, een (server-safe) Render-component\n * en een optionele (client/admin) Editor. Klant-blokken worden hier ingebracht via `defineBlock`\n * — zo blijft de core klant-onafhankelijk: de klant levert de render-component, core de structuur.\n *\n * Pure logica + types (geen \"use client\"): bruikbaar op server en client.\n */\nimport type { ComponentType } from \"react\";\nimport type { $ZodType } from \"zod/v4/core\";\nimport type { BlockBase } from \"../schema/blocks\";\nimport type { FieldHints } from \"../schema/fields\";\n\n/**\n * Het uitvoertype van een blok-schema, onafhankelijk van de Zod-façade.\n *\n * Core draait op `zod/mini` (zie schema/) omdat de classic-façade niet tree-shaket en ~300 KB aan\n * de publieke bundel toevoegde. De grens hieronder is daarom `$ZodType` uit `zod/v4/core` — de\n * gedeelde bovengrens van BEIDE smaken. Gevolg: een klantrepo mag zijn eigen blok-schema's zowel\n * met `zod` als met `zod/mini` schrijven; `defineBlock` accepteert allebei.\n */\ntype InferBlock<S extends $ZodType> = S[\"_zod\"][\"output\"];\n\nexport interface BlockRenderProps<T = unknown> {\n block: T;\n siteId: string;\n /** \"view\" = publiek; \"preview\" = draft-weergave in de editor. */\n mode?: \"view\" | \"preview\";\n}\n\nexport interface BlockEditorProps<T = unknown> {\n block: T;\n siteId: string;\n onChange: (next: T) => void;\n}\n\n/**\n * Indeling van een block in het toevoeg-menu:\n * - `content` (default): een gewoon blok dat je op de pagina plaatst.\n * - `widget`: een compacte variant die je naast een blok (in een zijbalk/kolom) zet — haalt z'n\n * gegevens uit dezelfde module als de volwaardige versie (bv. \"laatste nieuws\", \"komende agenda\").\n * - `layout`: een indelingsblok dat andere blokken bevat (kolommen, zijbalk, tabs).\n */\nexport type BlockCategory = \"content\" | \"widget\" | \"layout\";\n\nexport interface BlockDefinition<S extends $ZodType = $ZodType> {\n type: string;\n /**\n * Zod-schema van het blok. Voor de INGEBOUWDE blokken is dit veld leeg en wordt het schema\n * lazy geregistreerd via `blocks/builtinSchemas` (7.13): de schema's zijn editor-kennis, en\n * mét dit veld reisden álle schema's plus de zod-runtime (±73 KB gzip) mee naar elke publieke\n * bezoeker die nooit iets bewerkt. Klant-blokken (defineBlock in een klantrepo) mogen hun\n * schema gewoon hier blijven meegeven — dat is klein en houdt de API eenvoudig.\n */\n schema?: S;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- registry-grens: het blok\n // typeert zijn eigen props; hier is de data per definitie \"wat de database gaf\".\n Render: ComponentType<BlockRenderProps<any>>;\n /**\n * Admin-editor (lazy/client). Optioneel; ontbreekt = automatische schema-editor (GenericEditor):\n * die genereert een formulier uit het Zod-schema, dus een blok zonder eigen editor blijft bewerkbaar.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- zie Render.\n Editor?: ComponentType<BlockEditorProps<any>>;\n label: string;\n icon?: string;\n /** Groep in het toevoeg-menu (content/widget/layout). Standaard \"content\". */\n category?: BlockCategory;\n /** Defaultwaarden voor een nieuw block (zonder _key/_type). */\n defaults?: () => Record<string, unknown>;\n /**\n * Optionele hints voor het automatisch gegenereerde formulier (GenericEditor 2.0, 5.0):\n * per (dotted) veldsleutel een verfijning van de uit het Zod-schema afgeleide Field-IR —\n * bv. `{ \"items.date\": { type: \"date\" }, cta: { showIf: { field: \"showCta\" } } }`.\n * Zie `introspectToFields` (admin/lib/zodFields.ts) en `schema/fields.ts`.\n */\n fields?: FieldHints;\n}\n\n/**\n * Registreert een block-type.\n *\n * UPDATE-VEILIGHEID: voeg in een nieuwe versie nooit een VERPLICHT veld toe aan een bestaand\n * block-schema zonder `.default()` of `.optional()`. Anders faalt validatie op al opgeslagen\n * content en wordt die overgeslagen. Met defaults/optioneel blijft oude content gewoon werken.\n */\nexport function defineBlock<S extends $ZodType>(def: BlockDefinition<S>): BlockDefinition {\n // Type-erased return: het schema-type wordt alleen gebruikt om Render/Editor-props te\n // inferreren bij definitie. Daarna behandelen we blokken generiek in de registry.\n return def as unknown as BlockDefinition;\n}\n\nexport type BlockRegistry = Map<string, BlockDefinition>;\n\n/**\n * Bouwt de opties voor het \"blok toevoegen\"-menu, gegroepeerd op categorie: eerst gewone blokken,\n * dan indeling (kolommen/zijbalk/tabs), dan widgets. Widgets/indeling krijgen een suffix zodat de\n * groep duidelijk is in een platte keuzelijst.\n */\nexport function buildBlockAddOptions(defs: Iterable<BlockDefinition>): { value: string; label: string }[] {\n const order: Record<BlockCategory, number> = { content: 0, layout: 1, widget: 2 };\n const suffix: Record<BlockCategory, string> = { content: \"\", layout: \" · indeling\", widget: \" · widget\" };\n return [...defs]\n .map((d) => ({ def: d, cat: d.category ?? \"content\" }))\n .sort((a, b) => order[a.cat] - order[b.cat])\n .map(({ def, cat }) => ({ value: def.type, label: `${def.icon ?? \"▫\"} ${def.label}${suffix[cat]}` }));\n}\n\nexport function createRegistry(defs: BlockDefinition[]): BlockRegistry {\n const m: BlockRegistry = new Map();\n for (const d of defs) m.set(d.type, d);\n return m;\n}\n\nexport function mergeRegistry(base: BlockRegistry, extra: BlockDefinition[]): BlockRegistry {\n const m: BlockRegistry = new Map(base);\n for (const d of extra) m.set(d.type, d);\n return m;\n}\n\n/**\n * Strikte per-type validatie van een blocks-array tegen de registry. Onbekende types en\n * blocks die hun schema niet halen worden weggelaten (defensief — nooit half-rendered content).\n * Gebruikt bij publiceren (server) en vóór renderen.\n *\n * Recursief: geneste blokken in de indelingsblokken (kolommen, \"Met zijbalk\", tabs) worden óók\n * per-type gevalideerd. Hun schema's valideren kinderen bewust alleen als `BlockBaseSchema`\n * (passthrough) zodat klant-blokken erin mogen; de strikte controle gebeurt hier.\n */\n/**\n * De zod-parser wordt hier INGEPLUGD in plaats van geïmporteerd: een statische `safeParse`-import\n * zou de zod-runtime alsnog in elke publieke bundel trekken, ook nu de schema's zelf lazy zijn.\n * `blocks/builtinSchemas` (geladen door de admin/editor-chunks) zet hem; klant-blokken die hun\n * schema meegeven krijgen daarmee ook pas volledige client-validatie zodra de editor laadt —\n * publiek geldt voor hen dezelfde lichte vormcheck als voor de ingebouwde blokken.\n */\ntype SchemaParser = (schema: $ZodType, raw: unknown) => { success: boolean; data?: unknown; issues?: unknown };\nlet schemaParser: SchemaParser | null = null;\nexport function setSchemaParser(fn: SchemaParser): void {\n schemaParser = fn;\n}\n\nexport function validateBlocks(blocks: unknown[] | undefined, registry: BlockRegistry): BlockBase[] {\n const out: BlockBase[] = [];\n for (const raw of blocks ?? []) {\n const type = (raw as { _type?: unknown } | null)?._type;\n const def = typeof type === \"string\" ? registry.get(type) : undefined;\n if (!def) {\n warn(`onbekend block-type \"${String(type)}\" overgeslagen (geen registratie).`);\n continue;\n }\n /**\n * Twee niveaus van controle, afhankelijk van wat er geladen is:\n * - Schema + parser aanwezig (editor/admin, of een klant-blok dat zijn schema meedraagt):\n * volledige zod-validatie, zoals altijd.\n * - Geen schema (publieke render van ingebouwde blokken): een lichte vormcheck volstaat —\n * de inhoudelijke garantie is dan al geleverd door de publiceer-pijplijn (functions\n * saniteert en valideert bij publishPage). Zo blijft zod uit de bezoekersbundel.\n */\n const parser = def.schema ? schemaParser : null;\n if (def.schema && parser) {\n const res = parser(def.schema, raw);\n if (res.success) out.push(validateNestedBlocks(res.data as BlockBase, registry));\n else warn(`block \"${type}\" voldoet niet aan zijn schema en is overgeslagen.`, res.issues);\n } else if (raw && typeof raw === \"object\" && typeof (raw as { _type?: unknown })._type === \"string\") {\n out.push(validateNestedBlocks(raw as BlockBase, registry));\n } else {\n warn(`block \"${String(type)}\" heeft geen geldige vorm en is overgeslagen.`);\n }\n }\n return out;\n}\n\n/** Valideert de geneste blok-lijsten van de bekende indelingsvormen (immutable). */\nfunction validateNestedBlocks(block: BlockBase, registry: BlockRegistry): BlockBase {\n const b = block as BlockBase & Record<string, unknown>;\n if (b._type === \"columns\" && Array.isArray(b.columns)) {\n return {\n ...b,\n columns: b.columns.map((col) =>\n col && typeof col === \"object\" && Array.isArray((col as { blocks?: unknown }).blocks)\n ? { ...col, blocks: validateBlocks((col as { blocks: unknown[] }).blocks, registry) }\n : col,\n ),\n };\n }\n if (b._type === \"withSidebar\" && (Array.isArray(b.main) || Array.isArray(b.aside))) {\n return {\n ...b,\n main: Array.isArray(b.main) ? validateBlocks(b.main, registry) : b.main,\n aside: Array.isArray(b.aside) ? validateBlocks(b.aside, registry) : b.aside,\n };\n }\n if (b._type === \"tabs\" && Array.isArray(b.tabs)) {\n return {\n ...b,\n tabs: b.tabs.map((tab) =>\n tab && typeof tab === \"object\" && Array.isArray((tab as { blocks?: unknown }).blocks)\n ? { ...tab, blocks: validateBlocks((tab as { blocks: unknown[] }).blocks, registry) }\n : tab,\n ),\n };\n }\n return block;\n}\n\nfunction warn(msg: string, detail?: unknown): void {\n // Geen stil verlies: log waarom een block niet rendert (helpt bij schema-wijzigingen).\n if (typeof console !== \"undefined\") console.warn(`[xicms] ${msg}`, detail ?? \"\");\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/blocks/registry.ts"],"sourcesContent":["/**\n * Block-registry: koppelt een `_type` aan zijn Zod-schema, een (server-safe) Render-component\n * en een optionele (client/admin) Editor. Klant-blokken worden hier ingebracht via `defineBlock`\n * — zo blijft de core klant-onafhankelijk: de klant levert de render-component, core de structuur.\n *\n * Pure logica + types (geen \"use client\"): bruikbaar op server en client.\n */\nimport type { ComponentType } from \"react\";\nimport type { $ZodType } from \"zod/v4/core\";\nimport type { BlockBase } from \"../schema/blocks\";\nimport type { FieldHints } from \"../schema/fields\";\n\n/**\n * Het uitvoertype van een blok-schema, onafhankelijk van de Zod-façade.\n *\n * Core draait op `zod/mini` (zie schema/) omdat de classic-façade niet tree-shaket en ~300 KB aan\n * de publieke bundel toevoegde. De grens hieronder is daarom `$ZodType` uit `zod/v4/core` — de\n * gedeelde bovengrens van BEIDE smaken. Gevolg: een klantrepo mag zijn eigen blok-schema's zowel\n * met `zod` als met `zod/mini` schrijven; `defineBlock` accepteert allebei.\n */\ntype InferBlock<S extends $ZodType> = S[\"_zod\"][\"output\"];\n\nexport interface BlockRenderProps<T = unknown> {\n block: T;\n siteId: string;\n /** \"view\" = publiek; \"preview\" = draft-weergave in de editor. */\n mode?: \"view\" | \"preview\";\n}\n\nexport interface BlockEditorProps<T = unknown> {\n block: T;\n siteId: string;\n onChange: (next: T) => void;\n}\n\n/**\n * Indeling van een block in het toevoeg-menu:\n * - `content` (default): een gewoon blok dat je op de pagina plaatst.\n * - `widget`: een compacte variant die je naast een blok (in een zijbalk/kolom) zet — haalt z'n\n * gegevens uit dezelfde module als de volwaardige versie (bv. \"laatste nieuws\", \"komende agenda\").\n * - `layout`: een indelingsblok dat andere blokken bevat (kolommen, zijbalk, tabs).\n */\nexport type BlockCategory = \"content\" | \"widget\" | \"layout\";\n\nexport interface BlockDefinition<S extends $ZodType = $ZodType> {\n type: string;\n /**\n * Zod-schema van het blok. Voor de INGEBOUWDE blokken is dit veld leeg en wordt het schema\n * lazy geregistreerd via `blocks/builtinSchemas` (7.13): de schema's zijn editor-kennis, en\n * mét dit veld reisden álle schema's plus de zod-runtime (±73 KB gzip) mee naar elke publieke\n * bezoeker die nooit iets bewerkt. Klant-blokken (defineBlock in een klantrepo) mogen hun\n * schema gewoon hier blijven meegeven — dat is klein en houdt de API eenvoudig.\n */\n schema?: S;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- registry-grens: het blok\n // typeert zijn eigen props; hier is de data per definitie \"wat de database gaf\".\n Render: ComponentType<BlockRenderProps<any>>;\n /**\n * Admin-editor (lazy/client). Optioneel; ontbreekt = automatische schema-editor (GenericEditor):\n * die genereert een formulier uit het Zod-schema, dus een blok zonder eigen editor blijft bewerkbaar.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- zie Render.\n Editor?: ComponentType<BlockEditorProps<any>>;\n label: string;\n icon?: string;\n /** Groep in het toevoeg-menu (content/widget/layout). Standaard \"content\". */\n category?: BlockCategory;\n /** Defaultwaarden voor een nieuw block (zonder _key/_type). */\n defaults?: () => Record<string, unknown>;\n /**\n * Optionele hints voor het automatisch gegenereerde formulier (GenericEditor 2.0, 5.0):\n * per (dotted) veldsleutel een verfijning van de uit het Zod-schema afgeleide Field-IR —\n * bv. `{ \"items.date\": { type: \"date\" }, cta: { showIf: { field: \"showCta\" } } }`.\n * Zie `introspectToFields` (admin/lib/zodFields.ts) en `schema/fields.ts`.\n */\n fields?: FieldHints;\n /**\n * 8.10: een blok waar de werking van de site op leunt (winkelwagen, afrekenen, formulier).\n * Verwijderen in de editor vraagt dan eerst om bevestiging met deze uitleg; gewone blokken gaan\n * met één klik weg (er is ongedaan maken). De tekst zegt wat er stuk gaat, in klanttaal.\n */\n essentieel?: string;\n}\n\n/**\n * Registreert een block-type.\n *\n * UPDATE-VEILIGHEID: voeg in een nieuwe versie nooit een VERPLICHT veld toe aan een bestaand\n * block-schema zonder `.default()` of `.optional()`. Anders faalt validatie op al opgeslagen\n * content en wordt die overgeslagen. Met defaults/optioneel blijft oude content gewoon werken.\n */\nexport function defineBlock<S extends $ZodType>(def: BlockDefinition<S>): BlockDefinition {\n // Type-erased return: het schema-type wordt alleen gebruikt om Render/Editor-props te\n // inferreren bij definitie. Daarna behandelen we blokken generiek in de registry.\n return def as unknown as BlockDefinition;\n}\n\nexport type BlockRegistry = Map<string, BlockDefinition>;\n\n/**\n * Bouwt de opties voor het \"blok toevoegen\"-menu, gegroepeerd op categorie: eerst gewone blokken,\n * dan indeling (kolommen/zijbalk/tabs), dan widgets. Widgets/indeling krijgen een suffix zodat de\n * groep duidelijk is in een platte keuzelijst.\n */\nexport function buildBlockAddOptions(defs: Iterable<BlockDefinition>): { value: string; label: string }[] {\n const order: Record<BlockCategory, number> = { content: 0, layout: 1, widget: 2 };\n const suffix: Record<BlockCategory, string> = { content: \"\", layout: \" · indeling\", widget: \" · widget\" };\n return [...defs]\n .map((d) => ({ def: d, cat: d.category ?? \"content\" }))\n .sort((a, b) => order[a.cat] - order[b.cat])\n .map(({ def, cat }) => ({ value: def.type, label: `${def.icon ?? \"▫\"} ${def.label}${suffix[cat]}` }));\n}\n\nexport function createRegistry(defs: BlockDefinition[]): BlockRegistry {\n const m: BlockRegistry = new Map();\n for (const d of defs) m.set(d.type, d);\n return m;\n}\n\nexport function mergeRegistry(base: BlockRegistry, extra: BlockDefinition[]): BlockRegistry {\n const m: BlockRegistry = new Map(base);\n for (const d of extra) m.set(d.type, d);\n return m;\n}\n\n/**\n * Strikte per-type validatie van een blocks-array tegen de registry. Onbekende types en\n * blocks die hun schema niet halen worden weggelaten (defensief — nooit half-rendered content).\n * Gebruikt bij publiceren (server) en vóór renderen.\n *\n * Recursief: geneste blokken in de indelingsblokken (kolommen, \"Met zijbalk\", tabs) worden óók\n * per-type gevalideerd. Hun schema's valideren kinderen bewust alleen als `BlockBaseSchema`\n * (passthrough) zodat klant-blokken erin mogen; de strikte controle gebeurt hier.\n */\n/**\n * De zod-parser wordt hier INGEPLUGD in plaats van geïmporteerd: een statische `safeParse`-import\n * zou de zod-runtime alsnog in elke publieke bundel trekken, ook nu de schema's zelf lazy zijn.\n * `blocks/builtinSchemas` (geladen door de admin/editor-chunks) zet hem; klant-blokken die hun\n * schema meegeven krijgen daarmee ook pas volledige client-validatie zodra de editor laadt —\n * publiek geldt voor hen dezelfde lichte vormcheck als voor de ingebouwde blokken.\n */\ntype SchemaParser = (schema: $ZodType, raw: unknown) => { success: boolean; data?: unknown; issues?: unknown };\nlet schemaParser: SchemaParser | null = null;\nexport function setSchemaParser(fn: SchemaParser): void {\n schemaParser = fn;\n}\n\nexport function validateBlocks(blocks: unknown[] | undefined, registry: BlockRegistry): BlockBase[] {\n const out: BlockBase[] = [];\n for (const raw of blocks ?? []) {\n const type = (raw as { _type?: unknown } | null)?._type;\n const def = typeof type === \"string\" ? registry.get(type) : undefined;\n if (!def) {\n warn(`onbekend block-type \"${String(type)}\" overgeslagen (geen registratie).`);\n continue;\n }\n /**\n * Twee niveaus van controle, afhankelijk van wat er geladen is:\n * - Schema + parser aanwezig (editor/admin, of een klant-blok dat zijn schema meedraagt):\n * volledige zod-validatie, zoals altijd.\n * - Geen schema (publieke render van ingebouwde blokken): een lichte vormcheck volstaat —\n * de inhoudelijke garantie is dan al geleverd door de publiceer-pijplijn (functions\n * saniteert en valideert bij publishPage). Zo blijft zod uit de bezoekersbundel.\n */\n const parser = def.schema ? schemaParser : null;\n if (def.schema && parser) {\n const res = parser(def.schema, raw);\n if (res.success) out.push(validateNestedBlocks(res.data as BlockBase, registry));\n else warn(`block \"${type}\" voldoet niet aan zijn schema en is overgeslagen.`, res.issues);\n } else if (raw && typeof raw === \"object\" && typeof (raw as { _type?: unknown })._type === \"string\") {\n out.push(validateNestedBlocks(raw as BlockBase, registry));\n } else {\n warn(`block \"${String(type)}\" heeft geen geldige vorm en is overgeslagen.`);\n }\n }\n return out;\n}\n\n/** Valideert de geneste blok-lijsten van de bekende indelingsvormen (immutable). */\nfunction validateNestedBlocks(block: BlockBase, registry: BlockRegistry): BlockBase {\n const b = block as BlockBase & Record<string, unknown>;\n if (b._type === \"columns\" && Array.isArray(b.columns)) {\n return {\n ...b,\n columns: b.columns.map((col) =>\n col && typeof col === \"object\" && Array.isArray((col as { blocks?: unknown }).blocks)\n ? { ...col, blocks: validateBlocks((col as { blocks: unknown[] }).blocks, registry) }\n : col,\n ),\n };\n }\n if (b._type === \"withSidebar\" && (Array.isArray(b.main) || Array.isArray(b.aside))) {\n return {\n ...b,\n main: Array.isArray(b.main) ? validateBlocks(b.main, registry) : b.main,\n aside: Array.isArray(b.aside) ? validateBlocks(b.aside, registry) : b.aside,\n };\n }\n if (b._type === \"tabs\" && Array.isArray(b.tabs)) {\n return {\n ...b,\n tabs: b.tabs.map((tab) =>\n tab && typeof tab === \"object\" && Array.isArray((tab as { blocks?: unknown }).blocks)\n ? { ...tab, blocks: validateBlocks((tab as { blocks: unknown[] }).blocks, registry) }\n : tab,\n ),\n };\n }\n return block;\n}\n\nfunction warn(msg: string, detail?: unknown): void {\n // Geen stil verlies: log waarom een block niet rendert (helpt bij schema-wijzigingen).\n if (typeof console !== \"undefined\") console.warn(`[xicms] ${msg}`, detail ?? \"\");\n}\n"],"mappings":"AA2FO,SAAS,YAAgC,KAA0C;AAGxF,SAAO;AACT;AASO,SAAS,qBAAqB,MAAqE;AACxG,QAAM,QAAuC,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,EAAE;AAChF,QAAM,SAAwC,EAAE,SAAS,IAAI,QAAQ,mBAAgB,QAAQ,gBAAa;AAC1G,SAAO,CAAC,GAAG,IAAI,EACZ,IAAI,CAAC,OAAO,EAAE,KAAK,GAAG,KAAK,EAAE,YAAY,UAAU,EAAE,EACrD,KAAK,CAAC,GAAG,MAAM,MAAM,EAAE,GAAG,IAAI,MAAM,EAAE,GAAG,CAAC,EAC1C,IAAI,CAAC,EAAE,KAAK,IAAI,OAAO,EAAE,OAAO,IAAI,MAAM,OAAO,GAAG,IAAI,QAAQ,QAAG,KAAK,IAAI,KAAK,GAAG,OAAO,GAAG,CAAC,GAAG,EAAE;AACzG;AAEO,SAAS,eAAe,MAAwC;AACrE,QAAM,IAAmB,oBAAI,IAAI;AACjC,aAAW,KAAK,KAAM,GAAE,IAAI,EAAE,MAAM,CAAC;AACrC,SAAO;AACT;AAEO,SAAS,cAAc,MAAqB,OAAyC;AAC1F,QAAM,IAAmB,IAAI,IAAI,IAAI;AACrC,aAAW,KAAK,MAAO,GAAE,IAAI,EAAE,MAAM,CAAC;AACtC,SAAO;AACT;AAmBA,IAAI,eAAoC;AACjC,SAAS,gBAAgB,IAAwB;AACtD,iBAAe;AACjB;AAEO,SAAS,eAAe,QAA+B,UAAsC;AAClG,QAAM,MAAmB,CAAC;AAC1B,aAAW,OAAO,UAAU,CAAC,GAAG;AAC9B,UAAM,OAAQ,KAAoC;AAClD,UAAM,MAAM,OAAO,SAAS,WAAW,SAAS,IAAI,IAAI,IAAI;AAC5D,QAAI,CAAC,KAAK;AACR,WAAK,wBAAwB,OAAO,IAAI,CAAC,oCAAoC;AAC7E;AAAA,IACF;AASA,UAAM,SAAS,IAAI,SAAS,eAAe;AAC3C,QAAI,IAAI,UAAU,QAAQ;AACxB,YAAM,MAAM,OAAO,IAAI,QAAQ,GAAG;AAClC,UAAI,IAAI,QAAS,KAAI,KAAK,qBAAqB,IAAI,MAAmB,QAAQ,CAAC;AAAA,UAC1E,MAAK,UAAU,IAAI,sDAAsD,IAAI,MAAM;AAAA,IAC1F,WAAW,OAAO,OAAO,QAAQ,YAAY,OAAQ,IAA4B,UAAU,UAAU;AACnG,UAAI,KAAK,qBAAqB,KAAkB,QAAQ,CAAC;AAAA,IAC3D,OAAO;AACL,WAAK,UAAU,OAAO,IAAI,CAAC,+CAA+C;AAAA,IAC5E;AAAA,EACF;AACA,SAAO;AACT;AAGA,SAAS,qBAAqB,OAAkB,UAAoC;AAClF,QAAM,IAAI;AACV,MAAI,EAAE,UAAU,aAAa,MAAM,QAAQ,EAAE,OAAO,GAAG;AACrD,WAAO;AAAA,MACL,GAAG;AAAA,MACH,SAAS,EAAE,QAAQ;AAAA,QAAI,CAAC,QACtB,OAAO,OAAO,QAAQ,YAAY,MAAM,QAAS,IAA6B,MAAM,IAChF,EAAE,GAAG,KAAK,QAAQ,eAAgB,IAA8B,QAAQ,QAAQ,EAAE,IAClF;AAAA,MACN;AAAA,IACF;AAAA,EACF;AACA,MAAI,EAAE,UAAU,kBAAkB,MAAM,QAAQ,EAAE,IAAI,KAAK,MAAM,QAAQ,EAAE,KAAK,IAAI;AAClF,WAAO;AAAA,MACL,GAAG;AAAA,MACH,MAAM,MAAM,QAAQ,EAAE,IAAI,IAAI,eAAe,EAAE,MAAM,QAAQ,IAAI,EAAE;AAAA,MACnE,OAAO,MAAM,QAAQ,EAAE,KAAK,IAAI,eAAe,EAAE,OAAO,QAAQ,IAAI,EAAE;AAAA,IACxE;AAAA,EACF;AACA,MAAI,EAAE,UAAU,UAAU,MAAM,QAAQ,EAAE,IAAI,GAAG;AAC/C,WAAO;AAAA,MACL,GAAG;AAAA,MACH,MAAM,EAAE,KAAK;AAAA,QAAI,CAAC,QAChB,OAAO,OAAO,QAAQ,YAAY,MAAM,QAAS,IAA6B,MAAM,IAChF,EAAE,GAAG,KAAK,QAAQ,eAAgB,IAA8B,QAAQ,QAAQ,EAAE,IAClF;AAAA,MACN;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,KAAK,KAAa,QAAwB;AAEjD,MAAI,OAAO,YAAY,YAAa,SAAQ,KAAK,WAAW,GAAG,IAAI,UAAU,EAAE;AACjF;","names":[]}
|
package/dist/blocks/ui/index.js
CHANGED
|
@@ -29,6 +29,7 @@ function Btn({ label, href, variant = "primary", size = "lg", type, target, onCl
|
|
|
29
29
|
rel: target === "_blank" ? "noopener noreferrer" : void 0,
|
|
30
30
|
type: href ? void 0 : type ?? "button",
|
|
31
31
|
clickAction: onClick,
|
|
32
|
+
className: variant === "outline" ? "xicms-btn-omlijnd" : void 0,
|
|
32
33
|
children: editField ? /* @__PURE__ */ jsx("span", { "data-xicms-edit": editField, children: label }) : void 0
|
|
33
34
|
}
|
|
34
35
|
);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/blocks/ui/index.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * Gedeelde presentatie-primitieven voor de runtime-blokken — een dunne laag bovenop het Astryx\n * design system (Meta, StyleX). Blokken bouwen hierop i.p.v. losse `xicms-*`-classNames, zodat het\n * uiterlijk consistent is en we de onderlaag centraal kunnen wijzigen.\n *\n * De merk-look per site komt via de Astryx-token-mapping op `.xicms-root` (zie XiCMSProvider):\n * `--color-accent`, `--font-family-*`, `--radius-*` volgen de XiCMS-thema-tokens.\n */\n\nimport { type ReactNode } from \"react\";\nimport { Button, type ButtonVariant } from \"@astryxdesign/core/Button\";\n\nexport { VStack } from \"@astryxdesign/core/VStack\";\nexport { HStack } from \"@astryxdesign/core/HStack\";\nexport { Stack } from \"@astryxdesign/core/Stack\";\nexport { Grid } from \"@astryxdesign/core/Grid\";\nexport { Card } from \"@astryxdesign/core/Card\";\nexport { Heading } from \"@astryxdesign/core/Heading\";\nexport { Text } from \"@astryxdesign/core/Text\";\nexport { Divider } from \"@astryxdesign/core/Divider\";\nexport { Badge } from \"@astryxdesign/core/Badge\";\nexport { Avatar } from \"@astryxdesign/core/Avatar\";\n\n/** XiCMS-knopvarianten → Astryx-varianten (Astryx kent geen \"outline\" → ghost). */\nconst VARIANT_MAP: Record<string, ButtonVariant> = {\n primary: \"primary\",\n secondary: \"secondary\",\n outline: \"ghost\",\n ghost: \"ghost\",\n};\n\nexport interface BtnProps {\n label: string;\n href?: string;\n variant?: string;\n /** Marketing-knoppen zijn standaard wat groter dan de app-default. */\n size?: \"sm\" | \"md\" | \"lg\";\n type?: \"button\" | \"submit\" | \"reset\";\n target?: string;\n onClick?: () => void;\n children?: ReactNode;\n /**\n * Veldpad voor inline bewerken, bv. `\"buttonLabel\"` of `` `items.${i}.ctaLabel` ``. Het label\n * gaat dan door een eigen `<span>` met de `data-xicms-edit`-haak, zodat de knoptekst op het\n * canvas te bewerken is i.p.v. alleen via het zijpaneel. Astryx houdt `label` als toegankelijke\n * naam aan (het zet zelf `aria-label` zodra children afwijken), dus dit kost geen a11y.\n */\n editField?: string;\n}\n\n/**\n * Knop/CTA-helper bovenop Astryx `Button`. Rendert als link wanneer `href` gezet is. Houdt de\n * bestaande XiCMS-varianten (primary/secondary/outline) werkend via de mapping hierboven.\n */\nexport function Btn({ label, href, variant = \"primary\", size = \"lg\", type, target, onClick, editField }: BtnProps) {\n return (\n <Button\n label={label}\n variant={VARIANT_MAP[variant] ?? \"primary\"}\n size={size}\n href={href}\n target={href && target ? target : undefined}\n rel={target === \"_blank\" ? \"noopener noreferrer\" : undefined}\n type={href ? undefined : (type ?? \"button\")}\n clickAction={onClick}\n >\n {/* `undefined` laat Astryx op `label` terugvallen — alleen mét haak komt er een span omheen. */}\n {editField ? <span data-xicms-edit={editField}>{label}</span> : undefined}\n </Button>\n );\n}\n"],"mappings":";
|
|
1
|
+
{"version":3,"sources":["../../../src/blocks/ui/index.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * Gedeelde presentatie-primitieven voor de runtime-blokken — een dunne laag bovenop het Astryx\n * design system (Meta, StyleX). Blokken bouwen hierop i.p.v. losse `xicms-*`-classNames, zodat het\n * uiterlijk consistent is en we de onderlaag centraal kunnen wijzigen.\n *\n * De merk-look per site komt via de Astryx-token-mapping op `.xicms-root` (zie XiCMSProvider):\n * `--color-accent`, `--font-family-*`, `--radius-*` volgen de XiCMS-thema-tokens.\n */\n\nimport { type ReactNode } from \"react\";\nimport { Button, type ButtonVariant } from \"@astryxdesign/core/Button\";\n\nexport { VStack } from \"@astryxdesign/core/VStack\";\nexport { HStack } from \"@astryxdesign/core/HStack\";\nexport { Stack } from \"@astryxdesign/core/Stack\";\nexport { Grid } from \"@astryxdesign/core/Grid\";\nexport { Card } from \"@astryxdesign/core/Card\";\nexport { Heading } from \"@astryxdesign/core/Heading\";\nexport { Text } from \"@astryxdesign/core/Text\";\nexport { Divider } from \"@astryxdesign/core/Divider\";\nexport { Badge } from \"@astryxdesign/core/Badge\";\nexport { Avatar } from \"@astryxdesign/core/Avatar\";\n\n/** XiCMS-knopvarianten → Astryx-varianten (Astryx kent geen \"outline\" → ghost). */\nconst VARIANT_MAP: Record<string, ButtonVariant> = {\n primary: \"primary\",\n secondary: \"secondary\",\n outline: \"ghost\",\n ghost: \"ghost\",\n};\n\nexport interface BtnProps {\n label: string;\n href?: string;\n variant?: string;\n /** Marketing-knoppen zijn standaard wat groter dan de app-default. */\n size?: \"sm\" | \"md\" | \"lg\";\n type?: \"button\" | \"submit\" | \"reset\";\n target?: string;\n onClick?: () => void;\n children?: ReactNode;\n /**\n * Veldpad voor inline bewerken, bv. `\"buttonLabel\"` of `` `items.${i}.ctaLabel` ``. Het label\n * gaat dan door een eigen `<span>` met de `data-xicms-edit`-haak, zodat de knoptekst op het\n * canvas te bewerken is i.p.v. alleen via het zijpaneel. Astryx houdt `label` als toegankelijke\n * naam aan (het zet zelf `aria-label` zodra children afwijken), dus dit kost geen a11y.\n */\n editField?: string;\n}\n\n/**\n * Knop/CTA-helper bovenop Astryx `Button`. Rendert als link wanneer `href` gezet is. Houdt de\n * bestaande XiCMS-varianten (primary/secondary/outline) werkend via de mapping hierboven.\n */\nexport function Btn({ label, href, variant = \"primary\", size = \"lg\", type, target, onClick, editField }: BtnProps) {\n return (\n <Button\n label={label}\n variant={VARIANT_MAP[variant] ?? \"primary\"}\n size={size}\n href={href}\n target={href && target ? target : undefined}\n rel={target === \"_blank\" ? \"noopener noreferrer\" : undefined}\n type={href ? undefined : (type ?? \"button\")}\n clickAction={onClick}\n // 8.12: \"Omlijnd\" was een kale ghost-knop — losse tekst zonder rand, die naast een primaire\n // knop niet als knop las. Nu een echte omlijning (zie .xicms-btn-omlijnd).\n className={variant === \"outline\" ? \"xicms-btn-omlijnd\" : undefined}\n >\n {/* `undefined` laat Astryx op `label` terugvallen — alleen mét haak komt er een span omheen. */}\n {editField ? <span data-xicms-edit={editField}>{label}</span> : undefined}\n </Button>\n );\n}\n"],"mappings":";AAwEmB;AA5DnB,SAAS,cAAkC;AAE3C,SAAS,cAAc;AACvB,SAAS,cAAc;AACvB,SAAS,aAAa;AACtB,SAAS,YAAY;AACrB,SAAS,YAAY;AACrB,SAAS,eAAe;AACxB,SAAS,YAAY;AACrB,SAAS,eAAe;AACxB,SAAS,aAAa;AACtB,SAAS,cAAc;AAGvB,MAAM,cAA6C;AAAA,EACjD,SAAS;AAAA,EACT,WAAW;AAAA,EACX,SAAS;AAAA,EACT,OAAO;AACT;AAyBO,SAAS,IAAI,EAAE,OAAO,MAAM,UAAU,WAAW,OAAO,MAAM,MAAM,QAAQ,SAAS,UAAU,GAAa;AACjH,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,SAAS,YAAY,OAAO,KAAK;AAAA,MACjC;AAAA,MACA;AAAA,MACA,QAAQ,QAAQ,SAAS,SAAS;AAAA,MAClC,KAAK,WAAW,WAAW,wBAAwB;AAAA,MACnD,MAAM,OAAO,SAAa,QAAQ;AAAA,MAClC,aAAa;AAAA,MAGb,WAAW,YAAY,YAAY,sBAAsB;AAAA,MAGxD,sBAAY,oBAAC,UAAK,mBAAiB,WAAY,iBAAM,IAAU;AAAA;AAAA,EAClE;AAEJ;","names":[]}
|
|
@@ -8,11 +8,9 @@ import { useEditor } from "../editor/EditorProvider.js";
|
|
|
8
8
|
function EditBar() {
|
|
9
9
|
const { user, role } = useAuth();
|
|
10
10
|
const { open } = useEditor();
|
|
11
|
+
const siteId = useSiteConfig().siteId;
|
|
11
12
|
if (!canEdit(role) || !user || open) return null;
|
|
12
|
-
return /* @__PURE__ */ jsx(EditBarVoorEditor, { siteId
|
|
13
|
-
}
|
|
14
|
-
function useSiteConfigSiteId() {
|
|
15
|
-
return useSiteConfig().siteId;
|
|
13
|
+
return /* @__PURE__ */ jsx(EditBarVoorEditor, { siteId });
|
|
16
14
|
}
|
|
17
15
|
function EditBarVoorEditor({ siteId }) {
|
|
18
16
|
const nietLive = useNietLive(siteId);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/EditBar.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { useAuth } from \"../auth/AuthProvider\";\nimport { useSiteConfig } from \"../config\";\nimport { canEdit } from \"../auth/roles\";\nimport { useEditor, type Bewerkmodus } from \"../editor/EditorProvider\";\n\n/**\n * Zwevende werkbalk voor ingelogde beheerders/editors.\n *\n * Bewust met EIGEN klassen en eigen kleuren, niet met `.xicms-btn`. Die knop leest de\n * `--xicms-*`-tokens van de klantsite, en dit balkje zweeft over die site heen: het nam daardoor\n * half de huisstijl over (en op plekken waar de tokens niet in scope zijn, viel het terug op een\n * indigo die bij niets hoorde). Gereedschap hoort er als gereedschap uit te zien — hetzelfde op\n * elke site, herkenbaar los van de pagina eronder.\n *\n * ── VIER OPTIES, ALTIJD DEZELFDE (7.8) ────────────────────────────────────────────────────\n *\n * De balk had één hoofdactie (\"Pagina bewerken\") en verdween zodra je die indrukte: de editor\n * bracht zijn eigen balk mee, met andere knoppen op andere plekken. Je verloor dus je houvast op\n * het moment dat je het het hardst nodig had, en er was geen weg terug naar de live site zonder\n * de editor te sluiten.\n *\n * Nu staan er vier vaste opties — Live site · Bewerken · Beheren · Uitloggen — en die staan er in\n * béide toestanden, op dezelfde plek. `AdminModes` hieronder is letterlijk hetzelfde component in\n * de balk van de editor, zodat het overschakelen geen wisseling van balk is maar alleen een andere\n * knop die oplicht.\n */\nexport function EditBar() {\n // Bewust via useAuth (niet via het deprecated ContentProvider-pad): de EditBar hoort bij het\n // block-model en mag niet aan het legacy inline-systeem hangen.\n const { user, role } = useAuth();\n const { open } = useEditor();\n // Geen useBalkOffset meer: het pilletje zweeft óver de site (7.16), er wordt niets meer\n // omlaag geduwd — dus valt er ook niets meer onder een balk vandaan te schuiven.\n\n // In de bewerkmodus tekent de editor de balk (met dezelfde vier opties plus zijn gereedschap);\n // twee balken onder elkaar zou de site twee keer zo ver omlaag duwen.\n if (!canEdit(role) || !user || open) return null;\n\n return <EditBarVoorEditor siteId={
|
|
1
|
+
{"version":3,"sources":["../../src/components/EditBar.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { useAuth } from \"../auth/AuthProvider\";\nimport { useSiteConfig } from \"../config\";\nimport { canEdit } from \"../auth/roles\";\nimport { useEditor, type Bewerkmodus } from \"../editor/EditorProvider\";\n\n/**\n * Zwevende werkbalk voor ingelogde beheerders/editors.\n *\n * Bewust met EIGEN klassen en eigen kleuren, niet met `.xicms-btn`. Die knop leest de\n * `--xicms-*`-tokens van de klantsite, en dit balkje zweeft over die site heen: het nam daardoor\n * half de huisstijl over (en op plekken waar de tokens niet in scope zijn, viel het terug op een\n * indigo die bij niets hoorde). Gereedschap hoort er als gereedschap uit te zien — hetzelfde op\n * elke site, herkenbaar los van de pagina eronder.\n *\n * ── VIER OPTIES, ALTIJD DEZELFDE (7.8) ────────────────────────────────────────────────────\n *\n * De balk had één hoofdactie (\"Pagina bewerken\") en verdween zodra je die indrukte: de editor\n * bracht zijn eigen balk mee, met andere knoppen op andere plekken. Je verloor dus je houvast op\n * het moment dat je het het hardst nodig had, en er was geen weg terug naar de live site zonder\n * de editor te sluiten.\n *\n * Nu staan er vier vaste opties — Live site · Bewerken · Beheren · Uitloggen — en die staan er in\n * béide toestanden, op dezelfde plek. `AdminModes` hieronder is letterlijk hetzelfde component in\n * de balk van de editor, zodat het overschakelen geen wisseling van balk is maar alleen een andere\n * knop die oplicht.\n */\nexport function EditBar() {\n // Bewust via useAuth (niet via het deprecated ContentProvider-pad): de EditBar hoort bij het\n // block-model en mag niet aan het legacy inline-systeem hangen.\n const { user, role } = useAuth();\n const { open } = useEditor();\n // Vóór de vroege return: een hook ná `return null` wisselde van volgorde zodra de editor\n // dichtging (React-waarschuwing \"change in the order of Hooks\").\n const siteId = useSiteConfig().siteId;\n // Geen useBalkOffset meer: het pilletje zweeft óver de site (7.16), er wordt niets meer\n // omlaag geduwd — dus valt er ook niets meer onder een balk vandaan te schuiven.\n\n // In de bewerkmodus tekent de editor de balk (met dezelfde vier opties plus zijn gereedschap);\n // twee balken onder elkaar zou de site twee keer zo ver omlaag duwen.\n if (!canEdit(role) || !user || open) return null;\n\n return <EditBarVoorEditor siteId={siteId} />;\n}\n\n/**\n * Apart component zodat de draft-lezing hieronder ALLEEN draait voor een ingelogde editor — het\n * bovenliggende EditBar rendert voor elke bezoeker en mag geen Firestore aanraken.\n */\nfunction EditBarVoorEditor({ siteId }: { siteId: string }) {\n const nietLive = useNietLive(siteId);\n return (\n <div className=\"xicms-editbar\" role=\"toolbar\" aria-label=\"Beheer\" data-xicms-tooling=\"\">\n <EditbarBrand label=\"XiCMS\" />\n <AdminModes actief=\"live\" nietLive={nietLive} />\n </div>\n );\n}\n\n/**\n * Klein merkicoontje + naam (7.26) — gedeeld door de balk hierboven en die van de visuele editor\n * (VisualEditor.tsx), zodat \"hetzelfde gereedschap, ongeacht waar\" ook geldt voor het merkje zelf.\n * Puur decoratief: het icoon is `aria-hidden`, de naam draagt de eigenlijke betekenis.\n */\nexport function EditbarBrand({ label }: { label: string }) {\n return (\n <span className=\"xicms-editbar__brand\">\n {/* Bewust een sterretje, geen kruis: twee kruisende lijnen op deze schaal las als een\n sluitknopje naast de merknaam — precies de handeling die je hier niet wilt suggereren. */}\n <svg className=\"xicms-editbar__mark\" viewBox=\"0 0 16 16\" width=\"13\" height=\"13\" aria-hidden=\"true\">\n <rect width=\"16\" height=\"16\" rx=\"4.5\" fill=\"currentColor\" opacity=\"0.22\" />\n <path d=\"M8 3c.28 1.95 1.17 2.84 3.1 3.1-1.93.26-2.82 1.15-3.1 3.1-.28-1.95-1.17-2.84-3.1-3.1 1.93-.26 2.82-1.15 3.1-3.1z\" fill=\"currentColor\" />\n </svg>\n {label}\n </span>\n );\n}\n\n/**\n * Staat er op DEZE pagina een werkversie die afwijkt van wat live staat? (7.24)\n *\n * Eén lezing van de draft bij het laden (geen listener): dit is een signaal bij binnenkomst, geen\n * monitor. De vergelijking is dezelfde als in /beheer → Pagina's (`heeftOngepubliceerdeWijzigingen`).\n * Faalt stil — een hik mag de balk niet raken, en zonder signaal is er hoogstens geen stipje.\n */\nfunction useNietLive(siteId: string): boolean {\n const [nietLive, setNietLive] = useState(false);\n useEffect(() => {\n let actief = true;\n const raw = window.location.pathname || \"/\";\n const path = raw === \"/\" ? \"/\" : raw.replace(/\\/$/, \"\");\n void import(\"../admin/lib/pagesClient\")\n .then(async ({ getDraftByPath, heeftOngepubliceerdeWijzigingen }) => {\n const d = await getDraftByPath(siteId, path);\n if (actief && d) setNietLive(heeftOngepubliceerdeWijzigingen(d));\n })\n .catch(() => undefined);\n return () => {\n actief = false;\n };\n }, [siteId]);\n return nietLive;\n}\n\n/**\n * De vier opties zelf, los zodat de editor ze kan hergebruiken.\n *\n * `actief` zegt welke toestand je nú bekijkt, niet wat de knop doet: \"Live site\" is ingedrukt\n * zolang je de pagina ziet zoals een bezoeker hem ziet. Zonder die markering is er geen verschil\n * te zien tussen \"ik kijk naar de live site\" en \"ik bewerk maar heb nog niets veranderd\", en dat\n * verschil is precies waar deze balk over gaat.\n */\nexport function AdminModes({\n actief,\n voorSwitch,\n nietLive = false,\n}: {\n actief: Bewerkmodus;\n /** 7.24: toon een stipje op \"Bewerken\" — er ligt een werkversie die niemand ziet. */\n nietLive?: boolean;\n /**\n * Kans voor de editor om iets te zeggen vóór je weggaat — bijvoorbeeld dat er nog wijzigingen\n * klaarstaan die niet live staan. Bewust niet blokkerend: sluiten bewaart je werk, dus een\n * bevestigingsvraag zou alleen maar in de weg zitten. Melden is genoeg, tegenhouden is te veel.\n */\n voorSwitch?: (modus: Bewerkmodus) => void;\n}) {\n const { demo, signOut } = useAuth();\n const { zetModus } = useEditor();\n\n const knop = (modus: Bewerkmodus, label: string, titel: string) => (\n <button\n type=\"button\"\n className={`xicms-editbar__btn${actief === modus ? \" is-actief\" : \"\"}${modus === \"bewerken\" && nietLive ? \" heeft-wijzigingen\" : \"\"}`}\n aria-pressed={actief === modus}\n title={titel}\n onClick={() => {\n if (modus !== actief) voorSwitch?.(modus);\n zetModus(modus);\n }}\n >\n {label}\n </button>\n );\n\n return (\n <>\n {knop(\"live\", \"Live site\", \"De pagina precies zoals een bezoeker hem ziet\")}\n {knop(\"bewerken\", \"Bewerken\", nietLive ? \"Er staan wijzigingen klaar die nog niet live zijn\" : \"Dezelfde pagina, met de werkversie en potlood/tandwiel per blok\")}\n <span className=\"xicms-editbar__sep\" aria-hidden=\"true\" />\n {/* In de demo bestaat er geen /beheer-route; daar bewerk je alleen op de pagina. */}\n {!demo && (\n <a href=\"/beheer\" className=\"xicms-editbar__btn\" title=\"Pagina's, media, instellingen, webwinkel\">\n Beheren\n </a>\n )}\n <button type=\"button\" className=\"xicms-editbar__btn\" onClick={() => void signOut()}>\n Uitloggen\n </button>\n </>\n );\n}\n"],"mappings":";AA4CS,SAwGL,UAxGK,KAUL,YAVK;AA1CT,SAAS,WAAW,gBAAgB;AACpC,SAAS,eAAe;AACxB,SAAS,qBAAqB;AAC9B,SAAS,eAAe;AACxB,SAAS,iBAAmC;AAuBrC,SAAS,UAAU;AAGxB,QAAM,EAAE,MAAM,KAAK,IAAI,QAAQ;AAC/B,QAAM,EAAE,KAAK,IAAI,UAAU;AAG3B,QAAM,SAAS,cAAc,EAAE;AAM/B,MAAI,CAAC,QAAQ,IAAI,KAAK,CAAC,QAAQ,KAAM,QAAO;AAE5C,SAAO,oBAAC,qBAAkB,QAAgB;AAC5C;AAMA,SAAS,kBAAkB,EAAE,OAAO,GAAuB;AACzD,QAAM,WAAW,YAAY,MAAM;AACnC,SACE,qBAAC,SAAI,WAAU,iBAAgB,MAAK,WAAU,cAAW,UAAS,sBAAmB,IACnF;AAAA,wBAAC,gBAAa,OAAM,SAAQ;AAAA,IAC5B,oBAAC,cAAW,QAAO,QAAO,UAAoB;AAAA,KAChD;AAEJ;AAOO,SAAS,aAAa,EAAE,MAAM,GAAsB;AACzD,SACE,qBAAC,UAAK,WAAU,wBAGd;AAAA,yBAAC,SAAI,WAAU,uBAAsB,SAAQ,aAAY,OAAM,MAAK,QAAO,MAAK,eAAY,QAC1F;AAAA,0BAAC,UAAK,OAAM,MAAK,QAAO,MAAK,IAAG,OAAM,MAAK,gBAAe,SAAQ,QAAO;AAAA,MACzE,oBAAC,UAAK,GAAE,oHAAmH,MAAK,gBAAe;AAAA,OACjJ;AAAA,IACC;AAAA,KACH;AAEJ;AASA,SAAS,YAAY,QAAyB;AAC5C,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAC9C,YAAU,MAAM;AACd,QAAI,SAAS;AACb,UAAM,MAAM,OAAO,SAAS,YAAY;AACxC,UAAM,OAAO,QAAQ,MAAM,MAAM,IAAI,QAAQ,OAAO,EAAE;AACtD,SAAK,OAAO,0BAA0B,EACnC,KAAK,OAAO,EAAE,gBAAgB,gCAAgC,MAAM;AACnE,YAAM,IAAI,MAAM,eAAe,QAAQ,IAAI;AAC3C,UAAI,UAAU,EAAG,aAAY,gCAAgC,CAAC,CAAC;AAAA,IACjE,CAAC,EACA,MAAM,MAAM,MAAS;AACxB,WAAO,MAAM;AACX,eAAS;AAAA,IACX;AAAA,EACF,GAAG,CAAC,MAAM,CAAC;AACX,SAAO;AACT;AAUO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA,WAAW;AACb,GAUG;AACD,QAAM,EAAE,MAAM,QAAQ,IAAI,QAAQ;AAClC,QAAM,EAAE,SAAS,IAAI,UAAU;AAE/B,QAAM,OAAO,CAAC,OAAoB,OAAe,UAC/C;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,WAAW,qBAAqB,WAAW,QAAQ,eAAe,EAAE,GAAG,UAAU,cAAc,WAAW,uBAAuB,EAAE;AAAA,MACnI,gBAAc,WAAW;AAAA,MACzB,OAAO;AAAA,MACP,SAAS,MAAM;AACb,YAAI,UAAU,OAAQ,cAAa,KAAK;AACxC,iBAAS,KAAK;AAAA,MAChB;AAAA,MAEC;AAAA;AAAA,EACH;AAGF,SACE,iCACG;AAAA,SAAK,QAAQ,aAAa,+CAA+C;AAAA,IACzE,KAAK,YAAY,YAAY,WAAW,sDAAsD,iEAAiE;AAAA,IAChK,oBAAC,UAAK,WAAU,sBAAqB,eAAY,QAAO;AAAA,IAEvD,CAAC,QACA,oBAAC,OAAE,MAAK,WAAU,WAAU,sBAAqB,OAAM,4CAA2C,qBAElG;AAAA,IAEF,oBAAC,YAAO,MAAK,UAAS,WAAU,sBAAqB,SAAS,MAAM,KAAK,QAAQ,GAAG,uBAEpF;AAAA,KACF;AAEJ;","names":[]}
|
package/dist/components/News.js
CHANGED
|
@@ -18,7 +18,7 @@ function useNews(opts = {}) {
|
|
|
18
18
|
const live = opts.live ?? editor;
|
|
19
19
|
const cap = Math.min(opts.max ?? 50, 50);
|
|
20
20
|
const zichtbaar = (a) => a.published && (!a.date || String(a.date).slice(0, 10) <= (/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
|
|
21
|
-
const seed = serverNews ? (onlyPublished ? serverNews.filter(
|
|
21
|
+
const seed = serverNews ? (onlyPublished ? serverNews.filter((a) => a.published) : serverNews).slice(0, cap) : [];
|
|
22
22
|
const [articles, setArticles] = useState(seed);
|
|
23
23
|
const [loading, setLoading] = useState(!serverNews);
|
|
24
24
|
useEffect(() => {
|
|
@@ -88,23 +88,31 @@ function NewsList({ basePath = "/nieuws", className, emptyLabel = "Nog geen nieu
|
|
|
88
88
|
if (articles.length === 0) return /* @__PURE__ */ jsx("p", { children: emptyLabel });
|
|
89
89
|
const all = typeof limit === "number" ? articles.slice(0, limit) : articles;
|
|
90
90
|
const shown = paged ? all.slice(0, visible) : all;
|
|
91
|
-
return
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
91
|
+
return (
|
|
92
|
+
/* 8.12: een raster van gelijke kaarten. Tot nu toe stond elk bericht onder het vorige met zijn
|
|
93
|
+
cover op volle breedte en natuurlijke hoogte — één staande foto was een heel scherm. Nu een
|
|
94
|
+
vaste beeldverhouding, de titel in de tekstkleur (niet het blauw van een kale link) en de
|
|
95
|
+
hele kaart klikbaar. */
|
|
96
|
+
/* @__PURE__ */ jsxs("div", { className: ["xicms-nieuwslijst", className].filter(Boolean).join(" "), children: [
|
|
97
|
+
shown.map((a, i) => /* @__PURE__ */ jsxs("article", { className: "xicms-card xicms-nieuwskaart", children: [
|
|
98
|
+
a.coverUrl && // Eerste cover boven de vouw = vaak de LCP → priority (geen lazy-load-vertraging).
|
|
99
|
+
/* @__PURE__ */ jsx(Img, { src: a.coverUrl, alt: "", width: 1200, height: 750, sizes: "(max-width: 720px) 100vw, 33vw", priority: i === 0, className: "xicms-nieuwskaart__beeld" }),
|
|
100
|
+
/* @__PURE__ */ jsxs("div", { className: "xicms-nieuwskaart__tekst", children: [
|
|
101
|
+
/* @__PURE__ */ jsx("p", { className: "xicms-nieuwskaart__datum", children: a.date }),
|
|
102
|
+
/* @__PURE__ */ jsxs("h2", { className: "xicms-nieuwskaart__titel", children: [
|
|
103
|
+
/* @__PURE__ */ jsx("a", { href: `${basePath}/${a.slug ?? a.id}`, children: a.title }),
|
|
104
|
+
!a.published && /* @__PURE__ */ jsx("span", { className: "xicms-nieuwskaart__concept", children: "(concept)" })
|
|
105
|
+
] }),
|
|
106
|
+
a.excerpt && /* @__PURE__ */ jsx("p", { className: "xicms-nieuwskaart__samenvatting", children: a.excerpt })
|
|
107
|
+
] })
|
|
108
|
+
] }, a.id)),
|
|
109
|
+
paged && shown.length < all.length && /* @__PURE__ */ jsx("div", { style: { textAlign: "center" }, children: /* @__PURE__ */ jsxs("button", { type: "button", className: "xicms-btn", onClick: () => setVisible((v) => v + pageSize), children: [
|
|
110
|
+
"Meer laden (",
|
|
111
|
+
all.length - shown.length,
|
|
112
|
+
")"
|
|
113
|
+
] }) })
|
|
114
|
+
] })
|
|
115
|
+
);
|
|
108
116
|
}
|
|
109
117
|
function articleBlogPostingJsonLd(article, opts = {}) {
|
|
110
118
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/News.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { useSiteConfig } from \"../config\";\nimport DOMPurify from \"dompurify\";\nimport { useAuth } from \"../auth/AuthProvider\";\nimport { canEdit } from \"../auth/roles\";\nimport { useBlockData } from \"../blocks/context\";\nimport { isDemoActive, getDemoNews } from \"../demo/demoStore\";\nimport { Img } from \"../media/ImageContext\";\nimport type { NewsArticle } from \"../content/types\";\nimport { jsonLdHtml } from \"../blocks/builtins/jsonld\";\n\n/**\n * Lijst van nieuwsartikelen voor de huidige site.\n * - `live: false` (standaard publiek): één keer ophalen met getDocs → geen per-bezoeker listener\n * (scheelt Firestore-kosten op druk bezochte sites).\n * - `live: true` (admin): realtime via onSnapshot, zodat wijzigingen meteen zichtbaar zijn.\n */\nexport function useNews(opts: { onlyPublished?: boolean; live?: boolean; max?: number } = {}): {\n articles: NewsArticle[];\n loading: boolean;\n} {\n const { siteId } = useSiteConfig();\n // Via useAuth (niet via het deprecated ContentProvider-pad — News hoort niet aan legacy te hangen).\n const { role } = useAuth();\n const editor = canEdit(role);\n // Server-gefetchte nieuwsdata (content-bundle 5.7): als de publieke route deze via\n // <BlockDataProvider data={{ news }}> meegeeft, renderen we daaruit (SSR, geen browser-Firestore).\n // Afwezig (admin/preview/dev, of geen bundle) → val terug op het Firestore-pad hieronder.\n const serverNews = useBlockData<NewsArticle[]>(\"news\");\n const onlyPublished = opts.onlyPublished ?? !editor;\n const live = opts.live ?? editor; // editors zien live; publiek één keer\n // Begrens de query: zonder cap zou een nieuws-rijke site ALLE artikelen downloaden bij elk bezoek.\n const cap = Math.min(opts.max ?? 50, 50);\n // Bundle-data staat al bij de EERSTE render klaar (server + hydration, identiek): de nieuwsinhoud\n // zit zo in de SSR-HTML (SEO) i.p.v. pas na een client-effect (geen \"laden…\"-flits).\n // 7.24: gepubliceerd én niet in de toekomst gedateerd (ingepland artikel). Editors zien alles.\n const zichtbaar = (a: NewsArticle) => a.published && (!a.date || String(a.date).slice(0, 10) <= new Date().toISOString().slice(0, 10));\n const seed = serverNews ? (onlyPublished ? serverNews.filter(zichtbaar) : serverNews).slice(0, cap) : [];\n const [articles, setArticles] = useState<NewsArticle[]>(seed);\n const [loading, setLoading] = useState(!serverNews);\n\n useEffect(() => {\n // Bundle-data aanwezig? Render daaruit — geen listener, geen getDocs.\n if (serverNews) {\n const all = onlyPublished ? serverNews.filter(zichtbaar) : serverNews;\n setArticles(all.slice(0, cap));\n setLoading(false);\n return;\n }\n // Demo-modus: nieuws komt uit de lokale seed (geen Firebase), zodat nieuws-blokken/widgets\n // in de demo ook gevuld zijn.\n if (isDemoActive()) {\n const all = getDemoNews(siteId);\n setArticles((onlyPublished ? all.filter(zichtbaar) : all).slice(0, cap));\n setLoading(false);\n return;\n }\n // Firestore DYNAMISCH. Met bundle-data (hierboven) komen we hier niet eens; zonder bundle\n // laadt de SDK pas wanneer er echt nieuws opgehaald moet worden. Statisch importeren zette hem\n // in de bundel van élke publieke pagina, ook die zonder nieuws.\n let afgebroken = false;\n let unsub: (() => void) | undefined;\n void (async () => {\n const [{ getDb }, fs] = await Promise.all([\n import(\"../firebase/client\"),\n import(\"firebase/firestore\"),\n ]);\n const db = getDb();\n if (!db || afgebroken) {\n setLoading(false);\n return;\n }\n const base = fs.collection(db, \"sites\", siteId, \"news\");\n const q = onlyPublished\n ? fs.query(base, fs.where(\"published\", \"==\", true), fs.orderBy(\"date\", \"desc\"), fs.limit(cap))\n : fs.query(base, fs.orderBy(\"date\", \"desc\"), fs.limit(cap));\n const map = (snap: { docs: { id: string; data: () => unknown }[] }) => {\n const rijen = snap.docs.map((d) => ({ id: d.id, ...(d.data() as Omit<NewsArticle, \"id\">) }));\n setArticles(onlyPublished ? rijen.filter(zichtbaar) : rijen);\n };\n\n if (!live) {\n try {\n const snap = await fs.getDocs(q);\n if (!afgebroken) {\n map(snap);\n setLoading(false);\n }\n } catch {\n if (!afgebroken) setLoading(false);\n }\n return;\n }\n unsub = fs.onSnapshot(\n q,\n (snap) => {\n map(snap);\n setLoading(false);\n },\n () => setLoading(false),\n );\n })();\n return () => {\n afgebroken = true;\n unsub?.();\n };\n }, [siteId, onlyPublished, live, cap, serverNews]);\n\n return { articles, loading };\n}\n\ninterface NewsListProps {\n /** Basispad voor links naar een artikel, bv. \"/nieuws\". */\n basePath?: string;\n className?: string;\n emptyLabel?: string;\n /** Maximaal aantal artikelen om te tonen. */\n limit?: number;\n /** Toon eerst dit aantal en laad de rest met een \"Meer laden\"-knop (0/leeg = alles ineens). */\n pageSize?: number;\n}\n\n/** Standaardweergave van nieuwsartikelen als kaartenlijst, optioneel gepagineerd. */\nexport function NewsList({ basePath = \"/nieuws\", className, emptyLabel = \"Nog geen nieuws.\", limit, pageSize }: NewsListProps) {\n const { articles, loading } = useNews({ max: limit });\n const paged = typeof pageSize === \"number\" && pageSize > 0;\n const [visible, setVisible] = useState(paged ? pageSize : Infinity);\n\n if (loading) return <p>Nieuws laden…</p>;\n if (articles.length === 0) return <p>{emptyLabel}</p>;\n\n const all = typeof limit === \"number\" ? articles.slice(0, limit) : articles;\n const shown = paged ? all.slice(0, visible) : all;\n\n return (\n <div className={className} style={{ display: \"grid\", gap: \"1rem\" }}>\n {shown.map((a, i) => (\n <article key={a.id} className=\"xicms-card\" style={{ padding: \"1.25rem\" }}>\n {a.coverUrl && (\n // Eerste cover boven de vouw = vaak de LCP → priority (geen lazy-load-vertraging).\n <Img src={a.coverUrl} alt=\"\" width={1200} height={700} sizes=\"(max-width: 720px) 100vw, 33vw\" priority={i === 0} style={{ width: \"100%\", height: \"auto\", borderRadius: 8, marginBottom: \"0.75rem\" }} />\n )}\n <p style={{ fontSize: 12, opacity: 0.7, margin: 0 }}>{a.date}</p>\n {/* h2: de lijst staat direct onder de pagina-h1 — h3 zou een kopniveau overslaan. */}\n <h2 style={{ margin: \"0.25rem 0\", fontSize: \"1.35rem\" }}>\n <a href={`${basePath}/${a.slug ?? a.id}`}>{a.title}</a>\n {!a.published && <span style={{ marginLeft: 8, fontSize: 12, color: \"#b45309\" }}>(concept)</span>}\n </h2>\n {a.excerpt && <p style={{ margin: 0, opacity: 0.85 }}>{a.excerpt}</p>}\n </article>\n ))}\n {paged && shown.length < all.length && (\n <div style={{ textAlign: \"center\" }}>\n <button type=\"button\" className=\"xicms-btn\" onClick={() => setVisible((v) => v + (pageSize as number))}>\n Meer laden ({all.length - shown.length})\n </button>\n </div>\n )}\n </div>\n );\n}\n\n/** BlogPosting-JSON-LD voor een artikel — puur, zodat het testbaar is. */\nexport function articleBlogPostingJsonLd(article: NewsArticle, opts: { siteName?: string; url?: string } = {}): Record<string, unknown> {\n return {\n \"@context\": \"https://schema.org\",\n \"@type\": \"BlogPosting\",\n headline: article.title,\n datePublished: article.date,\n ...(article.excerpt ? { description: article.excerpt } : {}),\n ...(article.coverUrl ? { image: article.coverUrl } : {}),\n ...(opts.url ? { url: opts.url, mainEntityOfPage: opts.url } : {}),\n ...(opts.siteName ? { publisher: { \"@type\": \"Organization\", name: opts.siteName } } : {}),\n };\n}\n\n/** Detailweergave van één artikel, met BlogPosting-JSON-LD voor zoekmachines. */\nexport function NewsArticleView({ article, jsonLd = true }: { article: NewsArticle; jsonLd?: boolean }) {\n const { name } = useSiteConfig();\n // De render is de veiligheidsgrens: rich-text gaat door DOMPurify. Die heeft echter een echte DOM\n // nodig, dus op de server is hij geen optie — waardoor de opmaak vroeger pas ná hydratie verscheen.\n // Een crawler zag dan alleen kale tekst, en de lezer een layoutverschuiving.\n //\n // Komt het artikel uit de content-bundle (server-side meegegeven via BlockDataProvider), dan is\n // het al server-side gesaneerd in `getBundleNews` en mogen we het direct renderen — óók in de\n // SSR-HTML. Alleen client-gefetchte artikelen (admin/preview/dev, of geen bundle) wachten nog op\n // DOMPurify; die kunnen immers ongesaneerd uit Firestore komen.\n const serverNews = useBlockData<NewsArticle[]>(\"news\");\n const trusted = Boolean(\n article.bodyHtml && serverNews?.some((a) => a.slug === article.slug && a.bodyHtml === article.bodyHtml),\n );\n const [html, setHtml] = useState<string | null>(trusted ? (article.bodyHtml ?? null) : null);\n useEffect(() => {\n // Ook voor vertrouwde HTML: opnieuw saneren is idempotent en houdt de client-grens hard.\n setHtml(article.bodyHtml ? DOMPurify.sanitize(article.bodyHtml) : null);\n }, [article.bodyHtml]);\n // Fallback tot de sanitizer klaar is: kale tekst (tags gestript, gerenderd als tekstnodes — veilig).\n const fallbackText = article.body ?? (article.bodyHtml ? article.bodyHtml.replace(/<[^>]*>/g, \" \") : \"\");\n return (\n <article>\n <p style={{ fontSize: 12, opacity: 0.6 }}>{article.date}</p>\n <h1>{article.title}</h1>\n {article.coverUrl && (\n <Img src={article.coverUrl} alt=\"\" width={1200} height={700} sizes=\"(max-width: 900px) 100vw, 800px\" priority style={{ width: \"100%\", height: \"auto\", borderRadius: 12, margin: \"1rem 0\" }} />\n )}\n {html ? (\n // eslint-disable-next-line react/no-danger\n <div className=\"xicms-richtext\" dangerouslySetInnerHTML={{ __html: html }} />\n ) : (\n fallbackText.split(/\\n{2,}/).map((p, i) => (\n <p key={i} style={{ whiteSpace: \"pre-wrap\" }}>\n {p}\n </p>\n ))\n )}\n {jsonLd && (\n <script\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{ __html: jsonLdHtml(articleBlogPostingJsonLd(article, { siteName: name })) }}\n />\n )}\n </article>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/News.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { useSiteConfig } from \"../config\";\nimport DOMPurify from \"dompurify\";\nimport { useAuth } from \"../auth/AuthProvider\";\nimport { canEdit } from \"../auth/roles\";\nimport { useBlockData } from \"../blocks/context\";\nimport { isDemoActive, getDemoNews } from \"../demo/demoStore\";\nimport { Img } from \"../media/ImageContext\";\nimport type { NewsArticle } from \"../content/types\";\nimport { jsonLdHtml } from \"../blocks/builtins/jsonld\";\n\n/**\n * Lijst van nieuwsartikelen voor de huidige site.\n * - `live: false` (standaard publiek): één keer ophalen met getDocs → geen per-bezoeker listener\n * (scheelt Firestore-kosten op druk bezochte sites).\n * - `live: true` (admin): realtime via onSnapshot, zodat wijzigingen meteen zichtbaar zijn.\n */\nexport function useNews(opts: { onlyPublished?: boolean; live?: boolean; max?: number } = {}): {\n articles: NewsArticle[];\n loading: boolean;\n} {\n const { siteId } = useSiteConfig();\n // Via useAuth (niet via het deprecated ContentProvider-pad — News hoort niet aan legacy te hangen).\n const { role } = useAuth();\n const editor = canEdit(role);\n // Server-gefetchte nieuwsdata (content-bundle 5.7): als de publieke route deze via\n // <BlockDataProvider data={{ news }}> meegeeft, renderen we daaruit (SSR, geen browser-Firestore).\n // Afwezig (admin/preview/dev, of geen bundle) → val terug op het Firestore-pad hieronder.\n const serverNews = useBlockData<NewsArticle[]>(\"news\");\n const onlyPublished = opts.onlyPublished ?? !editor;\n const live = opts.live ?? editor; // editors zien live; publiek één keer\n // Begrens de query: zonder cap zou een nieuws-rijke site ALLE artikelen downloaden bij elk bezoek.\n const cap = Math.min(opts.max ?? 50, 50);\n // Bundle-data staat al bij de EERSTE render klaar (server + hydration, identiek): de nieuwsinhoud\n // zit zo in de SSR-HTML (SEO) i.p.v. pas na een client-effect (geen \"laden…\"-flits).\n // 7.24: gepubliceerd én niet in de toekomst gedateerd (ingepland artikel). Editors zien alles.\n const zichtbaar = (a: NewsArticle) => a.published && (!a.date || String(a.date).slice(0, 10) <= new Date().toISOString().slice(0, 10));\n // Seed zonder datumfilter (8.1): de server-data is al gefilterd op het moment van ophalen, en\n // `new Date()` hier gaf een andere uitkomst op de server (gecachete HTML) dan bij de hydratie.\n // Het effect hieronder past het datumfilter direct daarna toe.\n const seed = serverNews ? (onlyPublished ? serverNews.filter((a) => a.published) : serverNews).slice(0, cap) : [];\n const [articles, setArticles] = useState<NewsArticle[]>(seed);\n const [loading, setLoading] = useState(!serverNews);\n\n useEffect(() => {\n // Bundle-data aanwezig? Render daaruit — geen listener, geen getDocs.\n if (serverNews) {\n const all = onlyPublished ? serverNews.filter(zichtbaar) : serverNews;\n setArticles(all.slice(0, cap));\n setLoading(false);\n return;\n }\n // Demo-modus: nieuws komt uit de lokale seed (geen Firebase), zodat nieuws-blokken/widgets\n // in de demo ook gevuld zijn.\n if (isDemoActive()) {\n const all = getDemoNews(siteId);\n setArticles((onlyPublished ? all.filter(zichtbaar) : all).slice(0, cap));\n setLoading(false);\n return;\n }\n // Firestore DYNAMISCH. Met bundle-data (hierboven) komen we hier niet eens; zonder bundle\n // laadt de SDK pas wanneer er echt nieuws opgehaald moet worden. Statisch importeren zette hem\n // in de bundel van élke publieke pagina, ook die zonder nieuws.\n let afgebroken = false;\n let unsub: (() => void) | undefined;\n void (async () => {\n const [{ getDb }, fs] = await Promise.all([\n import(\"../firebase/client\"),\n import(\"firebase/firestore\"),\n ]);\n const db = getDb();\n if (!db || afgebroken) {\n setLoading(false);\n return;\n }\n const base = fs.collection(db, \"sites\", siteId, \"news\");\n const q = onlyPublished\n ? fs.query(base, fs.where(\"published\", \"==\", true), fs.orderBy(\"date\", \"desc\"), fs.limit(cap))\n : fs.query(base, fs.orderBy(\"date\", \"desc\"), fs.limit(cap));\n const map = (snap: { docs: { id: string; data: () => unknown }[] }) => {\n const rijen = snap.docs.map((d) => ({ id: d.id, ...(d.data() as Omit<NewsArticle, \"id\">) }));\n setArticles(onlyPublished ? rijen.filter(zichtbaar) : rijen);\n };\n\n if (!live) {\n try {\n const snap = await fs.getDocs(q);\n if (!afgebroken) {\n map(snap);\n setLoading(false);\n }\n } catch {\n if (!afgebroken) setLoading(false);\n }\n return;\n }\n unsub = fs.onSnapshot(\n q,\n (snap) => {\n map(snap);\n setLoading(false);\n },\n () => setLoading(false),\n );\n })();\n return () => {\n afgebroken = true;\n unsub?.();\n };\n }, [siteId, onlyPublished, live, cap, serverNews]);\n\n return { articles, loading };\n}\n\ninterface NewsListProps {\n /** Basispad voor links naar een artikel, bv. \"/nieuws\". */\n basePath?: string;\n className?: string;\n emptyLabel?: string;\n /** Maximaal aantal artikelen om te tonen. */\n limit?: number;\n /** Toon eerst dit aantal en laad de rest met een \"Meer laden\"-knop (0/leeg = alles ineens). */\n pageSize?: number;\n}\n\n/** Standaardweergave van nieuwsartikelen als kaartenlijst, optioneel gepagineerd. */\nexport function NewsList({ basePath = \"/nieuws\", className, emptyLabel = \"Nog geen nieuws.\", limit, pageSize }: NewsListProps) {\n const { articles, loading } = useNews({ max: limit });\n const paged = typeof pageSize === \"number\" && pageSize > 0;\n const [visible, setVisible] = useState(paged ? pageSize : Infinity);\n\n if (loading) return <p>Nieuws laden…</p>;\n if (articles.length === 0) return <p>{emptyLabel}</p>;\n\n const all = typeof limit === \"number\" ? articles.slice(0, limit) : articles;\n const shown = paged ? all.slice(0, visible) : all;\n\n return (\n /* 8.12: een raster van gelijke kaarten. Tot nu toe stond elk bericht onder het vorige met zijn\n cover op volle breedte en natuurlijke hoogte — één staande foto was een heel scherm. Nu een\n vaste beeldverhouding, de titel in de tekstkleur (niet het blauw van een kale link) en de\n hele kaart klikbaar. */\n <div className={[\"xicms-nieuwslijst\", className].filter(Boolean).join(\" \")}>\n {shown.map((a, i) => (\n <article key={a.id} className=\"xicms-card xicms-nieuwskaart\">\n {a.coverUrl && (\n // Eerste cover boven de vouw = vaak de LCP → priority (geen lazy-load-vertraging).\n <Img src={a.coverUrl} alt=\"\" width={1200} height={750} sizes=\"(max-width: 720px) 100vw, 33vw\" priority={i === 0} className=\"xicms-nieuwskaart__beeld\" />\n )}\n <div className=\"xicms-nieuwskaart__tekst\">\n <p className=\"xicms-nieuwskaart__datum\">{a.date}</p>\n {/* h2: de lijst staat direct onder de pagina-h1 — h3 zou een kopniveau overslaan. */}\n <h2 className=\"xicms-nieuwskaart__titel\">\n <a href={`${basePath}/${a.slug ?? a.id}`}>{a.title}</a>\n {!a.published && <span className=\"xicms-nieuwskaart__concept\">(concept)</span>}\n </h2>\n {a.excerpt && <p className=\"xicms-nieuwskaart__samenvatting\">{a.excerpt}</p>}\n </div>\n </article>\n ))}\n {paged && shown.length < all.length && (\n <div style={{ textAlign: \"center\" }}>\n <button type=\"button\" className=\"xicms-btn\" onClick={() => setVisible((v) => v + (pageSize as number))}>\n Meer laden ({all.length - shown.length})\n </button>\n </div>\n )}\n </div>\n );\n}\n\n/** BlogPosting-JSON-LD voor een artikel — puur, zodat het testbaar is. */\nexport function articleBlogPostingJsonLd(article: NewsArticle, opts: { siteName?: string; url?: string } = {}): Record<string, unknown> {\n return {\n \"@context\": \"https://schema.org\",\n \"@type\": \"BlogPosting\",\n headline: article.title,\n datePublished: article.date,\n ...(article.excerpt ? { description: article.excerpt } : {}),\n ...(article.coverUrl ? { image: article.coverUrl } : {}),\n ...(opts.url ? { url: opts.url, mainEntityOfPage: opts.url } : {}),\n ...(opts.siteName ? { publisher: { \"@type\": \"Organization\", name: opts.siteName } } : {}),\n };\n}\n\n/** Detailweergave van één artikel, met BlogPosting-JSON-LD voor zoekmachines. */\nexport function NewsArticleView({ article, jsonLd = true }: { article: NewsArticle; jsonLd?: boolean }) {\n const { name } = useSiteConfig();\n // De render is de veiligheidsgrens: rich-text gaat door DOMPurify. Die heeft echter een echte DOM\n // nodig, dus op de server is hij geen optie — waardoor de opmaak vroeger pas ná hydratie verscheen.\n // Een crawler zag dan alleen kale tekst, en de lezer een layoutverschuiving.\n //\n // Komt het artikel uit de content-bundle (server-side meegegeven via BlockDataProvider), dan is\n // het al server-side gesaneerd in `getBundleNews` en mogen we het direct renderen — óók in de\n // SSR-HTML. Alleen client-gefetchte artikelen (admin/preview/dev, of geen bundle) wachten nog op\n // DOMPurify; die kunnen immers ongesaneerd uit Firestore komen.\n const serverNews = useBlockData<NewsArticle[]>(\"news\");\n const trusted = Boolean(\n article.bodyHtml && serverNews?.some((a) => a.slug === article.slug && a.bodyHtml === article.bodyHtml),\n );\n const [html, setHtml] = useState<string | null>(trusted ? (article.bodyHtml ?? null) : null);\n useEffect(() => {\n // Ook voor vertrouwde HTML: opnieuw saneren is idempotent en houdt de client-grens hard.\n setHtml(article.bodyHtml ? DOMPurify.sanitize(article.bodyHtml) : null);\n }, [article.bodyHtml]);\n // Fallback tot de sanitizer klaar is: kale tekst (tags gestript, gerenderd als tekstnodes — veilig).\n const fallbackText = article.body ?? (article.bodyHtml ? article.bodyHtml.replace(/<[^>]*>/g, \" \") : \"\");\n return (\n <article>\n <p style={{ fontSize: 12, opacity: 0.6 }}>{article.date}</p>\n <h1>{article.title}</h1>\n {article.coverUrl && (\n <Img src={article.coverUrl} alt=\"\" width={1200} height={700} sizes=\"(max-width: 900px) 100vw, 800px\" priority style={{ width: \"100%\", height: \"auto\", borderRadius: 12, margin: \"1rem 0\" }} />\n )}\n {html ? (\n // eslint-disable-next-line react/no-danger\n <div className=\"xicms-richtext\" dangerouslySetInnerHTML={{ __html: html }} />\n ) : (\n fallbackText.split(/\\n{2,}/).map((p, i) => (\n <p key={i} style={{ whiteSpace: \"pre-wrap\" }}>\n {p}\n </p>\n ))\n )}\n {jsonLd && (\n <script\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{ __html: jsonLdHtml(articleBlogPostingJsonLd(article, { siteName: name })) }}\n />\n )}\n </article>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -10,4 +10,10 @@ export declare function PageView({ path }: {
|
|
|
10
10
|
* Telt een gebeurtenis (7.24): "formulier" verstuurd, "uitgaand" geklikt, enz. Zelfde teller,
|
|
11
11
|
* zelfde privacy-regels; fire-and-forget en nooit een fout naar de bezoeker.
|
|
12
12
|
*/
|
|
13
|
-
export declare function trackEvent(siteId: string, event: "formulier" | "bestelling" | "uitgaand" | "telefoon" | "mail" | "whatsapp", path?: string): void;
|
|
13
|
+
export declare function trackEvent(siteId: string, event: "formulier" | "bestelling" | "uitgaand" | "telefoon" | "mail" | "whatsapp" | "niet-gevonden", path?: string): void;
|
|
14
|
+
/**
|
|
15
|
+
* 404-monitor (8.4): zet dit op de niet-gevonden-pagina van de site. Meldt het pad één keer per
|
|
16
|
+
* sessie; in /beheer → Statistieken verschijnen de vaakst gemiste adressen met een knop om er een
|
|
17
|
+
* doorverwijzing van te maken. Geen cookies, alleen het pad.
|
|
18
|
+
*/
|
|
19
|
+
export declare function NietGevondenMelder(): null;
|
|
@@ -5,6 +5,7 @@ function PageView({ path }) {
|
|
|
5
5
|
const { siteId } = useSiteConfig();
|
|
6
6
|
useEffect(() => {
|
|
7
7
|
if (typeof window === "undefined") return;
|
|
8
|
+
if (navigator.webdriver) return;
|
|
8
9
|
const key = `xicms_view:${siteId}:${path}`;
|
|
9
10
|
try {
|
|
10
11
|
if (sessionStorage.getItem(key)) return;
|
|
@@ -45,7 +46,23 @@ function trackEvent(siteId, event, path) {
|
|
|
45
46
|
})().catch(() => {
|
|
46
47
|
});
|
|
47
48
|
}
|
|
49
|
+
function NietGevondenMelder() {
|
|
50
|
+
const { siteId } = useSiteConfig();
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (typeof window === "undefined" || navigator.webdriver) return;
|
|
53
|
+
const pad = window.location.pathname;
|
|
54
|
+
const key = `xicms_404:${siteId}:${pad}`;
|
|
55
|
+
try {
|
|
56
|
+
if (sessionStorage.getItem(key)) return;
|
|
57
|
+
sessionStorage.setItem(key, "1");
|
|
58
|
+
} catch {
|
|
59
|
+
}
|
|
60
|
+
trackEvent(siteId, "niet-gevonden", pad);
|
|
61
|
+
}, [siteId]);
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
48
64
|
export {
|
|
65
|
+
NietGevondenMelder,
|
|
49
66
|
PageView,
|
|
50
67
|
trackEvent
|
|
51
68
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/PageView.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect } from \"react\";\nimport { useSiteConfig } from \"../config\";\n\n/**\n * Registreert één pageview voor de huidige site/pad — max. één keer per browsersessie per pad\n * (sessionStorage). Privacy-vriendelijk en goedkoop. No-op zonder Firebase. Plaats dit in de\n * publieke layout van een klantsite (binnen <XiCMSProvider>).\n */\nexport function PageView({ path }: { path: string }) {\n const { siteId } = useSiteConfig();\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const key = `xicms_view:${siteId}:${path}`;\n try {\n if (sessionStorage.getItem(key)) return;\n sessionStorage.setItem(key, \"1\");\n } catch {\n /* private mode e.d. — ga gewoon door */\n }\n // Firebase pas laden wanneer er écht getracked wordt: statisch importeren zette de hele SDK\n // in de bundel van elke publieke pagina, voor één fire-and-forget-call.\n void (async () => {\n const [{ getFirebaseApp }, { callable }] = await Promise.all([\n import(\"../firebase/client\"),\n import(\"../firebase/functionsClient\"),\n ]);\n const app = getFirebaseApp();\n if (!app) return;\n // 7.24: alleen de HOST van de verwijzer (nooit pad/query — daar kan een zoekterm of\n // sessie-id in staan) en een grove apparaatklasse. Beide zonder cookies of fingerprint.\n let ref: string | undefined;\n try {\n ref = document.referrer ? new URL(document.referrer).hostname : undefined;\n } catch {\n ref = undefined;\n }\n const w = window.innerWidth;\n const device = w < 720 ? \"mobiel\" : w < 1100 ? \"tablet\" : \"desktop\";\n await callable(app, \"trackView\")({ siteId, path, ...(ref ? { ref } : {}), device });\n })().catch(() => {\n /* stats mogen nooit de site breken */\n });\n }, [siteId, path]);\n\n return null;\n}\n\n/**\n * Telt een gebeurtenis (7.24): \"formulier\" verstuurd, \"uitgaand\" geklikt, enz. Zelfde teller,\n * zelfde privacy-regels; fire-and-forget en nooit een fout naar de bezoeker.\n */\nexport function trackEvent(siteId: string, event: \"formulier\" | \"bestelling\" | \"uitgaand\" | \"telefoon\" | \"mail\" | \"whatsapp\", path?: string): void {\n if (typeof window === \"undefined\") return;\n void (async () => {\n const [{ getFirebaseApp }, { callable }] = await Promise.all([\n import(\"../firebase/client\"),\n import(\"../firebase/functionsClient\"),\n ]);\n const app = getFirebaseApp();\n if (!app) return;\n await callable(app, \"trackView\")({ siteId, path: path ?? window.location.pathname ?? \"/\", event });\n })().catch(() => {\n /* stats mogen nooit de site breken */\n });\n}\n"],"mappings":";AAEA,SAAS,iBAAiB;AAC1B,SAAS,qBAAqB;AAOvB,SAAS,SAAS,EAAE,KAAK,GAAqB;AACnD,QAAM,EAAE,OAAO,IAAI,cAAc;AAEjC,YAAU,MAAM;AACd,QAAI,OAAO,WAAW,YAAa;
|
|
1
|
+
{"version":3,"sources":["../../src/components/PageView.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect } from \"react\";\nimport { useSiteConfig } from \"../config\";\n\n/**\n * Registreert één pageview voor de huidige site/pad — max. één keer per browsersessie per pad\n * (sessionStorage). Privacy-vriendelijk en goedkoop. No-op zonder Firebase. Plaats dit in de\n * publieke layout van een klantsite (binnen <XiCMSProvider>).\n */\nexport function PageView({ path }: { path: string }) {\n const { siteId } = useSiteConfig();\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n // 8.3: geautomatiseerde browsers (Lighthouse, testtools) tellen niet als bezoek; de server\n // filtert daarnaast op user-agent.\n if (navigator.webdriver) return;\n const key = `xicms_view:${siteId}:${path}`;\n try {\n if (sessionStorage.getItem(key)) return;\n sessionStorage.setItem(key, \"1\");\n } catch {\n /* private mode e.d. — ga gewoon door */\n }\n // Firebase pas laden wanneer er écht getracked wordt: statisch importeren zette de hele SDK\n // in de bundel van elke publieke pagina, voor één fire-and-forget-call.\n void (async () => {\n const [{ getFirebaseApp }, { callable }] = await Promise.all([\n import(\"../firebase/client\"),\n import(\"../firebase/functionsClient\"),\n ]);\n const app = getFirebaseApp();\n if (!app) return;\n // 7.24: alleen de HOST van de verwijzer (nooit pad/query — daar kan een zoekterm of\n // sessie-id in staan) en een grove apparaatklasse. Beide zonder cookies of fingerprint.\n let ref: string | undefined;\n try {\n ref = document.referrer ? new URL(document.referrer).hostname : undefined;\n } catch {\n ref = undefined;\n }\n const w = window.innerWidth;\n const device = w < 720 ? \"mobiel\" : w < 1100 ? \"tablet\" : \"desktop\";\n await callable(app, \"trackView\")({ siteId, path, ...(ref ? { ref } : {}), device });\n })().catch(() => {\n /* stats mogen nooit de site breken */\n });\n }, [siteId, path]);\n\n return null;\n}\n\n/**\n * Telt een gebeurtenis (7.24): \"formulier\" verstuurd, \"uitgaand\" geklikt, enz. Zelfde teller,\n * zelfde privacy-regels; fire-and-forget en nooit een fout naar de bezoeker.\n */\nexport function trackEvent(siteId: string, event: \"formulier\" | \"bestelling\" | \"uitgaand\" | \"telefoon\" | \"mail\" | \"whatsapp\" | \"niet-gevonden\", path?: string): void {\n if (typeof window === \"undefined\") return;\n void (async () => {\n const [{ getFirebaseApp }, { callable }] = await Promise.all([\n import(\"../firebase/client\"),\n import(\"../firebase/functionsClient\"),\n ]);\n const app = getFirebaseApp();\n if (!app) return;\n await callable(app, \"trackView\")({ siteId, path: path ?? window.location.pathname ?? \"/\", event });\n })().catch(() => {\n /* stats mogen nooit de site breken */\n });\n}\n\n/**\n * 404-monitor (8.4): zet dit op de niet-gevonden-pagina van de site. Meldt het pad één keer per\n * sessie; in /beheer → Statistieken verschijnen de vaakst gemiste adressen met een knop om er een\n * doorverwijzing van te maken. Geen cookies, alleen het pad.\n */\nexport function NietGevondenMelder() {\n const { siteId } = useSiteConfig();\n useEffect(() => {\n if (typeof window === \"undefined\" || navigator.webdriver) return;\n const pad = window.location.pathname;\n const key = `xicms_404:${siteId}:${pad}`;\n try {\n if (sessionStorage.getItem(key)) return;\n sessionStorage.setItem(key, \"1\");\n } catch {\n /* private mode — gewoon melden */\n }\n trackEvent(siteId, \"niet-gevonden\", pad);\n }, [siteId]);\n return null;\n}\n"],"mappings":";AAEA,SAAS,iBAAiB;AAC1B,SAAS,qBAAqB;AAOvB,SAAS,SAAS,EAAE,KAAK,GAAqB;AACnD,QAAM,EAAE,OAAO,IAAI,cAAc;AAEjC,YAAU,MAAM;AACd,QAAI,OAAO,WAAW,YAAa;AAGnC,QAAI,UAAU,UAAW;AACzB,UAAM,MAAM,cAAc,MAAM,IAAI,IAAI;AACxC,QAAI;AACF,UAAI,eAAe,QAAQ,GAAG,EAAG;AACjC,qBAAe,QAAQ,KAAK,GAAG;AAAA,IACjC,QAAQ;AAAA,IAER;AAGA,UAAM,YAAY;AAChB,YAAM,CAAC,EAAE,eAAe,GAAG,EAAE,SAAS,CAAC,IAAI,MAAM,QAAQ,IAAI;AAAA,QAC3D,OAAO,oBAAoB;AAAA,QAC3B,OAAO,6BAA6B;AAAA,MACtC,CAAC;AACD,YAAM,MAAM,eAAe;AAC3B,UAAI,CAAC,IAAK;AAGV,UAAI;AACJ,UAAI;AACF,cAAM,SAAS,WAAW,IAAI,IAAI,SAAS,QAAQ,EAAE,WAAW;AAAA,MAClE,QAAQ;AACN,cAAM;AAAA,MACR;AACA,YAAM,IAAI,OAAO;AACjB,YAAM,SAAS,IAAI,MAAM,WAAW,IAAI,OAAO,WAAW;AAC1D,YAAM,SAAS,KAAK,WAAW,EAAE,EAAE,QAAQ,MAAM,GAAI,MAAM,EAAE,IAAI,IAAI,CAAC,GAAI,OAAO,CAAC;AAAA,IACpF,GAAG,EAAE,MAAM,MAAM;AAAA,IAEjB,CAAC;AAAA,EACH,GAAG,CAAC,QAAQ,IAAI,CAAC;AAEjB,SAAO;AACT;AAMO,SAAS,WAAW,QAAgB,OAAqG,MAAqB;AACnK,MAAI,OAAO,WAAW,YAAa;AACnC,QAAM,YAAY;AAChB,UAAM,CAAC,EAAE,eAAe,GAAG,EAAE,SAAS,CAAC,IAAI,MAAM,QAAQ,IAAI;AAAA,MAC3D,OAAO,oBAAoB;AAAA,MAC3B,OAAO,6BAA6B;AAAA,IACtC,CAAC;AACD,UAAM,MAAM,eAAe;AAC3B,QAAI,CAAC,IAAK;AACV,UAAM,SAAS,KAAK,WAAW,EAAE,EAAE,QAAQ,MAAM,QAAQ,OAAO,SAAS,YAAY,KAAK,MAAM,CAAC;AAAA,EACnG,GAAG,EAAE,MAAM,MAAM;AAAA,EAEjB,CAAC;AACH;AAOO,SAAS,qBAAqB;AACnC,QAAM,EAAE,OAAO,IAAI,cAAc;AACjC,YAAU,MAAM;AACd,QAAI,OAAO,WAAW,eAAe,UAAU,UAAW;AAC1D,UAAM,MAAM,OAAO,SAAS;AAC5B,UAAM,MAAM,aAAa,MAAM,IAAI,GAAG;AACtC,QAAI;AACF,UAAI,eAAe,QAAQ,GAAG,EAAG;AACjC,qBAAe,QAAQ,KAAK,GAAG;AAAA,IACjC,QAAQ;AAAA,IAER;AACA,eAAW,QAAQ,iBAAiB,GAAG;AAAA,EACzC,GAAG,CAAC,MAAM,CAAC;AACX,SAAO;AACT;","names":[]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { jsx
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { bronSet } from "../media/srcset.js";
|
|
2
3
|
function ResponsiveImage({
|
|
3
4
|
media,
|
|
4
5
|
url,
|
|
@@ -15,25 +16,22 @@ function ResponsiveImage({
|
|
|
15
16
|
const webp = media?.variants?.webp;
|
|
16
17
|
const focal = media?.focal;
|
|
17
18
|
const stijl = focal && !(style && style.objectPosition) ? { ...style, objectPosition: `${Math.round(focal.x * 100)}% ${Math.round(focal.y * 100)}%` } : style;
|
|
18
|
-
const srcSet = ladder.length ? ladder
|
|
19
|
-
return /* @__PURE__ */
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
)
|
|
36
|
-
] });
|
|
19
|
+
const srcSet = ladder.length ? bronSet(ladder, webp) : void 0;
|
|
20
|
+
return /* @__PURE__ */ jsx("picture", { children: /* @__PURE__ */ jsx(
|
|
21
|
+
"img",
|
|
22
|
+
{
|
|
23
|
+
src,
|
|
24
|
+
srcSet,
|
|
25
|
+
sizes: srcSet ? sizes : void 0,
|
|
26
|
+
alt: altText,
|
|
27
|
+
width: media?.width,
|
|
28
|
+
height: media?.height,
|
|
29
|
+
loading: priority ? "eager" : "lazy",
|
|
30
|
+
fetchPriority: priority ? "high" : void 0,
|
|
31
|
+
className,
|
|
32
|
+
style: stijl
|
|
33
|
+
}
|
|
34
|
+
) });
|
|
37
35
|
}
|
|
38
36
|
export {
|
|
39
37
|
ResponsiveImage
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/ResponsiveImage.tsx"],"sourcesContent":["import type { CSSProperties } from \"react\";\nimport type { MediaItem } from \"../content/types\";\n\n/**\n * Rendert een afbeelding met srcset/webp uit de door de pipeline gegenereerde varianten.\n * Server-safe. Valt terug op de originele `url` als er nog geen varianten zijn.\n */\nexport function ResponsiveImage({\n media,\n url,\n alt,\n className,\n style,\n sizes = \"100vw\",\n priority = false,\n}: {\n media?: Pick<MediaItem, \"url\" | \"alt\" | \"width\" | \"height\" | \"variants\" | \"focal\">;\n url?: string;\n alt?: string;\n className?: string;\n style?: CSSProperties;\n sizes?: string;\n /** Zet dit op een above-the-fold beeld (bv. hero): eager laden + hoge fetch-prioriteit voor LCP. */\n priority?: boolean;\n}) {\n const src = media?.url ?? url;\n if (!src) return null;\n const altText = alt ?? media?.alt ?? \"\";\n const ladder = media?.variants?.sizes ?? [];\n const webp = media?.variants?.webp;\n // Focuspunt (7.24): bijsnijden rond het gekozen punt i.p.v. het midden (zie MediaItem.focal).\n const focal = media?.focal;\n const stijl = focal && !(style && style.objectPosition)\n ? { ...style, objectPosition: `${Math.round(focal.x * 100)}% ${Math.round(focal.y * 100)}%` }\n : style;\n\n
|
|
1
|
+
{"version":3,"sources":["../../src/components/ResponsiveImage.tsx"],"sourcesContent":["import { bronSet } from \"../media/srcset\";\nimport type { CSSProperties } from \"react\";\nimport type { MediaItem } from \"../content/types\";\n\n/**\n * Rendert een afbeelding met srcset/webp uit de door de pipeline gegenereerde varianten.\n * Server-safe. Valt terug op de originele `url` als er nog geen varianten zijn.\n */\nexport function ResponsiveImage({\n media,\n url,\n alt,\n className,\n style,\n sizes = \"100vw\",\n priority = false,\n}: {\n media?: Pick<MediaItem, \"url\" | \"alt\" | \"width\" | \"height\" | \"variants\" | \"focal\">;\n url?: string;\n alt?: string;\n className?: string;\n style?: CSSProperties;\n sizes?: string;\n /** Zet dit op een above-the-fold beeld (bv. hero): eager laden + hoge fetch-prioriteit voor LCP. */\n priority?: boolean;\n}) {\n const src = media?.url ?? url;\n if (!src) return null;\n const altText = alt ?? media?.alt ?? \"\";\n const ladder = media?.variants?.sizes ?? [];\n const webp = media?.variants?.webp;\n // Focuspunt (7.24): bijsnijden rond het gekozen punt i.p.v. het midden (zie MediaItem.focal).\n const focal = media?.focal;\n const stijl = focal && !(style && style.objectPosition)\n ? { ...style, objectPosition: `${Math.round(focal.x * 100)}% ${Math.round(focal.y * 100)}%` }\n : style;\n\n // Eén srcset met ladder + volle WebP (8.3; de losse <source> overstemde de ladder altijd).\n const srcSet = ladder.length ? bronSet(ladder, webp) : undefined;\n\n return (\n <picture>\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img\n src={src}\n srcSet={srcSet}\n sizes={srcSet ? sizes : undefined}\n alt={altText}\n width={media?.width}\n height={media?.height}\n loading={priority ? \"eager\" : \"lazy\"}\n fetchPriority={priority ? \"high\" : undefined}\n className={className}\n style={stijl}\n />\n </picture>\n );\n}\n"],"mappings":"AA2CM;AA3CN,SAAS,eAAe;AAQjB,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR,WAAW;AACb,GASG;AACD,QAAM,MAAM,OAAO,OAAO;AAC1B,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,UAAU,OAAO,OAAO,OAAO;AACrC,QAAM,SAAS,OAAO,UAAU,SAAS,CAAC;AAC1C,QAAM,OAAO,OAAO,UAAU;AAE9B,QAAM,QAAQ,OAAO;AACrB,QAAM,QAAQ,SAAS,EAAE,SAAS,MAAM,kBACpC,EAAE,GAAG,OAAO,gBAAgB,GAAG,KAAK,MAAM,MAAM,IAAI,GAAG,CAAC,KAAK,KAAK,MAAM,MAAM,IAAI,GAAG,CAAC,IAAI,IAC1F;AAGJ,QAAM,SAAS,OAAO,SAAS,QAAQ,QAAQ,IAAI,IAAI;AAEvD,SACE,oBAAC,aAEC;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA,OAAO,SAAS,QAAQ;AAAA,MACxB,KAAK;AAAA,MACL,OAAO,OAAO;AAAA,MACd,QAAQ,OAAO;AAAA,MACf,SAAS,WAAW,UAAU;AAAA,MAC9B,eAAe,WAAW,SAAS;AAAA,MACnC;AAAA,MACA,OAAO;AAAA;AAAA,EACT,GACF;AAEJ;","names":[]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hoe hoog zijn de vaste balken onderaan het venster? → `--xicms-onder` op `:root` (8.16).
|
|
3
|
+
*
|
|
4
|
+
* De cookiebanner stond `bottom: 0`, en op een telefoon staat daar bijna altijd al iets: de
|
|
5
|
+
* demobalk, de belbalk van SiteHeader, of een eigen "Maak een afspraak"-balk van de klantsite. Op
|
|
6
|
+
* barbier lag de demobalk precies over de knoppen van de banner — de bezoeker kón geen keuze maken.
|
|
7
|
+
*
|
|
8
|
+
* Net als `useBalkOffset` (bovenkant) meten we in plaats van te gokken: elke klantsite noemt zijn
|
|
9
|
+
* balk anders. Telt als balk: `position: fixed`, zichtbaar, raakt de onderrand, minstens 60% van de
|
|
10
|
+
* breedte en niet hoger dan 40% van het venster (geen schermvullende overlay). Een balk die pas bij
|
|
11
|
+
* het scrollen inschuift wordt opnieuw gemeten zodra zijn klasse/stijl verandert of de overgang klaar is.
|
|
12
|
+
*/
|
|
13
|
+
export declare function useOnderOffset(actief?: boolean): void;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useEffect } from "react";
|
|
3
|
+
const ZWEVERS = ".xicms-consent, .xicms-consent__reopen, .xicms-whatsapp, [data-xicms-tooling]";
|
|
4
|
+
function useOnderOffset(actief = true) {
|
|
5
|
+
useEffect(() => {
|
|
6
|
+
if (!actief || typeof document === "undefined") return;
|
|
7
|
+
if (gebruikers++ === 0) stoppen = start();
|
|
8
|
+
return () => {
|
|
9
|
+
if (--gebruikers === 0) stoppen?.();
|
|
10
|
+
};
|
|
11
|
+
}, [actief]);
|
|
12
|
+
}
|
|
13
|
+
let gebruikers = 0;
|
|
14
|
+
let stoppen;
|
|
15
|
+
function start() {
|
|
16
|
+
const root = document.documentElement;
|
|
17
|
+
let vorige = -1;
|
|
18
|
+
let kandidaten = [];
|
|
19
|
+
const zoek = () => {
|
|
20
|
+
kandidaten = Array.from(document.querySelectorAll("body *")).filter(
|
|
21
|
+
(el) => getComputedStyle(el).position === "fixed" && !el.closest(ZWEVERS)
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
const meet = () => {
|
|
25
|
+
const vh = window.innerHeight;
|
|
26
|
+
const vw = window.innerWidth;
|
|
27
|
+
let hoogte = 0;
|
|
28
|
+
for (const el of kandidaten) {
|
|
29
|
+
if (!el.isConnected) continue;
|
|
30
|
+
const r = el.getBoundingClientRect();
|
|
31
|
+
if (r.height === 0 || r.bottom < vh - 2 || r.top >= vh) continue;
|
|
32
|
+
if (r.width < vw * 0.6 || r.height > vh * 0.4) continue;
|
|
33
|
+
const cs = getComputedStyle(el);
|
|
34
|
+
if (cs.visibility === "hidden" || Number(cs.opacity) === 0) continue;
|
|
35
|
+
hoogte = Math.max(hoogte, Math.round(vh - r.top));
|
|
36
|
+
}
|
|
37
|
+
if (hoogte !== vorige) {
|
|
38
|
+
vorige = hoogte;
|
|
39
|
+
root.style.setProperty("--xicms-onder", `${hoogte}px`);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
let frame = 0;
|
|
43
|
+
let opnieuwZoeken = false;
|
|
44
|
+
const plan = (zoeken) => {
|
|
45
|
+
opnieuwZoeken ||= zoeken;
|
|
46
|
+
cancelAnimationFrame(frame);
|
|
47
|
+
frame = requestAnimationFrame(() => {
|
|
48
|
+
if (opnieuwZoeken) zoek();
|
|
49
|
+
opnieuwZoeken = false;
|
|
50
|
+
meet();
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
const naMutatie = () => plan(true);
|
|
54
|
+
const naBeweging = () => plan(false);
|
|
55
|
+
zoek();
|
|
56
|
+
meet();
|
|
57
|
+
const obs = new MutationObserver(naMutatie);
|
|
58
|
+
obs.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["class", "style", "hidden"] });
|
|
59
|
+
window.addEventListener("resize", naBeweging);
|
|
60
|
+
document.addEventListener("transitionend", naBeweging, true);
|
|
61
|
+
window.addEventListener("scroll", naBeweging, { passive: true });
|
|
62
|
+
return () => {
|
|
63
|
+
obs.disconnect();
|
|
64
|
+
cancelAnimationFrame(frame);
|
|
65
|
+
window.removeEventListener("resize", naBeweging);
|
|
66
|
+
document.removeEventListener("transitionend", naBeweging, true);
|
|
67
|
+
window.removeEventListener("scroll", naBeweging);
|
|
68
|
+
root.style.removeProperty("--xicms-onder");
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
export {
|
|
72
|
+
useOnderOffset
|
|
73
|
+
};
|
|
74
|
+
//# sourceMappingURL=onderOffset.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/onderOffset.ts"],"sourcesContent":["\"use client\";\n\nimport { useEffect } from \"react\";\n\n/** Wat zelf boven de balken hoort te zweven, en dus niet als \"balk\" mag tellen (anders een rondje). */\nconst ZWEVERS = \".xicms-consent, .xicms-consent__reopen, .xicms-whatsapp, [data-xicms-tooling]\";\n\n/**\n * Hoe hoog zijn de vaste balken onderaan het venster? → `--xicms-onder` op `:root` (8.16).\n *\n * De cookiebanner stond `bottom: 0`, en op een telefoon staat daar bijna altijd al iets: de\n * demobalk, de belbalk van SiteHeader, of een eigen \"Maak een afspraak\"-balk van de klantsite. Op\n * barbier lag de demobalk precies over de knoppen van de banner — de bezoeker kón geen keuze maken.\n *\n * Net als `useBalkOffset` (bovenkant) meten we in plaats van te gokken: elke klantsite noemt zijn\n * balk anders. Telt als balk: `position: fixed`, zichtbaar, raakt de onderrand, minstens 60% van de\n * breedte en niet hoger dan 40% van het venster (geen schermvullende overlay). Een balk die pas bij\n * het scrollen inschuift wordt opnieuw gemeten zodra zijn klasse/stijl verandert of de overgang klaar is.\n */\nexport function useOnderOffset(actief = true): void {\n useEffect(() => {\n if (!actief || typeof document === \"undefined\") return;\n // Banner, 🍪-knopje en WhatsApp-knop vragen dit allemaal; één meting is genoeg.\n if (gebruikers++ === 0) stoppen = start();\n return () => {\n if (--gebruikers === 0) stoppen?.();\n };\n }, [actief]);\n}\n\nlet gebruikers = 0;\nlet stoppen: (() => void) | undefined;\n\nfunction start(): () => void {\n const root = document.documentElement;\n let vorige = -1;\n\n // Zoeken (dure scan van de hele pagina) alleen als de DOM verandert; meten (alleen de gevonden\n // vaste elementen) ook bij scrollen en na een overgang — dat moet goedkoop blijven.\n let kandidaten: HTMLElement[] = [];\n const zoek = () => {\n kandidaten = Array.from(document.querySelectorAll<HTMLElement>(\"body *\")).filter(\n (el) => getComputedStyle(el).position === \"fixed\" && !el.closest(ZWEVERS),\n );\n };\n const meet = () => {\n const vh = window.innerHeight;\n const vw = window.innerWidth;\n let hoogte = 0;\n for (const el of kandidaten) {\n if (!el.isConnected) continue;\n const r = el.getBoundingClientRect();\n if (r.height === 0 || r.bottom < vh - 2 || r.top >= vh) continue;\n if (r.width < vw * 0.6 || r.height > vh * 0.4) continue;\n const cs = getComputedStyle(el);\n if (cs.visibility === \"hidden\" || Number(cs.opacity) === 0) continue;\n hoogte = Math.max(hoogte, Math.round(vh - r.top));\n }\n if (hoogte !== vorige) {\n vorige = hoogte;\n root.style.setProperty(\"--xicms-onder\", `${hoogte}px`);\n }\n };\n\n // Gebundeld per frame: de observer ziet salvo's mutaties (zie useBalkOffset).\n let frame = 0;\n let opnieuwZoeken = false;\n const plan = (zoeken: boolean) => {\n opnieuwZoeken ||= zoeken;\n cancelAnimationFrame(frame);\n frame = requestAnimationFrame(() => {\n if (opnieuwZoeken) zoek();\n opnieuwZoeken = false;\n meet();\n });\n };\n const naMutatie = () => plan(true);\n const naBeweging = () => plan(false);\n zoek();\n meet();\n const obs = new MutationObserver(naMutatie);\n obs.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: [\"class\", \"style\", \"hidden\"] });\n window.addEventListener(\"resize\", naBeweging);\n document.addEventListener(\"transitionend\", naBeweging, true);\n // Sommige balken schuiven pas in na een stukje scrollen, zonder dat er een klasse verandert.\n window.addEventListener(\"scroll\", naBeweging, { passive: true });\n\n return () => {\n obs.disconnect();\n cancelAnimationFrame(frame);\n window.removeEventListener(\"resize\", naBeweging);\n document.removeEventListener(\"transitionend\", naBeweging, true);\n window.removeEventListener(\"scroll\", naBeweging);\n root.style.removeProperty(\"--xicms-onder\");\n };\n}\n"],"mappings":";AAEA,SAAS,iBAAiB;AAG1B,MAAM,UAAU;AAcT,SAAS,eAAe,SAAS,MAAY;AAClD,YAAU,MAAM;AACd,QAAI,CAAC,UAAU,OAAO,aAAa,YAAa;AAEhD,QAAI,iBAAiB,EAAG,WAAU,MAAM;AACxC,WAAO,MAAM;AACX,UAAI,EAAE,eAAe,EAAG,WAAU;AAAA,IACpC;AAAA,EACF,GAAG,CAAC,MAAM,CAAC;AACb;AAEA,IAAI,aAAa;AACjB,IAAI;AAEJ,SAAS,QAAoB;AAC3B,QAAM,OAAO,SAAS;AACtB,MAAI,SAAS;AAIb,MAAI,aAA4B,CAAC;AACjC,QAAM,OAAO,MAAM;AACjB,iBAAa,MAAM,KAAK,SAAS,iBAA8B,QAAQ,CAAC,EAAE;AAAA,MACxE,CAAC,OAAO,iBAAiB,EAAE,EAAE,aAAa,WAAW,CAAC,GAAG,QAAQ,OAAO;AAAA,IAC1E;AAAA,EACF;AACA,QAAM,OAAO,MAAM;AACjB,UAAM,KAAK,OAAO;AAClB,UAAM,KAAK,OAAO;AAClB,QAAI,SAAS;AACb,eAAW,MAAM,YAAY;AAC3B,UAAI,CAAC,GAAG,YAAa;AACrB,YAAM,IAAI,GAAG,sBAAsB;AACnC,UAAI,EAAE,WAAW,KAAK,EAAE,SAAS,KAAK,KAAK,EAAE,OAAO,GAAI;AACxD,UAAI,EAAE,QAAQ,KAAK,OAAO,EAAE,SAAS,KAAK,IAAK;AAC/C,YAAM,KAAK,iBAAiB,EAAE;AAC9B,UAAI,GAAG,eAAe,YAAY,OAAO,GAAG,OAAO,MAAM,EAAG;AAC5D,eAAS,KAAK,IAAI,QAAQ,KAAK,MAAM,KAAK,EAAE,GAAG,CAAC;AAAA,IAClD;AACA,QAAI,WAAW,QAAQ;AACrB,eAAS;AACT,WAAK,MAAM,YAAY,iBAAiB,GAAG,MAAM,IAAI;AAAA,IACvD;AAAA,EACF;AAGA,MAAI,QAAQ;AACZ,MAAI,gBAAgB;AACpB,QAAM,OAAO,CAAC,WAAoB;AAChC,sBAAkB;AAClB,yBAAqB,KAAK;AAC1B,YAAQ,sBAAsB,MAAM;AAClC,UAAI,cAAe,MAAK;AACxB,sBAAgB;AAChB,WAAK;AAAA,IACP,CAAC;AAAA,EACH;AACA,QAAM,YAAY,MAAM,KAAK,IAAI;AACjC,QAAM,aAAa,MAAM,KAAK,KAAK;AACnC,OAAK;AACL,OAAK;AACL,QAAM,MAAM,IAAI,iBAAiB,SAAS;AAC1C,MAAI,QAAQ,SAAS,MAAM,EAAE,WAAW,MAAM,SAAS,MAAM,YAAY,MAAM,iBAAiB,CAAC,SAAS,SAAS,QAAQ,EAAE,CAAC;AAC9H,SAAO,iBAAiB,UAAU,UAAU;AAC5C,WAAS,iBAAiB,iBAAiB,YAAY,IAAI;AAE3D,SAAO,iBAAiB,UAAU,YAAY,EAAE,SAAS,KAAK,CAAC;AAE/D,SAAO,MAAM;AACX,QAAI,WAAW;AACf,yBAAqB,KAAK;AAC1B,WAAO,oBAAoB,UAAU,UAAU;AAC/C,aAAS,oBAAoB,iBAAiB,YAAY,IAAI;AAC9D,WAAO,oBAAoB,UAAU,UAAU;AAC/C,SAAK,MAAM,eAAe,eAAe;AAAA,EAC3C;AACF;","names":[]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "KvK 12345678 · Btw NL123456789B01" uit het bedrijfsprofiel (8.16), voor sites met een eigen
|
|
3
|
+
* footer. Een ondernemer moet zijn KvK-nummer op de website vermelden, en bij verkoop aan
|
|
4
|
+
* consumenten ook het btw-identificatienummer. Zet dit in je footer; de klant vult de nummers in
|
|
5
|
+
* via /beheer → Instellingen → Bedrijf. Leeg profiel = niets.
|
|
6
|
+
*/
|
|
7
|
+
export declare function Bedrijfsnummers({ className }: {
|
|
8
|
+
className?: string;
|
|
9
|
+
}): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useMergedSiteConfig } from "../../settings/useSiteSettings.js";
|
|
4
|
+
import { bedrijfsnummersTekst } from "../../bedrijf/bedrijf.js";
|
|
5
|
+
function Bedrijfsnummers({ className }) {
|
|
6
|
+
const tekst = bedrijfsnummersTekst(useMergedSiteConfig().business);
|
|
7
|
+
return tekst ? /* @__PURE__ */ jsx("span", { className: className ?? "xicms-bedrijfsnummers", children: tekst }) : null;
|
|
8
|
+
}
|
|
9
|
+
export {
|
|
10
|
+
Bedrijfsnummers
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=Bedrijfsnummers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/site/Bedrijfsnummers.tsx"],"sourcesContent":["\"use client\";\n\nimport { useMergedSiteConfig } from \"../../settings/useSiteSettings\";\nimport { bedrijfsnummersTekst } from \"../../bedrijf/bedrijf\";\n\n/**\n * \"KvK 12345678 · Btw NL123456789B01\" uit het bedrijfsprofiel (8.16), voor sites met een eigen\n * footer. Een ondernemer moet zijn KvK-nummer op de website vermelden, en bij verkoop aan\n * consumenten ook het btw-identificatienummer. Zet dit in je footer; de klant vult de nummers in\n * via /beheer → Instellingen → Bedrijf. Leeg profiel = niets.\n */\nexport function Bedrijfsnummers({ className }: { className?: string }) {\n const tekst = bedrijfsnummersTekst(useMergedSiteConfig().business);\n return tekst ? <span className={className ?? \"xicms-bedrijfsnummers\"}>{tekst}</span> : null;\n}\n"],"mappings":";AAaiB;AAXjB,SAAS,2BAA2B;AACpC,SAAS,4BAA4B;AAQ9B,SAAS,gBAAgB,EAAE,UAAU,GAA2B;AACrE,QAAM,QAAQ,qBAAqB,oBAAoB,EAAE,QAAQ;AACjE,SAAO,QAAQ,oBAAC,UAAK,WAAW,aAAa,yBAA0B,iBAAM,IAAU;AACzF;","names":[]}
|