@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
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
addDoc,
|
|
4
4
|
collection,
|
|
5
5
|
deleteDoc,
|
|
6
|
+
deleteField,
|
|
6
7
|
doc,
|
|
7
8
|
getDoc,
|
|
8
9
|
getDocs,
|
|
@@ -12,11 +13,12 @@ import {
|
|
|
12
13
|
query,
|
|
13
14
|
serverTimestamp,
|
|
14
15
|
setDoc,
|
|
16
|
+
Timestamp,
|
|
15
17
|
where
|
|
16
18
|
} from "firebase/firestore";
|
|
17
19
|
import { callable } from "../../firebase/functionsClient.js";
|
|
18
20
|
import { getDb, getFirebaseApp } from "../../firebase/client.js";
|
|
19
|
-
import { isDemoActive, demoIdForPath, resolveDemoPage, saveDemoDraft } from "../../demo/demoStore.js";
|
|
21
|
+
import { DEMO_EVENT, isDemoActive, demoIdForPath, listDemoPages, resolveDemoPage, saveDemoDraft } from "../../demo/demoStore.js";
|
|
20
22
|
import { sanitizeDraftBlocks } from "./sanitizeDraft.js";
|
|
21
23
|
function ms(v) {
|
|
22
24
|
if (v && typeof v.toDate === "function") return v.toDate().getTime();
|
|
@@ -38,7 +40,13 @@ function paginaToestand(p) {
|
|
|
38
40
|
if (heeftOngepubliceerdeWijzigingen(p)) return "gewijzigd";
|
|
39
41
|
return p.published ? "live" : "concept";
|
|
40
42
|
}
|
|
41
|
-
function subscribePages(siteId, cb) {
|
|
43
|
+
function subscribePages(siteId, cb, onError) {
|
|
44
|
+
if (isDemoActive()) {
|
|
45
|
+
const sync = () => cb(listDemoPages(siteId));
|
|
46
|
+
sync();
|
|
47
|
+
window.addEventListener(DEMO_EVENT, sync);
|
|
48
|
+
return () => window.removeEventListener(DEMO_EVENT, sync);
|
|
49
|
+
}
|
|
42
50
|
const db = getDb();
|
|
43
51
|
if (!db) {
|
|
44
52
|
cb([]);
|
|
@@ -46,9 +54,17 @@ function subscribePages(siteId, cb) {
|
|
|
46
54
|
};
|
|
47
55
|
}
|
|
48
56
|
const q = query(collection(db, "sites", siteId, "drafts"), orderBy("order", "asc"));
|
|
49
|
-
return onSnapshot(
|
|
50
|
-
|
|
51
|
-
|
|
57
|
+
return onSnapshot(
|
|
58
|
+
q,
|
|
59
|
+
(snap) => {
|
|
60
|
+
cb(snap.docs.map((d) => ({ id: d.id, ...d.data() })));
|
|
61
|
+
},
|
|
62
|
+
// 8.1: zonder error-callback bleef een rechten-/verbindingsfout onzichtbaar ("nog geen pagina's").
|
|
63
|
+
(e) => {
|
|
64
|
+
console.error("[xicms] pagina's laden mislukt:", e);
|
|
65
|
+
onError?.(e);
|
|
66
|
+
}
|
|
67
|
+
);
|
|
52
68
|
}
|
|
53
69
|
function subscribeVersions(siteId, pageId, cb) {
|
|
54
70
|
const db = getDb();
|
|
@@ -58,7 +74,11 @@ function subscribeVersions(siteId, pageId, cb) {
|
|
|
58
74
|
};
|
|
59
75
|
}
|
|
60
76
|
const q = query(collection(db, "sites", siteId, "pages", pageId, "versions"), orderBy("createdAt", "desc"));
|
|
61
|
-
return onSnapshot(
|
|
77
|
+
return onSnapshot(
|
|
78
|
+
q,
|
|
79
|
+
(snap) => cb(snap.docs.map((d) => ({ id: d.id, ...d.data() }))),
|
|
80
|
+
(e) => console.warn("[xicms] live-lijst niet geladen:", e.message)
|
|
81
|
+
);
|
|
62
82
|
}
|
|
63
83
|
async function getDraftByPath(siteId, path) {
|
|
64
84
|
if (isDemoActive()) {
|
|
@@ -90,15 +110,39 @@ function subscribeDraft(siteId, pageId, cb) {
|
|
|
90
110
|
const db = getDb();
|
|
91
111
|
if (!db) return () => {
|
|
92
112
|
};
|
|
93
|
-
return onSnapshot(
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
113
|
+
return onSnapshot(
|
|
114
|
+
doc(db, "sites", siteId, "drafts", pageId),
|
|
115
|
+
(snap) => {
|
|
116
|
+
cb(
|
|
117
|
+
snap.exists() ? sanitizeDraftBlocks({ id: snap.id, ...snap.data() }) : null,
|
|
118
|
+
{ fromServer: !snap.metadata.hasPendingWrites }
|
|
119
|
+
);
|
|
120
|
+
},
|
|
121
|
+
(e) => console.warn("[xicms] werkversie volgen mislukt:", e.message)
|
|
122
|
+
);
|
|
99
123
|
}
|
|
100
124
|
function zonderUndefined(o) {
|
|
101
|
-
return Object.fromEntries(
|
|
125
|
+
return Object.fromEntries(
|
|
126
|
+
Object.entries(o).filter(([, v]) => v !== void 0).map(([k, v]) => [k, diepZonderUndefined(v)])
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
function isPlatObject(v) {
|
|
130
|
+
return typeof v === "object" && v !== null && Object.getPrototypeOf(v) === Object.prototype;
|
|
131
|
+
}
|
|
132
|
+
function diepZonderUndefined(v) {
|
|
133
|
+
if (Array.isArray(v)) return v.map(diepZonderUndefined);
|
|
134
|
+
if (isPlatObject(v)) {
|
|
135
|
+
return Object.fromEntries(Object.entries(v).filter(([, x]) => x !== void 0).map(([k, x]) => [k, diepZonderUndefined(x)]));
|
|
136
|
+
}
|
|
137
|
+
return v;
|
|
138
|
+
}
|
|
139
|
+
function voorMerge(o, maakWis = deleteField) {
|
|
140
|
+
const binnen = (v) => {
|
|
141
|
+
if (Array.isArray(v)) return v.map(diepZonderUndefined);
|
|
142
|
+
if (isPlatObject(v)) return Object.fromEntries(Object.entries(v).map(([k, x]) => [k, x === void 0 ? maakWis() : binnen(x)]));
|
|
143
|
+
return v;
|
|
144
|
+
};
|
|
145
|
+
return Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0).map(([k, v]) => [k, binnen(v)]));
|
|
102
146
|
}
|
|
103
147
|
async function createPage(siteId, input) {
|
|
104
148
|
if (isDemoActive()) {
|
|
@@ -128,15 +172,15 @@ async function saveDraft(siteId, pageId, partial) {
|
|
|
128
172
|
if (!db) return;
|
|
129
173
|
await setDoc(
|
|
130
174
|
doc(db, "sites", siteId, "drafts", pageId),
|
|
131
|
-
{ ...
|
|
175
|
+
{ ...voorMerge(partial), updatedAt: serverTimestamp() },
|
|
132
176
|
{ merge: true }
|
|
133
177
|
);
|
|
134
178
|
}
|
|
135
|
-
async function duplicatePage(siteId, bron) {
|
|
179
|
+
async function duplicatePage(siteId, bron, bestaandePaden = []) {
|
|
136
180
|
const nieuweKey = () => (globalThis.crypto?.randomUUID?.() ?? `b${Date.now()}${Math.random()}`).slice(0, 12);
|
|
137
181
|
const blocks = (bron.blocks ?? []).map((b) => ({ ...b, _key: nieuweKey() }));
|
|
138
|
-
const
|
|
139
|
-
const
|
|
182
|
+
const path = vrijKopiePad(bron.path, bestaandePaden);
|
|
183
|
+
const slug = path.split("/").pop() || `${bron.slug || "pagina"}-kopie`;
|
|
140
184
|
return createPage(siteId, {
|
|
141
185
|
title: `${bron.title} (kopie)`,
|
|
142
186
|
slug,
|
|
@@ -146,6 +190,48 @@ async function duplicatePage(siteId, bron) {
|
|
|
146
190
|
...bron.translations ? { translations: bron.translations } : {}
|
|
147
191
|
});
|
|
148
192
|
}
|
|
193
|
+
function vrijKopiePad(pad, bestaande) {
|
|
194
|
+
const basis = pad === "/" ? "/home-kopie" : `${pad.replace(/\/+$/, "")}-kopie`;
|
|
195
|
+
const bezet = new Set(bestaande);
|
|
196
|
+
if (!bezet.has(basis)) return basis;
|
|
197
|
+
for (let n = 2; ; n++) if (!bezet.has(`${basis}-${n}`)) return `${basis}-${n}`;
|
|
198
|
+
}
|
|
199
|
+
async function publicatieContext(siteId) {
|
|
200
|
+
if (isDemoActive()) return {};
|
|
201
|
+
const db = getDb();
|
|
202
|
+
if (!db) return {};
|
|
203
|
+
try {
|
|
204
|
+
const [drafts, pub] = await Promise.all([
|
|
205
|
+
getDocs(collection(db, "sites", siteId, "drafts")),
|
|
206
|
+
getDoc(doc(db, "sites", siteId, "settings", "public"))
|
|
207
|
+
]);
|
|
208
|
+
const bekendePaden = drafts.docs.filter((d) => !d.get("trashed")).map((d) => String(d.get("path") ?? "")).filter(Boolean);
|
|
209
|
+
const livePaden = drafts.docs.filter((d) => !d.get("trashed") && d.get("published") === true).map((d) => String(d.get("path") ?? "")).filter(Boolean);
|
|
210
|
+
const redirects = (pub.exists() ? pub.get("redirects") : void 0) ?? [];
|
|
211
|
+
return { bekendePaden, livePaden, redirects };
|
|
212
|
+
} catch {
|
|
213
|
+
return {};
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
function planningDatum(v) {
|
|
217
|
+
if (v && typeof v.toDate === "function") return v.toDate();
|
|
218
|
+
if (typeof v === "number" && Number.isFinite(v)) return new Date(v);
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
async function zetPlanning(siteId, pageId, plan) {
|
|
222
|
+
const db = getDb();
|
|
223
|
+
if (!db) throw new Error("Firebase niet geconfigureerd.");
|
|
224
|
+
await setDoc(
|
|
225
|
+
doc(db, "sites", siteId, "drafts", pageId),
|
|
226
|
+
{
|
|
227
|
+
publishAt: plan.publishAt ? Timestamp.fromDate(plan.publishAt) : deleteField(),
|
|
228
|
+
unpublishAt: plan.unpublishAt ? Timestamp.fromDate(plan.unpublishAt) : deleteField(),
|
|
229
|
+
planningFout: deleteField(),
|
|
230
|
+
updatedAt: serverTimestamp()
|
|
231
|
+
},
|
|
232
|
+
{ merge: true }
|
|
233
|
+
);
|
|
234
|
+
}
|
|
149
235
|
async function unpublishPage(siteId, pageId) {
|
|
150
236
|
if (isDemoActive()) return;
|
|
151
237
|
const app = getFirebaseApp();
|
|
@@ -156,7 +242,7 @@ async function trashPage(siteId, page) {
|
|
|
156
242
|
const db = getDb();
|
|
157
243
|
if (!db) return;
|
|
158
244
|
if (page.published) {
|
|
159
|
-
await unpublishPage(siteId, page.id)
|
|
245
|
+
await unpublishPage(siteId, page.id);
|
|
160
246
|
}
|
|
161
247
|
await setDoc(doc(db, "sites", siteId, "drafts", page.id), { trashed: true, trashedAt: serverTimestamp(), updatedAt: serverTimestamp() }, { merge: true });
|
|
162
248
|
}
|
|
@@ -166,16 +252,19 @@ async function restorePage(siteId, pageId) {
|
|
|
166
252
|
await setDoc(doc(db, "sites", siteId, "drafts", pageId), { trashed: false, trashedAt: null, updatedAt: serverTimestamp() }, { merge: true });
|
|
167
253
|
}
|
|
168
254
|
async function deletePage(siteId, pageId) {
|
|
169
|
-
const db = getDb();
|
|
170
|
-
if (!db) return;
|
|
171
|
-
await deleteDoc(doc(db, "sites", siteId, "drafts", pageId));
|
|
172
255
|
const app = getFirebaseApp();
|
|
173
|
-
if (!app)
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
} catch {
|
|
256
|
+
if (!app) {
|
|
257
|
+
const db = getDb();
|
|
258
|
+
if (db) await deleteDoc(doc(db, "sites", siteId, "drafts", pageId));
|
|
259
|
+
return;
|
|
178
260
|
}
|
|
261
|
+
await callable(app, "deletePage")({ siteId, pageId });
|
|
262
|
+
}
|
|
263
|
+
async function getVersieSnapshot(siteId, pageId, versionId) {
|
|
264
|
+
const db = getDb();
|
|
265
|
+
if (!db) return null;
|
|
266
|
+
const snap = await getDoc(doc(db, "sites", siteId, "pages", pageId, "versions", versionId));
|
|
267
|
+
return snap.exists() ? snap.data().snapshot ?? {} : null;
|
|
179
268
|
}
|
|
180
269
|
async function restoreVersion(siteId, pageId, versionId) {
|
|
181
270
|
const db = getDb();
|
|
@@ -189,7 +278,9 @@ async function restoreVersion(siteId, pageId, versionId) {
|
|
|
189
278
|
path: s.path,
|
|
190
279
|
seo: s.seo,
|
|
191
280
|
blocks: s.blocks,
|
|
192
|
-
order: s.order
|
|
281
|
+
order: s.order,
|
|
282
|
+
// Vertalingen hoorden bij de versie maar werden bij herstellen overgeslagen (8.1).
|
|
283
|
+
...s.translations ? { translations: s.translations } : {}
|
|
193
284
|
};
|
|
194
285
|
await saveDraft(siteId, pageId, restored);
|
|
195
286
|
return restored;
|
|
@@ -210,8 +301,11 @@ export {
|
|
|
210
301
|
duplicatePage,
|
|
211
302
|
getDraftByPath,
|
|
212
303
|
getPublishedPage,
|
|
304
|
+
getVersieSnapshot,
|
|
213
305
|
heeftOngepubliceerdeWijzigingen,
|
|
214
306
|
paginaToestand,
|
|
307
|
+
planningDatum,
|
|
308
|
+
publicatieContext,
|
|
215
309
|
publishPage,
|
|
216
310
|
restorePage,
|
|
217
311
|
restoreVersion,
|
|
@@ -220,6 +314,10 @@ export {
|
|
|
220
314
|
subscribePages,
|
|
221
315
|
subscribeVersions,
|
|
222
316
|
trashPage,
|
|
223
|
-
unpublishPage
|
|
317
|
+
unpublishPage,
|
|
318
|
+
voorMerge,
|
|
319
|
+
vrijKopiePad,
|
|
320
|
+
zetPlanning,
|
|
321
|
+
zonderUndefined
|
|
224
322
|
};
|
|
225
323
|
//# sourceMappingURL=pagesClient.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/lib/pagesClient.ts"],"sourcesContent":["\"use client\";\n\nimport {\n addDoc,\n collection,\n deleteDoc,\n doc,\n getDoc,\n getDocs,\n limit,\n onSnapshot,\n orderBy,\n query,\n serverTimestamp,\n setDoc,\n where,\n} from \"firebase/firestore\";\nimport { callable } from \"../../firebase/functionsClient\";\nimport { getDb, getFirebaseApp } from \"../../firebase/client\";\nimport type { Page } from \"../../schema/pages\";\nimport { isDemoActive, demoIdForPath, resolveDemoPage, saveDemoDraft } from \"../../demo/demoStore\";\nimport { sanitizeDraftBlocks } from \"./sanitizeDraft\";\n\nexport type DraftPage = Page & {\n id: string;\n published?: boolean;\n /** Firestore-Timestamp (editor) of ISO-string (import-scripts). */\n updatedAt?: unknown;\n lastPublishedAt?: unknown;\n /** 7.24: in de prullenbak; wordt na 30 dagen definitief verwijderd (purgeTrashedPages). */\n trashed?: boolean;\n trashedAt?: unknown;\n};\n\nfunction ms(v: unknown): number {\n if (v && typeof (v as { toDate?: () => Date }).toDate === \"function\") return (v as { toDate: () => Date }).toDate().getTime();\n if (typeof v === \"string\") {\n const t = Date.parse(v);\n return Number.isNaN(t) ? 0 : t;\n }\n return 0;\n}\n\n/**\n * Staat er op deze pagina iets dat nog niet live is? (7.24)\n *\n * Nooit gepubliceerd = concept (dat is een eigen toestand, geen \"wijziging\"). Gepubliceerd en\n * daarna opnieuw opgeslagen = wijzigingen die niemand ziet. De marge van 1,5 s vangt de\n * autosave die `publishPage` vlak vóór het publiceren zelf doet (die zet `updatedAt` een tel\n * eerder dan `lastPublishedAt`).\n */\nexport function heeftOngepubliceerdeWijzigingen(p: Pick<DraftPage, \"published\" | \"updatedAt\" | \"lastPublishedAt\">): boolean {\n if (!p.published) return false;\n const u = ms(p.updatedAt);\n const l = ms(p.lastPublishedAt);\n if (!u || !l) return false;\n return u - l > 1500;\n}\n\n/** Leesbare toestand voor badges: live / gewijzigd / concept / prullenbak. */\nexport function paginaToestand(p: DraftPage): \"prullenbak\" | \"gewijzigd\" | \"live\" | \"concept\" {\n if (p.trashed) return \"prullenbak\";\n if (heeftOngepubliceerdeWijzigingen(p)) return \"gewijzigd\";\n return p.published ? \"live\" : \"concept\";\n}\n\n/** Live-lijst van pagina's (werkversies in `drafts/`). */\nexport function subscribePages(siteId: string, cb: (pages: DraftPage[]) => void): () => void {\n const db = getDb();\n if (!db) {\n cb([]);\n return () => {};\n }\n const q = query(collection(db, \"sites\", siteId, \"drafts\"), orderBy(\"order\", \"asc\"));\n return onSnapshot(q, (snap) => {\n cb(snap.docs.map((d) => ({ id: d.id, ...(d.data() as Omit<DraftPage, \"id\">) })));\n });\n}\n\nexport function subscribeVersions(\n siteId: string,\n pageId: string,\n cb: (versions: { id: string; createdAt?: unknown; kind?: string; label?: string }[]) => void,\n): () => void {\n const db = getDb();\n if (!db) {\n cb([]);\n return () => {};\n }\n const q = query(collection(db, \"sites\", siteId, \"pages\", pageId, \"versions\"), orderBy(\"createdAt\", \"desc\"));\n return onSnapshot(q, (snap) => cb(snap.docs.map((d) => ({ id: d.id, ...(d.data() as Record<string, unknown>) }))));\n}\n\n/** Laadt de werkversie (draft) voor een pad — voor het inline bewerken op de live pagina. */\nexport async function getDraftByPath(siteId: string, path: string): Promise<DraftPage | null> {\n // Demo-modus: de werkversie komt uit de lokale browseropslag (seed + lokale bewerkingen).\n if (isDemoActive()) {\n const page = resolveDemoPage(siteId, path);\n return page ? ({ ...page, id: demoIdForPath(path) } as DraftPage) : null;\n }\n const db = getDb();\n if (!db) return null;\n const snap = await getDocs(\n query(collection(db, \"sites\", siteId, \"drafts\"), where(\"path\", \"==\", path), limit(1)),\n );\n const d = snap.docs[0];\n // Rich text uit een werkversie is nooit server-side gesaneerd (zie sanitizeDraft.ts).\n return d ? sanitizeDraftBlocks({ id: d.id, ...(d.data() as Omit<DraftPage, \"id\">) }) : null;\n}\n\n/**\n * De GEPUBLICEERDE versie van één pagina — dat wat een gewone bezoeker ziet.\n *\n * De editor heeft dit nodig om te kunnen zeggen wát er veranderd is. Zonder deze tweede lezing\n * weet hij alleen of de werkversie sinds de laatste autosave nog is aangeraakt, en dat is een\n * andere vraag: je kunt een uur geleden iets hebben gewijzigd, keurig opgeslagen, en nog steeds\n * niets live hebben staan. \"Opgeslagen\" en \"gepubliceerd\" zijn twee toestanden, en de knop\n * rechtsonder gaat over de tweede.\n *\n * Publieke leesrechten volstaan: `sites/{id}/pages/{id}` is precies de content die ook in de\n * HTML van de live pagina staat. Geen werkversies, geen inzendingen.\n */\nexport async function getPublishedPage(siteId: string, pageId: string): Promise<Page | null> {\n /**\n * In de demo is \"live\" wat er in de SEED stond — de pagina zoals de prospect hem aantrof.\n *\n * Dit gaf eerst `null` terug (\"nog nooit gepubliceerd\"), en dan telt de vergelijking élk blok\n * als nieuw: op groenhof stond de publiceerknop meteen blauw met \"12 wijzigingen\" en droeg elk\n * blok een rode kantlijn, zonder dat er iets was aangeraakt. Precies het signaal dat \"gewijzigd\"\n * betekenis moet geven, uitgehold op de site die het systeem moet verkopen. Tegen de seed\n * vergeleken start de demo rustig, en kleurt er pas iets zodra de bezoeker echt typt.\n */\n if (isDemoActive()) {\n const { getSeed, demoPathForId } = await import(\"../../demo/demoStore\");\n const pad = demoPathForId(pageId);\n return pad ? getSeed(siteId, pad) : null;\n }\n const db = getDb();\n if (!db) return null;\n const snap = await getDoc(doc(db, \"sites\", siteId, \"pages\", pageId));\n return snap.exists() ? (snap.data() as Page) : null;\n}\n\n/**\n * Live-listener op één werkversie — voor conflictdetectie in de visuele editor (twee tabs of\n * twee editors tegelijk). `fromServer` is false voor de lokale echo van een eigen write\n * (hasPendingWrites), zodat de aanroeper eigen saves kan negeren.\n */\nexport function subscribeDraft(\n siteId: string,\n pageId: string,\n cb: (draft: DraftPage | null, meta: { fromServer: boolean }) => void,\n): () => void {\n if (isDemoActive()) return () => {};\n const db = getDb();\n if (!db) return () => {};\n return onSnapshot(doc(db, \"sites\", siteId, \"drafts\", pageId), (snap) => {\n cb(\n snap.exists() ? sanitizeDraftBlocks({ id: snap.id, ...(snap.data() as Omit<DraftPage, \"id\">) }) : null,\n { fromServer: !snap.metadata.hasPendingWrites },\n );\n });\n}\n\n/**\n * Firestore WEIGERT `undefined` als veldwaarde: `setDoc({ seo: undefined })` gooit\n * \"Unsupported field value: undefined\" en de hele schrijfactie mislukt. De editors geven hun\n * pagina-state als geheel door (`{ title, slug, path, seo, blocks }`), en bij een nieuwe pagina is\n * `seo` gewoon nog niet ingevuld — dus liep élke eerste publicatie van een nieuwe pagina stuk op\n * de autosave die eraan voorafgaat. Gevonden op de staging-deploy van groenhof: \"Publiceren\n * mislukt — Unsupported field value: undefined (found in field seo)\".\n *\n * Weglaten is hier ook de JUISTE semantiek: deze writes zijn `merge: true`, en \"veld niet\n * meegestuurd\" betekent daar \"laat staan wat er stond\". Een veld écht wissen doe je met\n * `deleteField()`, niet met `undefined` — dat onderscheid blijft zo intact.\n */\nfunction zonderUndefined<T extends Record<string, unknown>>(o: T): Partial<T> {\n return Object.fromEntries(Object.entries(o).filter(([, v]) => v !== undefined)) as Partial<T>;\n}\n\nexport async function createPage(siteId: string, input: Partial<Page> & { title: string; slug: string; path: string }) {\n /**\n * In de demo bestaat er geen Firestore om naar te schrijven.\n *\n * Deze functie was als enige van dit bestand de demo-tak vergeten — `getDraftByPath`,\n * `saveDraft` en `publishPage` hebben hem alle drie. Gevolg: wie in een demo op\n * \"+ Pagina aanmaken\" drukte, kreeg \"Missing or insufficient permissions\" van de echte\n * database. Op een site die bestaat om te laten zien hoe prettig het bewerken werkt.\n */\n if (isDemoActive()) {\n const id = demoIdForPath(input.path);\n saveDemoDraft(siteId, id, { blocks: [], status: \"draft\", ...zonderUndefined(input) } as Partial<Page>);\n return id;\n }\n const db = getDb();\n if (!db) throw new Error(\"Firebase niet geconfigureerd.\");\n const ref = await addDoc(collection(db, \"sites\", siteId, \"drafts\"), {\n parentId: null,\n order: Date.now(),\n status: \"draft\",\n blocks: [],\n published: false,\n ...zonderUndefined(input),\n updatedAt: serverTimestamp(),\n });\n return ref.id;\n}\n\nexport async function saveDraft(siteId: string, pageId: string, partial: Partial<Page>) {\n // Demo-modus: bewaar de bewerking alleen lokaal in de browser (niet in Firebase).\n if (isDemoActive()) {\n saveDemoDraft(siteId, pageId, partial);\n return;\n }\n const db = getDb();\n if (!db) return;\n await setDoc(\n doc(db, \"sites\", siteId, \"drafts\", pageId),\n { ...zonderUndefined(partial), updatedAt: serverTimestamp() },\n { merge: true },\n );\n}\n\n/**\n * Pagina dupliceren (7.24): nieuwe draft met dezelfde blokken (verse `_key`s), als concept, met\n * \"-kopie\" achter pad en titel zodat hij nergens mee botst.\n */\nexport async function duplicatePage(siteId: string, bron: DraftPage): Promise<string> {\n const nieuweKey = () => (globalThis.crypto?.randomUUID?.() ?? `b${Date.now()}${Math.random()}`).slice(0, 12);\n const blocks = (bron.blocks ?? []).map((b) => ({ ...(b as Record<string, unknown>), _key: nieuweKey() })) as Page[\"blocks\"];\n const slug = `${bron.slug || \"pagina\"}-kopie`;\n const path = bron.path === \"/\" ? \"/home-kopie\" : `${bron.path}-kopie`;\n return createPage(siteId, {\n title: `${bron.title} (kopie)`,\n slug,\n path,\n blocks,\n ...(bron.seo ? { seo: bron.seo } : {}),\n ...(bron.translations ? { translations: bron.translations } : {}),\n });\n}\n\n/** Offline halen (7.24): de live versie verdwijnt, de werkversie blijft. Via de callable. */\nexport async function unpublishPage(siteId: string, pageId: string): Promise<void> {\n if (isDemoActive()) return;\n const app = getFirebaseApp();\n if (!app) throw new Error(\"Firebase niet geconfigureerd.\");\n await callable(app, \"unpublishPage\")({ siteId, pageId });\n}\n\n/**\n * Naar de prullenbak (7.24): offline halen + markeren. Geen delete — een klant die per ongeluk\n * op \"verwijderen\" drukte kon tot 7.24 alleen via een herstelpunt terug. Na 30 dagen ruimt de\n * backend definitief op (purgeTrashedPages).\n */\nexport async function trashPage(siteId: string, page: DraftPage): Promise<void> {\n const db = getDb();\n if (!db) return;\n if (page.published) {\n // Nog live? Eerst offline. Een pagina die niet live staat heeft niets om offline te halen.\n await unpublishPage(siteId, page.id).catch(() => undefined);\n }\n await setDoc(doc(db, \"sites\", siteId, \"drafts\", page.id), { trashed: true, trashedAt: serverTimestamp(), updatedAt: serverTimestamp() }, { merge: true });\n}\n\n/** Terug uit de prullenbak: als concept (publiceren is een bewuste tweede stap). */\nexport async function restorePage(siteId: string, pageId: string): Promise<void> {\n const db = getDb();\n if (!db) return;\n await setDoc(doc(db, \"sites\", siteId, \"drafts\", pageId), { trashed: false, trashedAt: null, updatedAt: serverTimestamp() }, { merge: true });\n}\n\nexport async function deletePage(siteId: string, pageId: string) {\n const db = getDb();\n if (!db) return;\n // Draft direct (client mag drafts beheren); de gepubliceerde pagina via de Cloud Function —\n // die verwijdert óók de versions-subcollectie (Firestore cascadeert niet; directe client-deletes\n // lieten wees-versiegeschiedenis achter en zijn nu in de rules dichtgezet).\n await deleteDoc(doc(db, \"sites\", siteId, \"drafts\", pageId));\n const app = getFirebaseApp();\n if (!app) return;\n try {\n const fn = callable(app, \"deletePage\");\n await fn({ siteId, pageId });\n } catch {\n /* gepubliceerde versie bestond niet — negeren */\n }\n}\n\n/**\n * Herstelt een eerdere versie naar de WERKVERSIE (draft). De gebruiker moet daarna zelf\n * publiceren — zo is herstellen veilig en omkeerbaar (de live site verandert pas bij Publiceren).\n * Geeft de herstelde velden terug zodat de editor zijn lokale state kan bijwerken.\n */\nexport async function restoreVersion(\n siteId: string,\n pageId: string,\n versionId: string,\n): Promise<Partial<Page>> {\n const db = getDb();\n if (!db) throw new Error(\"Firebase niet geconfigureerd.\");\n const snap = await getDoc(doc(db, \"sites\", siteId, \"pages\", pageId, \"versions\", versionId));\n if (!snap.exists()) throw new Error(\"Versie niet gevonden.\");\n const s = (snap.data().snapshot ?? {}) as Record<string, unknown>;\n const restored: Partial<Page> = {\n title: s.title as string,\n slug: s.slug as string,\n path: s.path as string,\n seo: s.seo as Page[\"seo\"],\n blocks: s.blocks as Page[\"blocks\"],\n order: s.order as number,\n };\n await saveDraft(siteId, pageId, restored);\n return restored;\n}\n\n/** Publiceren via de Cloud Function (server-side validatie + versie-snapshot + audit). */\nexport async function publishPage(siteId: string, pageId: string): Promise<void> {\n // Demo-modus: er wordt niets echt gepubliceerd; de lokale bewerking is al zichtbaar.\n if (isDemoActive()) {\n saveDemoDraft(siteId, pageId, {});\n return;\n }\n const app = getFirebaseApp();\n if (!app) throw new Error(\"Firebase niet geconfigureerd.\");\n const fn = callable(app, \"publishPage\");\n await fn({ siteId, pageId });\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/lib/pagesClient.ts"],"sourcesContent":["\"use client\";\n\nimport {\n addDoc,\n collection,\n deleteDoc,\n deleteField,\n doc,\n getDoc,\n getDocs,\n limit,\n onSnapshot,\n orderBy,\n query,\n serverTimestamp,\n setDoc,\n Timestamp,\n where,\n} from \"firebase/firestore\";\nimport { callable } from \"../../firebase/functionsClient\";\nimport { getDb, getFirebaseApp } from \"../../firebase/client\";\nimport type { Page } from \"../../schema/pages\";\nimport { DEMO_EVENT, isDemoActive, demoIdForPath, listDemoPages, resolveDemoPage, saveDemoDraft } from \"../../demo/demoStore\";\nimport { sanitizeDraftBlocks } from \"./sanitizeDraft\";\n\nexport type DraftPage = Page & {\n id: string;\n published?: boolean;\n /** Firestore-Timestamp (editor) of ISO-string (import-scripts). */\n updatedAt?: unknown;\n lastPublishedAt?: unknown;\n /** 7.24: in de prullenbak; wordt na 30 dagen definitief verwijderd (purgeTrashedPages). */\n trashed?: boolean;\n trashedAt?: unknown;\n /** 8.4: ingepland publiceren / offline halen (Firestore-Timestamp); de planner wist ze na uitvoering. */\n publishAt?: unknown;\n unpublishAt?: unknown;\n /** 8.4: waarom een ingeplande actie mislukte (gezet door de planner). */\n planningFout?: string;\n};\n\nfunction ms(v: unknown): number {\n if (v && typeof (v as { toDate?: () => Date }).toDate === \"function\") return (v as { toDate: () => Date }).toDate().getTime();\n if (typeof v === \"string\") {\n const t = Date.parse(v);\n return Number.isNaN(t) ? 0 : t;\n }\n return 0;\n}\n\n/**\n * Staat er op deze pagina iets dat nog niet live is? (7.24)\n *\n * Nooit gepubliceerd = concept (dat is een eigen toestand, geen \"wijziging\"). Gepubliceerd en\n * daarna opnieuw opgeslagen = wijzigingen die niemand ziet. De marge van 1,5 s vangt de\n * autosave die `publishPage` vlak vóór het publiceren zelf doet (die zet `updatedAt` een tel\n * eerder dan `lastPublishedAt`).\n */\nexport function heeftOngepubliceerdeWijzigingen(p: Pick<DraftPage, \"published\" | \"updatedAt\" | \"lastPublishedAt\">): boolean {\n if (!p.published) return false;\n const u = ms(p.updatedAt);\n const l = ms(p.lastPublishedAt);\n if (!u || !l) return false;\n return u - l > 1500;\n}\n\n/** Leesbare toestand voor badges: live / gewijzigd / concept / prullenbak. */\nexport function paginaToestand(p: DraftPage): \"prullenbak\" | \"gewijzigd\" | \"live\" | \"concept\" {\n if (p.trashed) return \"prullenbak\";\n if (heeftOngepubliceerdeWijzigingen(p)) return \"gewijzigd\";\n return p.published ? \"live\" : \"concept\";\n}\n\n/** Live-lijst van pagina's (werkversies in `drafts/`). */\nexport function subscribePages(siteId: string, cb: (pages: DraftPage[]) => void, onError?: (e: Error) => void): () => void {\n // Demo: de pagina's die de demo kent, meteen — geen echte database (die weigert toch).\n if (isDemoActive()) {\n const sync = () => cb(listDemoPages(siteId) as unknown as DraftPage[]);\n sync();\n window.addEventListener(DEMO_EVENT, sync);\n return () => window.removeEventListener(DEMO_EVENT, sync);\n }\n const db = getDb();\n if (!db) {\n cb([]);\n return () => {};\n }\n const q = query(collection(db, \"sites\", siteId, \"drafts\"), orderBy(\"order\", \"asc\"));\n return onSnapshot(\n q,\n (snap) => {\n cb(snap.docs.map((d) => ({ id: d.id, ...(d.data() as Omit<DraftPage, \"id\">) })));\n },\n // 8.1: zonder error-callback bleef een rechten-/verbindingsfout onzichtbaar (\"nog geen pagina's\").\n (e) => {\n console.error(\"[xicms] pagina's laden mislukt:\", e);\n onError?.(e);\n },\n );\n}\n\nexport function subscribeVersions(\n siteId: string,\n pageId: string,\n cb: (versions: { id: string; createdAt?: unknown; kind?: string; label?: string }[]) => void,\n): () => void {\n const db = getDb();\n if (!db) {\n cb([]);\n return () => {};\n }\n const q = query(collection(db, \"sites\", siteId, \"pages\", pageId, \"versions\"), orderBy(\"createdAt\", \"desc\"));\n return onSnapshot(\n q,\n (snap) => cb(snap.docs.map((d) => ({ id: d.id, ...(d.data() as Record<string, unknown>) }))),\n (e) => console.warn(\"[xicms] live-lijst niet geladen:\", e.message),\n );\n}\n\n/** Laadt de werkversie (draft) voor een pad — voor het inline bewerken op de live pagina. */\nexport async function getDraftByPath(siteId: string, path: string): Promise<DraftPage | null> {\n // Demo-modus: de werkversie komt uit de lokale browseropslag (seed + lokale bewerkingen).\n if (isDemoActive()) {\n const page = resolveDemoPage(siteId, path);\n return page ? ({ ...page, id: demoIdForPath(path) } as DraftPage) : null;\n }\n const db = getDb();\n if (!db) return null;\n const snap = await getDocs(\n query(collection(db, \"sites\", siteId, \"drafts\"), where(\"path\", \"==\", path), limit(1)),\n );\n const d = snap.docs[0];\n // Rich text uit een werkversie is nooit server-side gesaneerd (zie sanitizeDraft.ts).\n return d ? sanitizeDraftBlocks({ id: d.id, ...(d.data() as Omit<DraftPage, \"id\">) }) : null;\n}\n\n/**\n * De GEPUBLICEERDE versie van één pagina — dat wat een gewone bezoeker ziet.\n *\n * De editor heeft dit nodig om te kunnen zeggen wát er veranderd is. Zonder deze tweede lezing\n * weet hij alleen of de werkversie sinds de laatste autosave nog is aangeraakt, en dat is een\n * andere vraag: je kunt een uur geleden iets hebben gewijzigd, keurig opgeslagen, en nog steeds\n * niets live hebben staan. \"Opgeslagen\" en \"gepubliceerd\" zijn twee toestanden, en de knop\n * rechtsonder gaat over de tweede.\n *\n * Publieke leesrechten volstaan: `sites/{id}/pages/{id}` is precies de content die ook in de\n * HTML van de live pagina staat. Geen werkversies, geen inzendingen.\n */\nexport async function getPublishedPage(siteId: string, pageId: string): Promise<Page | null> {\n /**\n * In de demo is \"live\" wat er in de SEED stond — de pagina zoals de prospect hem aantrof.\n *\n * Dit gaf eerst `null` terug (\"nog nooit gepubliceerd\"), en dan telt de vergelijking élk blok\n * als nieuw: op groenhof stond de publiceerknop meteen blauw met \"12 wijzigingen\" en droeg elk\n * blok een rode kantlijn, zonder dat er iets was aangeraakt. Precies het signaal dat \"gewijzigd\"\n * betekenis moet geven, uitgehold op de site die het systeem moet verkopen. Tegen de seed\n * vergeleken start de demo rustig, en kleurt er pas iets zodra de bezoeker echt typt.\n */\n if (isDemoActive()) {\n const { getSeed, demoPathForId } = await import(\"../../demo/demoStore\");\n const pad = demoPathForId(pageId);\n return pad ? getSeed(siteId, pad) : null;\n }\n const db = getDb();\n if (!db) return null;\n const snap = await getDoc(doc(db, \"sites\", siteId, \"pages\", pageId));\n return snap.exists() ? (snap.data() as Page) : null;\n}\n\n/**\n * Live-listener op één werkversie — voor conflictdetectie in de visuele editor (twee tabs of\n * twee editors tegelijk). `fromServer` is false voor de lokale echo van een eigen write\n * (hasPendingWrites), zodat de aanroeper eigen saves kan negeren.\n */\nexport function subscribeDraft(\n siteId: string,\n pageId: string,\n cb: (draft: DraftPage | null, meta: { fromServer: boolean }) => void,\n): () => void {\n if (isDemoActive()) return () => {};\n const db = getDb();\n if (!db) return () => {};\n return onSnapshot(\n doc(db, \"sites\", siteId, \"drafts\", pageId),\n (snap) => {\n cb(\n snap.exists() ? sanitizeDraftBlocks({ id: snap.id, ...(snap.data() as Omit<DraftPage, \"id\">) }) : null,\n { fromServer: !snap.metadata.hasPendingWrites },\n );\n },\n (e) => console.warn(\"[xicms] werkversie volgen mislukt:\", e.message),\n );\n}\n\n/**\n * Firestore WEIGERT `undefined` als veldwaarde: `setDoc({ seo: undefined })` gooit\n * \"Unsupported field value: undefined\" en de hele schrijfactie mislukt. De editors geven hun\n * pagina-state als geheel door (`{ title, slug, path, seo, blocks }`), en bij een nieuwe pagina is\n * `seo` gewoon nog niet ingevuld — dus liep élke eerste publicatie van een nieuwe pagina stuk op\n * de autosave die eraan voorafgaat. Gevonden op de staging-deploy van groenhof: \"Publiceren\n * mislukt — Unsupported field value: undefined (found in field seo)\".\n *\n * Weglaten is hier ook de JUISTE semantiek: deze writes zijn `merge: true`, en \"veld niet\n * meegestuurd\" betekent daar \"laat staan wat er stond\". Een veld écht wissen doe je met\n * `deleteField()`, niet met `undefined` — dat onderscheid blijft zo intact.\n */\nexport function zonderUndefined<T extends Record<string, unknown>>(o: T): Partial<T> {\n return Object.fromEntries(\n Object.entries(o)\n .filter(([, v]) => v !== undefined)\n .map(([k, v]) => [k, diepZonderUndefined(v)]),\n ) as Partial<T>;\n}\n\nfunction isPlatObject(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && Object.getPrototypeOf(v) === Object.prototype;\n}\n\n/** Haalt `undefined` ook uit geneste objecten en arrays (blokken, seo) — voor `addDoc`/zonder merge. */\nfunction diepZonderUndefined(v: unknown): unknown {\n if (Array.isArray(v)) return v.map(diepZonderUndefined);\n if (isPlatObject(v)) {\n return Object.fromEntries(Object.entries(v).filter(([, x]) => x !== undefined).map(([k, x]) => [k, diepZonderUndefined(x)]));\n }\n return v;\n}\n\n/**\n * Voor `setDoc(…, { merge: true })` (8.1). Bovenaan blijft \"undefined = niet meesturen = laten\n * staan\". Maar ín een object (bv. `seo`) betekent `undefined` dat de gebruiker het veld leegmaakte:\n * dat wordt `deleteField()`. Tot 8.1 gaf \"niet in Google\" uitvinken of de deelafbeelding weghalen\n * een geneste `undefined` — Firestore weigert die en de hele autosave mislukte. En gewoon weglaten\n * had het oude veld laten staan, want merge werkt per veld door in objecten.\n * Binnen arrays kan `deleteField()` niet; daar wordt `undefined` gewoon weggelaten.\n */\nexport function voorMerge<T extends Record<string, unknown>>(o: T, maakWis: () => unknown = deleteField): Record<string, unknown> {\n const binnen = (v: unknown): unknown => {\n if (Array.isArray(v)) return v.map(diepZonderUndefined);\n if (isPlatObject(v)) return Object.fromEntries(Object.entries(v).map(([k, x]) => [k, x === undefined ? maakWis() : binnen(x)]));\n return v;\n };\n return Object.fromEntries(Object.entries(o).filter(([, v]) => v !== undefined).map(([k, v]) => [k, binnen(v)]));\n}\n\nexport async function createPage(siteId: string, input: Partial<Page> & { title: string; slug: string; path: string }) {\n /**\n * In de demo bestaat er geen Firestore om naar te schrijven.\n *\n * Deze functie was als enige van dit bestand de demo-tak vergeten — `getDraftByPath`,\n * `saveDraft` en `publishPage` hebben hem alle drie. Gevolg: wie in een demo op\n * \"+ Pagina aanmaken\" drukte, kreeg \"Missing or insufficient permissions\" van de echte\n * database. Op een site die bestaat om te laten zien hoe prettig het bewerken werkt.\n */\n if (isDemoActive()) {\n const id = demoIdForPath(input.path);\n saveDemoDraft(siteId, id, { blocks: [], status: \"draft\", ...zonderUndefined(input) } as Partial<Page>);\n return id;\n }\n const db = getDb();\n if (!db) throw new Error(\"Firebase niet geconfigureerd.\");\n const ref = await addDoc(collection(db, \"sites\", siteId, \"drafts\"), {\n parentId: null,\n order: Date.now(),\n status: \"draft\",\n blocks: [],\n published: false,\n ...zonderUndefined(input),\n updatedAt: serverTimestamp(),\n });\n return ref.id;\n}\n\nexport async function saveDraft(siteId: string, pageId: string, partial: Partial<Page>) {\n // Demo-modus: bewaar de bewerking alleen lokaal in de browser (niet in Firebase).\n if (isDemoActive()) {\n saveDemoDraft(siteId, pageId, partial);\n return;\n }\n const db = getDb();\n if (!db) return;\n await setDoc(\n doc(db, \"sites\", siteId, \"drafts\", pageId),\n { ...voorMerge(partial), updatedAt: serverTimestamp() },\n { merge: true },\n );\n}\n\n/**\n * Pagina dupliceren (7.24): nieuwe draft met dezelfde blokken (verse `_key`s), als concept, met\n * \"-kopie\" achter pad en titel zodat hij nergens mee botst.\n */\nexport async function duplicatePage(siteId: string, bron: DraftPage, bestaandePaden: string[] = []): Promise<string> {\n const nieuweKey = () => (globalThis.crypto?.randomUUID?.() ?? `b${Date.now()}${Math.random()}`).slice(0, 12);\n const blocks = (bron.blocks ?? []).map((b) => ({ ...(b as Record<string, unknown>), _key: nieuweKey() })) as Page[\"blocks\"];\n // Twee keer dupliceren gaf twee keer \"-kopie\": dezelfde URL, één van beide onbereikbaar (8.1).\n const path = vrijKopiePad(bron.path, bestaandePaden);\n const slug = path.split(\"/\").pop() || `${bron.slug || \"pagina\"}-kopie`;\n return createPage(siteId, {\n title: `${bron.title} (kopie)`,\n slug,\n path,\n blocks,\n ...(bron.seo ? { seo: bron.seo } : {}),\n ...(bron.translations ? { translations: bron.translations } : {}),\n });\n}\n\n/** Eerstvolgend vrij kopie-pad: `/over-kopie`, dan `/over-kopie-2`, `/over-kopie-3`, … */\nexport function vrijKopiePad(pad: string, bestaande: string[]): string {\n const basis = pad === \"/\" ? \"/home-kopie\" : `${pad.replace(/\\/+$/, \"\")}-kopie`;\n const bezet = new Set(bestaande);\n if (!bezet.has(basis)) return basis;\n for (let n = 2; ; n++) if (!bezet.has(`${basis}-${n}`)) return `${basis}-${n}`;\n}\n\n/**\n * Wat de publicatiecontrole moet weten (8.4): de paden die bestaan (werkversies, niet in de\n * prullenbak) en de doorverwijzingen. Leesfouten = geen linkcontrole (liever geen waarschuwing dan\n * een valse).\n */\nexport async function publicatieContext(siteId: string): Promise<{ bekendePaden?: string[]; livePaden?: string[]; redirects?: { from: string }[] }> {\n if (isDemoActive()) return {};\n const db = getDb();\n if (!db) return {};\n try {\n const [drafts, pub] = await Promise.all([\n getDocs(collection(db, \"sites\", siteId, \"drafts\")),\n getDoc(doc(db, \"sites\", siteId, \"settings\", \"public\")),\n ]);\n const bekendePaden = drafts.docs.filter((d) => !d.get(\"trashed\")).map((d) => String(d.get(\"path\") ?? \"\")).filter(Boolean);\n // 8.10: ook welke ervan live staan — een link naar een concept is voor bezoekers een 404.\n const livePaden = drafts.docs.filter((d) => !d.get(\"trashed\") && d.get(\"published\") === true).map((d) => String(d.get(\"path\") ?? \"\")).filter(Boolean);\n const redirects = (pub.exists() ? (pub.get(\"redirects\") as { from: string }[] | undefined) : undefined) ?? [];\n return { bekendePaden, livePaden, redirects };\n } catch {\n return {};\n }\n}\n\n/** Tijdstip uit een Firestore-Timestamp (of ms), voor de planning-UI. */\nexport function planningDatum(v: unknown): Date | null {\n if (v && typeof (v as { toDate?: () => Date }).toDate === \"function\") return (v as { toDate: () => Date }).toDate();\n if (typeof v === \"number\" && Number.isFinite(v)) return new Date(v);\n return null;\n}\n\n/**\n * Planning zetten of wissen (8.4). `null` = wissen. De planner in de functions (`geplandePaginas`,\n * elke 10 minuten) voert hem uit via dezelfde weg als de knoppen.\n */\nexport async function zetPlanning(siteId: string, pageId: string, plan: { publishAt: Date | null; unpublishAt: Date | null }): Promise<void> {\n const db = getDb();\n if (!db) throw new Error(\"Firebase niet geconfigureerd.\");\n await setDoc(\n doc(db, \"sites\", siteId, \"drafts\", pageId),\n {\n publishAt: plan.publishAt ? Timestamp.fromDate(plan.publishAt) : deleteField(),\n unpublishAt: plan.unpublishAt ? Timestamp.fromDate(plan.unpublishAt) : deleteField(),\n planningFout: deleteField(),\n updatedAt: serverTimestamp(),\n },\n { merge: true },\n );\n}\n\n/** Offline halen (7.24): de live versie verdwijnt, de werkversie blijft. Via de callable. */\nexport async function unpublishPage(siteId: string, pageId: string): Promise<void> {\n if (isDemoActive()) return;\n const app = getFirebaseApp();\n if (!app) throw new Error(\"Firebase niet geconfigureerd.\");\n await callable(app, \"unpublishPage\")({ siteId, pageId });\n}\n\n/**\n * Naar de prullenbak (7.24): offline halen + markeren. Geen delete — een klant die per ongeluk\n * op \"verwijderen\" drukte kon tot 7.24 alleen via een herstelpunt terug. Na 30 dagen ruimt de\n * backend definitief op (purgeTrashedPages).\n */\nexport async function trashPage(siteId: string, page: DraftPage): Promise<void> {\n const db = getDb();\n if (!db) return;\n if (page.published) {\n // Nog live? Eerst offline. Mislukt dat, dan NIET als \"in de prullenbak\" markeren: de pagina\n // zou dan live blijven terwijl de klant denkt dat hij weg is (tot 8.1 werd de fout geslikt).\n await unpublishPage(siteId, page.id);\n }\n await setDoc(doc(db, \"sites\", siteId, \"drafts\", page.id), { trashed: true, trashedAt: serverTimestamp(), updatedAt: serverTimestamp() }, { merge: true });\n}\n\n/** Terug uit de prullenbak: als concept (publiceren is een bewuste tweede stap). */\nexport async function restorePage(siteId: string, pageId: string): Promise<void> {\n const db = getDb();\n if (!db) return;\n await setDoc(doc(db, \"sites\", siteId, \"drafts\", pageId), { trashed: false, trashedAt: null, updatedAt: serverTimestamp() }, { merge: true });\n}\n\nexport async function deletePage(siteId: string, pageId: string) {\n // Alles via de Cloud Function: die verwijdert in één batch de live pagina, de versions-\n // subcollectie én de werkversie. Tot 8.1 ging eerst de werkversie weg en werd élke fout van de\n // callable geslikt — mislukte die, dan bleef de pagina live zonder werkversie, onbeheerbaar.\n const app = getFirebaseApp();\n if (!app) {\n const db = getDb();\n if (db) await deleteDoc(doc(db, \"sites\", siteId, \"drafts\", pageId));\n return;\n }\n await callable(app, \"deletePage\")({ siteId, pageId });\n}\n\n/** De inhoud van één opgeslagen versie (8.4: om te vergelijken vóór herstellen). */\nexport async function getVersieSnapshot(siteId: string, pageId: string, versionId: string): Promise<Partial<Page> | null> {\n const db = getDb();\n if (!db) return null;\n const snap = await getDoc(doc(db, \"sites\", siteId, \"pages\", pageId, \"versions\", versionId));\n return snap.exists() ? ((snap.data().snapshot ?? {}) as Partial<Page>) : null;\n}\n\n/**\n * Herstelt een eerdere versie naar de WERKVERSIE (draft). De gebruiker moet daarna zelf\n * publiceren — zo is herstellen veilig en omkeerbaar (de live site verandert pas bij Publiceren).\n * Geeft de herstelde velden terug zodat de editor zijn lokale state kan bijwerken.\n */\nexport async function restoreVersion(\n siteId: string,\n pageId: string,\n versionId: string,\n): Promise<Partial<Page>> {\n const db = getDb();\n if (!db) throw new Error(\"Firebase niet geconfigureerd.\");\n const snap = await getDoc(doc(db, \"sites\", siteId, \"pages\", pageId, \"versions\", versionId));\n if (!snap.exists()) throw new Error(\"Versie niet gevonden.\");\n const s = (snap.data().snapshot ?? {}) as Record<string, unknown>;\n const restored: Partial<Page> = {\n title: s.title as string,\n slug: s.slug as string,\n path: s.path as string,\n seo: s.seo as Page[\"seo\"],\n blocks: s.blocks as Page[\"blocks\"],\n order: s.order as number,\n // Vertalingen hoorden bij de versie maar werden bij herstellen overgeslagen (8.1).\n ...(s.translations ? { translations: s.translations as Page[\"translations\"] } : {}),\n };\n await saveDraft(siteId, pageId, restored);\n return restored;\n}\n\n/** Publiceren via de Cloud Function (server-side validatie + versie-snapshot + audit). */\nexport async function publishPage(siteId: string, pageId: string): Promise<void> {\n // Demo-modus: er wordt niets echt gepubliceerd; de lokale bewerking is al zichtbaar.\n if (isDemoActive()) {\n saveDemoDraft(siteId, pageId, {});\n return;\n }\n const app = getFirebaseApp();\n if (!app) throw new Error(\"Firebase niet geconfigureerd.\");\n const fn = callable(app, \"publishPage\");\n await fn({ siteId, pageId });\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Waar wordt een pagina gebruikt? (8.10)
|
|
3
|
+
*
|
|
4
|
+
* Een pagina weggooien of offline halen brak tot nu toe stil het menu, de footer en elke knop die
|
|
5
|
+
* ernaar wees: allemaal een 404, en de klant merkte het pas als een bezoeker het meldde. Deze
|
|
6
|
+
* zoektocht levert de plekken op, zodat de bevestiging ze kan noemen ("staat in het menu en op
|
|
7
|
+
* 2 pagina's").
|
|
8
|
+
*
|
|
9
|
+
* Het zoeken zelf is puur (`vindVerwijzingen`) en daarmee testbaar; `laadPaginaGebruik` haalt de
|
|
10
|
+
* gegevens op. Sites zijn klein, dus één read van menu, footer en alle werkversies volstaat.
|
|
11
|
+
*/
|
|
12
|
+
interface LinkItem {
|
|
13
|
+
href?: string;
|
|
14
|
+
label?: string;
|
|
15
|
+
children?: LinkItem[];
|
|
16
|
+
}
|
|
17
|
+
export interface Verwijzingen {
|
|
18
|
+
/** Menu-items (labels) die naar de pagina wijzen. */
|
|
19
|
+
menu: string[];
|
|
20
|
+
/** Footer-links (labels) die naar de pagina wijzen. */
|
|
21
|
+
footer: string[];
|
|
22
|
+
/** Andere pagina's (titel of pad) met een link naar de pagina. */
|
|
23
|
+
paginas: string[];
|
|
24
|
+
}
|
|
25
|
+
/** Het eigen pad uit een href ("/over-ons#team?x" → "/over-ons"); extern of leeg → null. */
|
|
26
|
+
export declare function padVanHref(href: string): string | null;
|
|
27
|
+
/** Alle hrefs in een stuk blokdata: velden die op `href` eindigen én links in rich text. */
|
|
28
|
+
export declare function hrefsIn(v: unknown, uit?: string[], diepte?: number): string[];
|
|
29
|
+
export declare function vindVerwijzingen(pad: string, bron: {
|
|
30
|
+
nav?: LinkItem[];
|
|
31
|
+
footer?: {
|
|
32
|
+
columns?: {
|
|
33
|
+
links?: LinkItem[];
|
|
34
|
+
}[];
|
|
35
|
+
};
|
|
36
|
+
paginas?: {
|
|
37
|
+
id?: string;
|
|
38
|
+
title?: string;
|
|
39
|
+
path?: string;
|
|
40
|
+
blocks?: unknown[];
|
|
41
|
+
trashed?: boolean;
|
|
42
|
+
}[];
|
|
43
|
+
}, eigenId?: string): Verwijzingen;
|
|
44
|
+
/** Eén zin voor in een bevestiging, of null als de pagina nergens gebruikt wordt. */
|
|
45
|
+
export declare function verwijzingenZin(v: Verwijzingen): string | null;
|
|
46
|
+
/** Leest menu, footer en alle werkversies. Leesfout → null (dan geen extra zin, geen valse belofte). */
|
|
47
|
+
export declare function laadPaginaGebruik(siteId: string, pad: string, eigenId?: string): Promise<Verwijzingen | null>;
|
|
48
|
+
/** Bevat deze blokdata (ook genest: kolommen, tabs) een blok dat aan `test` voldoet? */
|
|
49
|
+
export declare function bevatBlok(v: unknown, test: (b: Record<string, unknown>) => boolean, diepte?: number): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Pagina's (titels/paden) met een formulierblok voor dit formulier — live én werkversie. Een
|
|
52
|
+
* verwijderd formulier liet het contactblok "niet beschikbaar" tonen, en de klant merkte het pas
|
|
53
|
+
* aan de leads die uitbleven. Leesfout → null.
|
|
54
|
+
*/
|
|
55
|
+
export declare function laadFormulierGebruik(siteId: string, formId: string): Promise<string[] | null>;
|
|
56
|
+
export {};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { collection, doc, getDoc, getDocs } from "firebase/firestore";
|
|
3
|
+
import { getDb } from "../../firebase/client.js";
|
|
4
|
+
import { isDemoActive } from "../../demo/demoStore.js";
|
|
5
|
+
function padVanHref(href) {
|
|
6
|
+
const h = href.trim();
|
|
7
|
+
if (!h.startsWith("/") || h.startsWith("//")) return null;
|
|
8
|
+
return h.split(/[?#]/)[0].replace(/\/+$/, "") || "/";
|
|
9
|
+
}
|
|
10
|
+
function hrefsIn(v, uit = [], diepte = 0) {
|
|
11
|
+
if (diepte > 14 || v == null) return uit;
|
|
12
|
+
if (Array.isArray(v)) {
|
|
13
|
+
for (const x of v) hrefsIn(x, uit, diepte + 1);
|
|
14
|
+
return uit;
|
|
15
|
+
}
|
|
16
|
+
if (typeof v !== "object") return uit;
|
|
17
|
+
for (const [k, x] of Object.entries(v)) {
|
|
18
|
+
if (typeof x === "string") {
|
|
19
|
+
if (/href$/i.test(k)) uit.push(x);
|
|
20
|
+
else if (x.includes("href=")) for (const m of x.matchAll(/href\s*=\s*["']([^"']+)["']/gi)) uit.push(m[1]);
|
|
21
|
+
} else if (typeof x === "object") hrefsIn(x, uit, diepte + 1);
|
|
22
|
+
}
|
|
23
|
+
return uit;
|
|
24
|
+
}
|
|
25
|
+
function vindVerwijzingen(pad, bron, eigenId) {
|
|
26
|
+
const doel = padVanHref(pad) ?? pad;
|
|
27
|
+
const wijstNaar = (href) => typeof href === "string" && padVanHref(href) === doel;
|
|
28
|
+
const menu = [];
|
|
29
|
+
for (const item of bron.nav ?? []) {
|
|
30
|
+
if (wijstNaar(item.href)) menu.push(item.label || item.href || "");
|
|
31
|
+
for (const kind of item.children ?? []) if (wijstNaar(kind.href)) menu.push(kind.label || kind.href || "");
|
|
32
|
+
}
|
|
33
|
+
const footer = [];
|
|
34
|
+
for (const kolom of bron.footer?.columns ?? []) {
|
|
35
|
+
for (const link of kolom.links ?? []) if (wijstNaar(link.href)) footer.push(link.label || link.href || "");
|
|
36
|
+
}
|
|
37
|
+
const paginas = [];
|
|
38
|
+
for (const p of bron.paginas ?? []) {
|
|
39
|
+
if (p.trashed || eigenId && p.id === eigenId || p.path === doel) continue;
|
|
40
|
+
if (hrefsIn(p.blocks ?? []).some(wijstNaar)) paginas.push(p.title || p.path || "");
|
|
41
|
+
}
|
|
42
|
+
return { menu, footer, paginas };
|
|
43
|
+
}
|
|
44
|
+
function verwijzingenZin(v) {
|
|
45
|
+
const delen = [];
|
|
46
|
+
if (v.menu.length) delen.push(`in het menu (${v.menu.map((l) => `\u201C${l}\u201D`).join(", ")})`);
|
|
47
|
+
if (v.footer.length) delen.push("in de footer");
|
|
48
|
+
if (v.paginas.length === 1) delen.push(`op de pagina \u201C${v.paginas[0]}\u201D`);
|
|
49
|
+
else if (v.paginas.length > 1) delen.push(`op ${v.paginas.length} pagina's (${v.paginas.slice(0, 3).map((t) => `\u201C${t}\u201D`).join(", ")}${v.paginas.length > 3 ? " \u2026" : ""})`);
|
|
50
|
+
if (!delen.length) return null;
|
|
51
|
+
const lijst = delen.length === 1 ? delen[0] : `${delen.slice(0, -1).join(", ")} en ${delen[delen.length - 1]}`;
|
|
52
|
+
return `Er wordt naar deze pagina gelinkt ${lijst}. Die links geven daarna een foutpagina \u2014 pas ze aan of verwijder ze.`;
|
|
53
|
+
}
|
|
54
|
+
async function laadPaginaGebruik(siteId, pad, eigenId) {
|
|
55
|
+
if (isDemoActive()) return null;
|
|
56
|
+
const db = getDb();
|
|
57
|
+
if (!db) return null;
|
|
58
|
+
try {
|
|
59
|
+
const [instellingen, drafts] = await Promise.all([
|
|
60
|
+
getDoc(doc(db, "sites", siteId, "settings", "site")),
|
|
61
|
+
getDocs(collection(db, "sites", siteId, "drafts"))
|
|
62
|
+
]);
|
|
63
|
+
const s = instellingen.data() ?? {};
|
|
64
|
+
return vindVerwijzingen(
|
|
65
|
+
pad,
|
|
66
|
+
{ nav: s.nav, footer: s.footer, paginas: drafts.docs.map((d) => ({ id: d.id, ...d.data() })) },
|
|
67
|
+
eigenId
|
|
68
|
+
);
|
|
69
|
+
} catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function bevatBlok(v, test, diepte = 0) {
|
|
74
|
+
if (diepte > 14 || v == null || typeof v !== "object") return false;
|
|
75
|
+
if (Array.isArray(v)) return v.some((x) => bevatBlok(x, test, diepte + 1));
|
|
76
|
+
const o = v;
|
|
77
|
+
if (typeof o._type === "string" && test(o)) return true;
|
|
78
|
+
return Object.values(o).some((x) => typeof x === "object" && bevatBlok(x, test, diepte + 1));
|
|
79
|
+
}
|
|
80
|
+
async function laadFormulierGebruik(siteId, formId) {
|
|
81
|
+
if (isDemoActive()) return null;
|
|
82
|
+
const db = getDb();
|
|
83
|
+
if (!db) return null;
|
|
84
|
+
try {
|
|
85
|
+
const [drafts, pages] = await Promise.all([
|
|
86
|
+
getDocs(collection(db, "sites", siteId, "drafts")),
|
|
87
|
+
getDocs(collection(db, "sites", siteId, "pages"))
|
|
88
|
+
]);
|
|
89
|
+
const treffers = /* @__PURE__ */ new Set();
|
|
90
|
+
for (const d of [...drafts.docs, ...pages.docs]) {
|
|
91
|
+
const data = d.data();
|
|
92
|
+
if (data.trashed) continue;
|
|
93
|
+
if (bevatBlok(data.blocks ?? [], (b) => b._type === "form" && b.formId === formId)) treffers.add(data.title || data.path || d.id);
|
|
94
|
+
}
|
|
95
|
+
return [...treffers];
|
|
96
|
+
} catch {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
export {
|
|
101
|
+
bevatBlok,
|
|
102
|
+
hrefsIn,
|
|
103
|
+
laadFormulierGebruik,
|
|
104
|
+
laadPaginaGebruik,
|
|
105
|
+
padVanHref,
|
|
106
|
+
verwijzingenZin,
|
|
107
|
+
vindVerwijzingen
|
|
108
|
+
};
|
|
109
|
+
//# sourceMappingURL=paginaGebruik.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/lib/paginaGebruik.ts"],"sourcesContent":["\"use client\";\n\nimport { collection, doc, getDoc, getDocs } from \"firebase/firestore\";\nimport { getDb } from \"../../firebase/client\";\nimport { isDemoActive } from \"../../demo/demoStore\";\n\n/**\n * Waar wordt een pagina gebruikt? (8.10)\n *\n * Een pagina weggooien of offline halen brak tot nu toe stil het menu, de footer en elke knop die\n * ernaar wees: allemaal een 404, en de klant merkte het pas als een bezoeker het meldde. Deze\n * zoektocht levert de plekken op, zodat de bevestiging ze kan noemen (\"staat in het menu en op\n * 2 pagina's\").\n *\n * Het zoeken zelf is puur (`vindVerwijzingen`) en daarmee testbaar; `laadPaginaGebruik` haalt de\n * gegevens op. Sites zijn klein, dus één read van menu, footer en alle werkversies volstaat.\n */\n\ninterface LinkItem {\n href?: string;\n label?: string;\n children?: LinkItem[];\n}\n\nexport interface Verwijzingen {\n /** Menu-items (labels) die naar de pagina wijzen. */\n menu: string[];\n /** Footer-links (labels) die naar de pagina wijzen. */\n footer: string[];\n /** Andere pagina's (titel of pad) met een link naar de pagina. */\n paginas: string[];\n}\n\n/** Het eigen pad uit een href (\"/over-ons#team?x\" → \"/over-ons\"); extern of leeg → null. */\nexport function padVanHref(href: string): string | null {\n const h = href.trim();\n if (!h.startsWith(\"/\") || h.startsWith(\"//\")) return null;\n return h.split(/[?#]/)[0].replace(/\\/+$/, \"\") || \"/\";\n}\n\n/** Alle hrefs in een stuk blokdata: velden die op `href` eindigen én links in rich text. */\nexport function hrefsIn(v: unknown, uit: string[] = [], diepte = 0): string[] {\n if (diepte > 14 || v == null) return uit;\n if (Array.isArray(v)) {\n for (const x of v) hrefsIn(x, uit, diepte + 1);\n return uit;\n }\n if (typeof v !== \"object\") return uit;\n for (const [k, x] of Object.entries(v as Record<string, unknown>)) {\n if (typeof x === \"string\") {\n if (/href$/i.test(k)) uit.push(x);\n else if (x.includes(\"href=\")) for (const m of x.matchAll(/href\\s*=\\s*[\"']([^\"']+)[\"']/gi)) uit.push(m[1]);\n } else if (typeof x === \"object\") hrefsIn(x, uit, diepte + 1);\n }\n return uit;\n}\n\nexport function vindVerwijzingen(\n pad: string,\n bron: {\n nav?: LinkItem[];\n footer?: { columns?: { links?: LinkItem[] }[] };\n paginas?: { id?: string; title?: string; path?: string; blocks?: unknown[]; trashed?: boolean }[];\n },\n eigenId?: string,\n): Verwijzingen {\n const doel = padVanHref(pad) ?? pad;\n const wijstNaar = (href?: string) => typeof href === \"string\" && padVanHref(href) === doel;\n const menu: string[] = [];\n for (const item of bron.nav ?? []) {\n if (wijstNaar(item.href)) menu.push(item.label || item.href || \"\");\n for (const kind of item.children ?? []) if (wijstNaar(kind.href)) menu.push(kind.label || kind.href || \"\");\n }\n const footer: string[] = [];\n for (const kolom of bron.footer?.columns ?? []) {\n for (const link of kolom.links ?? []) if (wijstNaar(link.href)) footer.push(link.label || link.href || \"\");\n }\n const paginas: string[] = [];\n for (const p of bron.paginas ?? []) {\n if (p.trashed || (eigenId && p.id === eigenId) || p.path === doel) continue;\n if (hrefsIn(p.blocks ?? []).some(wijstNaar)) paginas.push(p.title || p.path || \"\");\n }\n return { menu, footer, paginas };\n}\n\n/** Eén zin voor in een bevestiging, of null als de pagina nergens gebruikt wordt. */\nexport function verwijzingenZin(v: Verwijzingen): string | null {\n const delen: string[] = [];\n if (v.menu.length) delen.push(`in het menu (${v.menu.map((l) => `“${l}”`).join(\", \")})`);\n if (v.footer.length) delen.push(\"in de footer\");\n if (v.paginas.length === 1) delen.push(`op de pagina “${v.paginas[0]}”`);\n else if (v.paginas.length > 1) delen.push(`op ${v.paginas.length} pagina's (${v.paginas.slice(0, 3).map((t) => `“${t}”`).join(\", \")}${v.paginas.length > 3 ? \" …\" : \"\"})`);\n if (!delen.length) return null;\n const lijst = delen.length === 1 ? delen[0] : `${delen.slice(0, -1).join(\", \")} en ${delen[delen.length - 1]}`;\n return `Er wordt naar deze pagina gelinkt ${lijst}. Die links geven daarna een foutpagina — pas ze aan of verwijder ze.`;\n}\n\n/** Leest menu, footer en alle werkversies. Leesfout → null (dan geen extra zin, geen valse belofte). */\nexport async function laadPaginaGebruik(siteId: string, pad: string, eigenId?: string): Promise<Verwijzingen | null> {\n if (isDemoActive()) return null;\n const db = getDb();\n if (!db) return null;\n try {\n const [instellingen, drafts] = await Promise.all([\n getDoc(doc(db, \"sites\", siteId, \"settings\", \"site\")),\n getDocs(collection(db, \"sites\", siteId, \"drafts\")),\n ]);\n const s = (instellingen.data() ?? {}) as { nav?: LinkItem[]; footer?: { columns?: { links?: LinkItem[] }[] } };\n return vindVerwijzingen(\n pad,\n { nav: s.nav, footer: s.footer, paginas: drafts.docs.map((d) => ({ id: d.id, ...(d.data() as Record<string, unknown>) })) },\n eigenId,\n );\n } catch {\n return null;\n }\n}\n\n/** Bevat deze blokdata (ook genest: kolommen, tabs) een blok dat aan `test` voldoet? */\nexport function bevatBlok(v: unknown, test: (b: Record<string, unknown>) => boolean, diepte = 0): boolean {\n if (diepte > 14 || v == null || typeof v !== \"object\") return false;\n if (Array.isArray(v)) return v.some((x) => bevatBlok(x, test, diepte + 1));\n const o = v as Record<string, unknown>;\n if (typeof o._type === \"string\" && test(o)) return true;\n return Object.values(o).some((x) => typeof x === \"object\" && bevatBlok(x, test, diepte + 1));\n}\n\n/**\n * Pagina's (titels/paden) met een formulierblok voor dit formulier — live én werkversie. Een\n * verwijderd formulier liet het contactblok \"niet beschikbaar\" tonen, en de klant merkte het pas\n * aan de leads die uitbleven. Leesfout → null.\n */\nexport async function laadFormulierGebruik(siteId: string, formId: string): Promise<string[] | null> {\n if (isDemoActive()) return null;\n const db = getDb();\n if (!db) return null;\n try {\n const [drafts, pages] = await Promise.all([\n getDocs(collection(db, \"sites\", siteId, \"drafts\")),\n getDocs(collection(db, \"sites\", siteId, \"pages\")),\n ]);\n const treffers = new Set<string>();\n for (const d of [...drafts.docs, ...pages.docs]) {\n const data = d.data() as { title?: string; path?: string; blocks?: unknown[]; trashed?: boolean };\n if (data.trashed) continue;\n if (bevatBlok(data.blocks ?? [], (b) => b._type === \"form\" && b.formId === formId)) treffers.add(data.title || data.path || d.id);\n }\n return [...treffers];\n } catch {\n return null;\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Controle vóór publiceren (8.4) — waarschuwingen, nooit een blokkade. Puur (geen Firestore), zodat
|
|
3
|
+
* hij testbaar is en in de editor én de pagina-instellingen hetzelfde zegt.
|
|
4
|
+
*
|
|
5
|
+
* Gezocht wordt generiek door de blokdata (elk bloktype, ook maatwerk), op vormen die vrijwel zeker
|
|
6
|
+
* iets betekenen: een object met een beeld-URL en een leeg `alt`, een `href` naar een eigen pad dat
|
|
7
|
+
* niet bestaat, een knop met tekst maar zonder link.
|
|
8
|
+
*/
|
|
9
|
+
export interface Waarschuwing {
|
|
10
|
+
soort: "alt" | "link" | "linkConcept" | "linkLeeg" | "anker" | "kop" | "leeg" | "voorbeeld" | "seo" | "noindex" | "leesbaarheid";
|
|
11
|
+
tekst: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function controleerPagina(page: {
|
|
14
|
+
blocks?: unknown[];
|
|
15
|
+
seo?: {
|
|
16
|
+
description?: string;
|
|
17
|
+
noindex?: boolean;
|
|
18
|
+
};
|
|
19
|
+
}, ctx?: {
|
|
20
|
+
bekendePaden?: string[];
|
|
21
|
+
livePaden?: string[];
|
|
22
|
+
redirects?: {
|
|
23
|
+
from: string;
|
|
24
|
+
}[];
|
|
25
|
+
}): Waarschuwing[];
|