@xiwebdesign/xicms-core 7.30.0 → 8.15.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 +22 -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 -56
- 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 +247 -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 +144 -0
- package/dist/admin/settings/BedrijfInstellingen.js.map +1 -0
- package/dist/admin/settings/MailSettings.js +2 -2
- package/dist/admin/settings/MailSettings.js.map +1 -1
- 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 +27 -1
- 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/bedrijf/bedrijf.d.ts +36 -0
- package/dist/bedrijf/bedrijf.js +110 -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 +29 -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 +1 -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/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/site/SiteFooter.d.ts +6 -1
- package/dist/components/site/SiteFooter.js +61 -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/index.d.ts +4 -1
- package/dist/index.js +16 -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 +16 -3
- package/dist/modules/builtins.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 +140 -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 +470 -0
- package/dist/schema/blocks.js +102 -4
- package/dist/schema/blocks.js.map +1 -1
- package/dist/schema/business.d.ts +117 -0
- package/dist/schema/business.js +73 -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 +101 -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 +68 -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 +15 -2
- 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 +1149 -208
- 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 +6 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/media/MediaManager.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport {\n collection,\n deleteDoc,\n deleteField,\n doc,\n limit,\n onSnapshot,\n orderBy,\n query,\n updateDoc,\n} from \"firebase/firestore\";\nimport { deleteObject, ref as storageRef } from \"firebase/storage\";\nimport { getBucket, getDb } from \"../../firebase/client\";\nimport { DEMO_EVENT, isDemoActive, listDemoMediaAll } from \"../../demo/demoStore\";\nimport type { MediaItem } from \"../../content/types\";\nimport { Button, Card, Badge, Input } from \"../ui/primitives\";\nimport { useToast } from \"../ui/toast\";\nimport { useConfirm } from \"../ui/ConfirmDialog\";\nimport { uploadImage } from \"../lib/upload\";\nimport { findMediaUsage, findUnusedMedia } from \"../lib/mediaUsage\";\n\n/** Paginagrootte van de bibliotheek: begrensd laden i.p.v. álles (kosten + geheugen bij 100+ foto's). */\nconst PAGE_SIZE = 48;\n\nexport function MediaManager({ siteId }: { siteId: string }) {\n const { toast } = useToast();\n const confirm = useConfirm();\n const [items, setItems] = useState<MediaItem[]>([]);\n const [busy, setBusy] = useState(false);\n /** Ids van aantoonbaar ongebruikte afbeeldingen (na een expliciete scan); null = nog niet gescand. */\n const [ongebruikt, setOngebruikt] = useState<string[] | null>(null);\n const [scant, setScant] = useState(false);\n const [pageCount, setPageCount] = useState(1);\n const [hasMore, setHasMore] = useState(false);\n const inputRef = useRef<HTMLInputElement>(null);\n /** 7.24: zoeken op naam/alt — bij 100+ foto's is scrollen geen zoeken meer. */\n const [zoek, setZoek] = useState(\"\");\n const [sleepOver, setSleepOver] = useState(false);\n\n useEffect(() => {\n // Demo-modus: lokale bibliotheek (geseede foto's + eigen \"uploads\") i.p.v. Firestore.\n if (isDemoActive()) {\n const sync = () => setItems(listDemoMediaAll(siteId));\n sync();\n window.addEventListener(DEMO_EVENT, sync);\n return () => window.removeEventListener(DEMO_EVENT, sync);\n }\n const db = getDb();\n if (!db) return;\n // limit + \"meer laden\" (pageCount groeit): geen volledige collectie-read per open.\n const max = pageCount * PAGE_SIZE;\n const q = query(collection(db, \"sites\", siteId, \"media\"), orderBy(\"createdAt\", \"desc\"), limit(max + 1));\n return onSnapshot(\n q,\n (snap) => {\n setHasMore(snap.docs.length > max);\n setItems(snap.docs.slice(0, max).map((d) => ({ id: d.id, ...(d.data() as Omit<MediaItem, \"id\">) })));\n },\n // Leesfouten (rules/App Check) niet stil slikken — anders lijkt de bibliotheek leeg.\n (err) => toast({ title: \"Kon mediabibliotheek niet laden\", description: err.message, tone: \"error\" }),\n );\n }, [siteId, toast, pageCount]);\n\n async function onFiles(files: FileList) {\n setBusy(true);\n let ok = 0;\n const errors: string[] = [];\n for (const file of Array.from(files)) {\n try {\n await uploadImage(siteId, file);\n ok++;\n } catch (e) {\n errors.push(`${file.name}: ${(e as Error).message}`);\n }\n }\n if (ok > 0) toast({ title: `${ok} foto('s) geüpload`, description: \"Automatisch verkleind; varianten worden op de achtergrond gemaakt.\", tone: \"success\" });\n if (errors.length) toast({ title: \"Sommige uploads mislukten\", description: errors.join(\" · \"), tone: \"error\" });\n setBusy(false);\n }\n\n /**\n * Focuspunt (7.24): klik op de plek in de foto die in beeld moet blijven. Bewaard als fracties;\n * de renderers vertalen dat naar `object-position` (Img/ResponsiveImage). Nog een klik = anders,\n * \"midden\" = weg.\n */\n async function zetFocus(item: MediaItem, focal: { x: number; y: number } | null) {\n const db = getDb();\n if (!db) return;\n try {\n await updateDoc(doc(db, \"sites\", siteId, \"media\", item.id), focal ? { focal } : { focal: deleteField() });\n toast({ title: focal ? \"Focuspunt opgeslagen\" : \"Focuspunt verwijderd\", description: focal ? \"Bij bijsnijden blijft dit deel in beeld.\" : undefined, tone: \"success\" });\n } catch (e) {\n toast({ title: \"Focuspunt opslaan mislukt\", description: (e as Error).message, tone: \"error\" });\n }\n }\n\n async function saveAlt(item: MediaItem, alt: string) {\n const db = getDb();\n if (!db) return;\n if ((item.alt ?? \"\") === alt) return; // niets gewijzigd → geen write/toast\n try {\n await updateDoc(doc(db, \"sites\", siteId, \"media\", item.id), { alt });\n toast({ title: \"Alt-tekst opgeslagen\", tone: \"success\" });\n } catch (e) {\n // Niet stil slikken: de klant dacht anders dat de alt-tekst opgeslagen was.\n toast({ title: \"Alt-tekst opslaan mislukt\", description: (e as Error).message, tone: \"error\" });\n }\n }\n\n async function remove(item: MediaItem) {\n const db = getDb();\n const bucket = getBucket();\n if (!db) return;\n const usedOn = await findMediaUsage(siteId, item);\n const ok = await confirm({\n title: \"Afbeelding verwijderen?\",\n description:\n usedOn.length > 0\n ? `Let op: deze afbeelding wordt gebruikt op ${usedOn.length} gepubliceerde pagina('s) (${usedOn.join(\", \")}). Daar verschijnt dan een gebroken afbeelding.`\n : \"De afbeelding wordt definitief verwijderd.\",\n confirmLabel: \"Verwijderen\",\n tone: \"danger\",\n });\n if (!ok) return;\n try {\n await verwijderItem(item);\n toast({ title: \"Verwijderd\" });\n } catch (e) {\n // Rules/offline-fout niet stil slikken — anders lijkt verwijderen \"niets te doen\".\n toast({ title: \"Verwijderen mislukt\", description: (e as Error).message, tone: \"error\" });\n }\n }\n\n /** Doc + origineel + alle varianten weg — gedeeld door los verwijderen en de opruimer. */\n async function verwijderItem(item: MediaItem) {\n const db = getDb();\n const bucket = getBucket();\n if (!db) throw new Error(\"Geen databaseverbinding.\");\n await deleteDoc(doc(db, \"sites\", siteId, \"media\", item.id));\n if (bucket) {\n // Ook de srcset-varianten opruimen die de Cloud Function heeft gemaakt — anders blijven\n // die (publieke) bestanden voor altijd in Storage staan.\n const paths = [\n item.path,\n item.variants?.webp?.path,\n ...(item.variants?.sizes ?? []).map((v) => v.path),\n ].filter((p): p is string => Boolean(p));\n await Promise.all(\n paths.map((p) => deleteObject(storageRef(bucket, p)).catch(() => undefined /* al weg */)),\n );\n }\n }\n\n /**\n * Opruimer (7.14): Storage groeide anders onbeperkt — elke ooit geüploade foto (plus zijn\n * webp/srcset-varianten) bleef staan, ook als hij allang uit elk blok was gehaald. De scan is\n * expliciet (één knop, één gecombineerde content-read via findUnusedMedia) en het verwijderen\n * vraagt altijd eerst bevestiging met aantal en omvang.\n */\n async function scanOngebruikt() {\n setScant(true);\n try {\n const ids = await findUnusedMedia(siteId, items);\n setOngebruikt(ids);\n if (ids.length === 0) toast({ title: \"Niets op te ruimen\", description: \"Elke afbeelding in de bibliotheek wordt ergens gebruikt.\", tone: \"success\" });\n } catch (e) {\n toast({ title: \"Scan mislukt\", description: (e as Error).message, tone: \"error\" });\n } finally {\n setScant(false);\n }\n }\n\n async function ruimOngebruiktOp() {\n const doelen = items.filter((it) => ongebruikt?.includes(it.id));\n if (doelen.length === 0) return;\n const mb = (doelen.reduce((som, it) => som + (it.size ?? 0), 0) / (1024 * 1024)).toFixed(1);\n const ok = await confirm({\n title: `${doelen.length} ongebruikte afbeelding(en) verwijderen?`,\n description: `Samen ±${mb} MB, inclusief alle automatisch gemaakte varianten. Dit kan niet ongedaan worden gemaakt.`,\n confirmLabel: \"Opruimen\",\n tone: \"danger\",\n });\n if (!ok) return;\n setBusy(true);\n let gelukt = 0;\n const fouten: string[] = [];\n for (const it of doelen) {\n try { await verwijderItem(it); gelukt++; } catch (e) { fouten.push(`${it.name}: ${(e as Error).message}`); }\n }\n setOngebruikt(null);\n setBusy(false);\n if (gelukt) toast({ title: `${gelukt} afbeelding(en) opgeruimd`, description: `±${mb} MB opslag vrijgemaakt.`, tone: \"success\" });\n if (fouten.length) toast({ title: \"Niet alles kon weg\", description: fouten.join(\" · \"), tone: \"error\" });\n }\n\n const term = zoek.trim().toLowerCase();\n const getoond = term ? items.filter((m) => `${m.name ?? \"\"} ${m.alt ?? \"\"}`.toLowerCase().includes(term)) : items;\n\n return (\n <div\n className={`grid gap-4 rounded-xl transition ${sleepOver ? \"outline-2 outline-dashed outline-brand-500 outline-offset-4\" : \"\"}`}\n // Slepen vanaf het bureaublad (7.24): de hele bibliotheek is een dropzone. Alleen bestanden\n // (geen tekst-drags), en de browser mag het bestand niet zelf openen.\n onDragOver={(e) => {\n if (e.dataTransfer.types.includes(\"Files\")) {\n e.preventDefault();\n setSleepOver(true);\n }\n }}\n onDragLeave={() => setSleepOver(false)}\n onDrop={(e) => {\n if (!e.dataTransfer.files?.length) return;\n e.preventDefault();\n setSleepOver(false);\n void onFiles(e.dataTransfer.files);\n }}\n >\n <div>\n <Button disabled={busy} onClick={() => inputRef.current?.click()}>\n {busy ? \"Uploaden…\" : \"Foto's uploaden\"}\n </Button>{\" \"}\n {!isDemoActive() && items.length > 0 && (\n ongebruikt === null ? (\n <Button variant=\"ghost\" disabled={scant || busy} onClick={() => void scanOngebruikt()}>\n {scant ? \"Zoeken…\" : \"🧹 Zoek ongebruikte foto's\"}\n </Button>\n ) : ongebruikt.length > 0 ? (\n <>\n <Button variant=\"secondary\" disabled={busy} onClick={() => void ruimOngebruiktOp()}>\n 🧹 {ongebruikt.length} ongebruikte verwijderen\n </Button>{\" \"}\n <Button variant=\"ghost\" onClick={() => setOngebruikt(null)}>Annuleren</Button>\n </>\n ) : null\n )}\n <p className=\"mt-1 text-xs text-slate-500\">\n Je kunt meerdere foto's tegelijk kiezen of ze hierheen slepen. Ze worden automatisch verkleind en\n geoptimaliseerd (webp) — dat houdt je site snel en bespaart opslag. Max. 15 MB per bestand.\n Klik op een foto om het <strong>focuspunt</strong> te kiezen: dat deel blijft in beeld als de foto\n wordt bijgesneden (bijvoorbeeld een brede kopfoto op een telefoon).\n </p>\n {items.length > 8 && (\n <div className=\"mt-3 max-w-xs\">\n <Input value={zoek} onChange={(e) => setZoek(e.target.value)} placeholder=\"Zoek op naam of alt-tekst…\" aria-label=\"Zoek in mediabibliotheek\" />\n </div>\n )}\n <input\n ref={inputRef}\n type=\"file\"\n accept=\"image/*\"\n multiple\n hidden\n onChange={(e) => {\n if (e.target.files?.length) void onFiles(e.target.files);\n e.target.value = \"\";\n }}\n />\n </div>\n <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-4\">\n {getoond.map((m) => (\n <Card key={m.id} className=\"p-3\">\n <FocusFoto item={m} onFocus={(f) => void zetFocus(m, f)} />\n <div className=\"mb-2 flex items-center justify-between gap-1\">\n <span className=\"truncate text-xs text-slate-500\">\n {m.name}\n {(m as { width?: number; height?: number }).width && (\n <span className=\"text-slate-400\"> · {(m as { width?: number }).width}×{(m as { height?: number }).height}</span>\n )}\n </span>\n {m.status === \"processing\" ? (\n <Badge tone=\"amber\">verwerken</Badge>\n ) : ongebruikt?.includes(m.id) ? (\n <Badge tone=\"amber\">ongebruikt</Badge>\n ) : (\n <Badge tone=\"green\">klaar</Badge>\n )}\n </div>\n <AltInput item={m} onSave={saveAlt} />\n <div className=\"flex gap-1\">\n <Button variant=\"ghost\" size=\"sm\" className=\"text-xs\" onClick={() => void navigator.clipboard?.writeText(m.url)}>\n Kopieer URL\n </Button>\n <Button variant=\"ghost\" size=\"sm\" className=\"text-xs\" onClick={() => void remove(m)}>\n Verwijder\n </Button>\n </div>\n </Card>\n ))}\n {items.length === 0 && <p className=\"text-sm text-slate-500\">Nog geen media.</p>}\n {items.length > 0 && getoond.length === 0 && <p className=\"text-sm text-slate-500\">Geen foto's gevonden voor “{zoek}”.</p>}\n </div>\n {hasMore && (\n <div>\n <Button variant=\"secondary\" size=\"sm\" onClick={() => setPageCount((c) => c + 1)}>\n Meer laden…\n </Button>\n </div>\n )}\n </div>\n );\n}\n\n/** Controlled alt-veld: volgt externe updates (realtime snapshot) en slaat alleen échte wijzigingen op. */\nfunction AltInput({ item, onSave }: { item: MediaItem; onSave: (item: MediaItem, alt: string) => Promise<void> }) {\n const [value, setValue] = useState(item.alt ?? \"\");\n // Extern gewijzigd (andere tab/verwerking) → veld bijtrekken, behalve terwijl de klant zelf typt.\n const [focused, setFocused] = useState(false);\n useEffect(() => {\n if (!focused) setValue(item.alt ?? \"\");\n }, [item.alt, focused]);\n return (\n <Input\n value={value}\n placeholder=\"Alt-tekst\"\n className=\"mb-2 h-8 text-xs\"\n onChange={(e) => setValue(e.target.value)}\n onFocus={() => setFocused(true)}\n onBlur={() => {\n setFocused(false);\n void onSave(item, value);\n }}\n />\n );\n}\n\n/**\n * Thumbnail met focuspunt-kiezer: klik = zet het punt op die plek, klik op het punt zelf = weg.\n * Het beeld toont `object-position` op het gekozen punt, zodat je meteen ziet wat het doet.\n */\nfunction FocusFoto({ item, onFocus }: { item: MediaItem; onFocus: (f: { x: number; y: number } | null) => void }) {\n const f = item.focal;\n return (\n <button\n type=\"button\"\n className=\"relative mb-2 block h-28 w-full cursor-crosshair overflow-hidden rounded-md focus-visible:ring-2 focus-visible:ring-brand-500 focus-visible:outline-none\"\n title={f ? \"Focuspunt: klik om te verplaatsen, klik op het punt om te verwijderen\" : \"Klik om het focuspunt te kiezen\"}\n aria-label={f ? `Focuspunt van ${item.name} aanpassen` : `Focuspunt van ${item.name} kiezen`}\n onClick={(e) => {\n const r = e.currentTarget.getBoundingClientRect();\n const x = Math.min(1, Math.max(0, (e.clientX - r.left) / r.width));\n const y = Math.min(1, Math.max(0, (e.clientY - r.top) / r.height));\n // Klik (vrijwel) op het bestaande punt = verwijderen.\n if (f && Math.abs(f.x - x) < 0.08 && Math.abs(f.y - y) < 0.12) onFocus(null);\n else onFocus({ x: Math.round(x * 100) / 100, y: Math.round(y * 100) / 100 });\n }}\n >\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img\n src={item.url}\n alt={item.alt ?? item.name}\n className=\"h-full w-full object-cover\"\n style={f ? { objectPosition: `${Math.round(f.x * 100)}% ${Math.round(f.y * 100)}%` } : undefined}\n />\n {f && (\n <span\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute h-4 w-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white bg-brand-500 shadow\"\n style={{ left: `${f.x * 100}%`, top: `${f.y * 100}%` }}\n />\n )}\n </button>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/media/MediaManager.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport {\n collection,\n deleteDoc,\n deleteField,\n doc,\n limit,\n onSnapshot,\n orderBy,\n query,\n updateDoc,\n} from \"firebase/firestore\";\nimport { deleteObject, ref as storageRef } from \"firebase/storage\";\nimport { getBucket, getDb } from \"../../firebase/client\";\nimport { DEMO_EVENT, isDemoActive, listDemoMediaAll, removeDemoMedia } from \"../../demo/demoStore\";\nimport type { MediaItem } from \"../../content/types\";\nimport { Button, Card, Badge, Input, LegeStaat } from \"../ui/primitives\";\nimport { useToast } from \"../ui/toast\";\nimport { Switch } from \"../ui/switch\";\nimport { useConfirm } from \"../ui/ConfirmDialog\";\nimport { uploadImage } from \"../lib/upload\";\nimport { findMediaUsage, findUnusedMedia } from \"../lib/mediaUsage\";\nimport { korteDatum, leesbareGrootte, MEDIA_SORTERINGEN, sorteerMedia, type MediaSortering } from \"../lib/mediaLijst\";\nimport { Select } from \"../ui/select\";\n\n/** Paginagrootte van de bibliotheek: begrensd laden i.p.v. álles (kosten + geheugen bij 100+ foto's). */\nconst PAGE_SIZE = 48;\n\nexport function MediaManager({ siteId }: { siteId: string }) {\n const { toast } = useToast();\n const confirm = useConfirm();\n const [items, setItems] = useState<MediaItem[]>([]);\n const [busy, setBusy] = useState(false);\n /** Ids van aantoonbaar ongebruikte afbeeldingen (na een expliciete scan); null = nog niet gescand. */\n const [ongebruikt, setOngebruikt] = useState<string[] | null>(null);\n const [scant, setScant] = useState(false);\n const [pageCount, setPageCount] = useState(1);\n const [hasMore, setHasMore] = useState(false);\n const inputRef = useRef<HTMLInputElement>(null);\n /** 7.24: zoeken op naam/alt — bij 100+ foto's is scrollen geen zoeken meer. */\n const [zoek, setZoek] = useState(\"\");\n /** 8.4: alleen foto's zonder beschrijving tonen — om ze snel aan te vullen. */\n const [zonderAlt, setZonderAlt] = useState(false);\n const [sleepOver, setSleepOver] = useState(false);\n /** 8.5: sorteren en meerdere foto's tegelijk selecteren (verwijderen/downloaden). */\n const [sortering, setSortering] = useState<MediaSortering>(\"nieuw\");\n const [geselecteerd, setGeselecteerd] = useState<Set<string>>(() => new Set());\n\n useEffect(() => {\n // Demo-modus: lokale bibliotheek (geseede foto's + eigen \"uploads\") i.p.v. Firestore.\n if (isDemoActive()) {\n const sync = () => setItems(listDemoMediaAll(siteId));\n sync();\n window.addEventListener(DEMO_EVENT, sync);\n return () => window.removeEventListener(DEMO_EVENT, sync);\n }\n const db = getDb();\n if (!db) return;\n // limit + \"meer laden\" (pageCount groeit): geen volledige collectie-read per open.\n const max = pageCount * PAGE_SIZE;\n const q = query(collection(db, \"sites\", siteId, \"media\"), orderBy(\"createdAt\", \"desc\"), limit(max + 1));\n return onSnapshot(\n q,\n (snap) => {\n setHasMore(snap.docs.length > max);\n setItems(snap.docs.slice(0, max).map((d) => ({ id: d.id, ...(d.data() as Omit<MediaItem, \"id\">) })));\n },\n // Leesfouten (rules/App Check) niet stil slikken — anders lijkt de bibliotheek leeg.\n (err) => toast({ title: \"Kon mediabibliotheek niet laden\", description: err.message, tone: \"error\" }),\n );\n }, [siteId, toast, pageCount]);\n\n async function onFiles(files: FileList) {\n setBusy(true);\n let ok = 0;\n const errors: string[] = [];\n for (const file of Array.from(files)) {\n try {\n await uploadImage(siteId, file);\n ok++;\n } catch (e) {\n errors.push(`${file.name}: ${(e as Error).message}`);\n }\n }\n if (ok > 0) toast({ title: `${ok} foto('s) geüpload`, description: \"Automatisch verkleind; varianten worden op de achtergrond gemaakt.\", tone: \"success\" });\n if (errors.length) toast({ title: \"Sommige uploads mislukten\", description: errors.join(\" · \"), tone: \"error\" });\n setBusy(false);\n }\n\n /**\n * Focuspunt (7.24): klik op de plek in de foto die in beeld moet blijven. Bewaard als fracties;\n * de renderers vertalen dat naar `object-position` (Img/ResponsiveImage). Nog een klik = anders,\n * \"midden\" = weg.\n */\n async function zetFocus(item: MediaItem, focal: { x: number; y: number } | null) {\n const db = getDb();\n if (!db) return;\n try {\n await updateDoc(doc(db, \"sites\", siteId, \"media\", item.id), focal ? { focal } : { focal: deleteField() });\n toast({ title: focal ? \"Focuspunt opgeslagen\" : \"Focuspunt verwijderd\", description: focal ? \"Bij bijsnijden blijft dit deel in beeld.\" : undefined, tone: \"success\" });\n } catch (e) {\n toast({ title: \"Focuspunt opslaan mislukt\", description: (e as Error).message, tone: \"error\" });\n }\n }\n\n async function saveAlt(item: MediaItem, alt: string) {\n const db = getDb();\n if (!db) return;\n if ((item.alt ?? \"\") === alt) return; // niets gewijzigd → geen write/toast\n try {\n await updateDoc(doc(db, \"sites\", siteId, \"media\", item.id), { alt });\n toast({ title: \"Alt-tekst opgeslagen\", tone: \"success\" });\n } catch (e) {\n // Niet stil slikken: de klant dacht anders dat de alt-tekst opgeslagen was.\n toast({ title: \"Alt-tekst opslaan mislukt\", description: (e as Error).message, tone: \"error\" });\n }\n }\n\n async function remove(item: MediaItem) {\n const demo = isDemoActive();\n if (!demo && !getDb()) return;\n const usedOn = demo ? [] : await findMediaUsage(siteId, item);\n const ok = await confirm({\n title: \"Afbeelding verwijderen?\",\n description:\n usedOn === null\n ? \"Er kon niet worden nagegaan of deze afbeelding nog ergens gebruikt wordt. Wordt hij dat wel, dan verschijnt daar een gebroken afbeelding.\"\n : usedOn.length > 0\n ? `Let op: deze afbeelding wordt nog gebruikt (${usedOn.slice(0, 5).join(\", \")}${usedOn.length > 5 ? \" …\" : \"\"}). Daar verschijnt dan een gebroken afbeelding.`\n : \"De afbeelding wordt nergens gebruikt en wordt definitief verwijderd.\",\n confirmLabel: usedOn && usedOn.length > 0 ? \"Toch verwijderen\" : \"Verwijderen\",\n tone: \"danger\",\n });\n if (!ok) return;\n try {\n await verwijderItem(item);\n toast({ title: \"Verwijderd\" });\n } catch (e) {\n // Rules/offline-fout niet stil slikken — anders lijkt verwijderen \"niets te doen\".\n toast({ title: \"Verwijderen mislukt\", description: (e as Error).message, tone: \"error\" });\n }\n }\n\n /**\n * Meerdere tegelijk verwijderen (8.5). Eén gecombineerde scan (dezelfde als de opruimer) zegt\n * hoeveel van de selectie nog ergens gebruikt worden — dat staat in de bevestiging.\n */\n async function verwijderSelectie() {\n const doelen = items.filter((it) => geselecteerd.has(it.id));\n if (doelen.length === 0) return;\n setBusy(true);\n // Demo: geen inhoud om te scannen; null zou daar ten onrechte \"kon niet nagaan\" melden.\n let inGebruik: number | null = isDemoActive() ? 0 : null;\n try {\n if (!isDemoActive()) {\n const vrij = new Set(await findUnusedMedia(siteId, doelen));\n inGebruik = doelen.filter((d) => !vrij.has(d.id)).length;\n }\n } catch {\n inGebruik = null; // scan mislukt: niet raden, wel waarschuwen\n }\n setBusy(false);\n const ok = await confirm({\n title: `${doelen.length} foto('s) verwijderen?`,\n description:\n inGebruik === null\n ? \"Kon niet nagaan of deze foto's nog op de site staan. Waar ze gebruikt worden, verschijnt dan een gebroken afbeelding.\"\n : inGebruik > 0\n ? `Let op: ${inGebruik} hiervan ${inGebruik === 1 ? \"wordt\" : \"worden\"} nog gebruikt (of ${inGebruik === 1 ? \"is\" : \"zijn\"} net geüpload). Daar verschijnt dan een gebroken afbeelding.`\n : \"Geen van deze foto's wordt nog gebruikt. Ze worden definitief verwijderd.\",\n confirmLabel: \"Verwijderen\",\n tone: \"danger\",\n });\n if (!ok) return;\n setBusy(true);\n let gelukt = 0;\n const fouten: string[] = [];\n for (const it of doelen) {\n try { await verwijderItem(it); gelukt++; } catch (e) { fouten.push(`${it.name}: ${(e as Error).message}`); }\n }\n setGeselecteerd(new Set());\n setBusy(false);\n if (gelukt) toast({ title: `${gelukt} foto('s) verwijderd`, tone: \"success\" });\n if (fouten.length) toast({ title: \"Niet alles kon weg\", description: fouten.join(\" · \"), tone: \"error\" });\n }\n\n /**\n * Origineel downloaden. `download` werkt niet cross-origin (Storage), dus eerst als blob ophalen;\n * lukt dat niet (CORS), dan in een nieuw tabblad openen — daar kan de klant hem bewaren.\n */\n async function download(item: MediaItem) {\n try {\n const res = await fetch(item.url);\n if (!res.ok) throw new Error(String(res.status));\n const url = URL.createObjectURL(await res.blob());\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = item.name || \"foto\";\n a.click();\n setTimeout(() => URL.revokeObjectURL(url), 1000);\n } catch {\n window.open(item.url, \"_blank\", \"noopener\");\n }\n }\n\n function wisselSelectie(id: string) {\n setGeselecteerd((s) => {\n const n = new Set(s);\n if (n.has(id)) n.delete(id);\n else n.add(id);\n return n;\n });\n }\n\n /** Doc + origineel + alle varianten weg — gedeeld door los verwijderen en de opruimer. */\n async function verwijderItem(item: MediaItem) {\n if (isDemoActive()) return removeDemoMedia(siteId, item.id);\n const db = getDb();\n const bucket = getBucket();\n if (!db) throw new Error(\"Geen databaseverbinding.\");\n await deleteDoc(doc(db, \"sites\", siteId, \"media\", item.id));\n if (bucket) {\n // Ook de srcset-varianten opruimen die de Cloud Function heeft gemaakt — anders blijven\n // die (publieke) bestanden voor altijd in Storage staan.\n const paths = [\n item.path,\n item.variants?.webp?.path,\n ...(item.variants?.sizes ?? []).map((v) => v.path),\n ].filter((p): p is string => Boolean(p));\n await Promise.all(\n paths.map((p) => deleteObject(storageRef(bucket, p)).catch(() => undefined /* al weg */)),\n );\n }\n }\n\n /**\n * Opruimer (7.14): Storage groeide anders onbeperkt — elke ooit geüploade foto (plus zijn\n * webp/srcset-varianten) bleef staan, ook als hij allang uit elk blok was gehaald. De scan is\n * expliciet (één knop, één gecombineerde content-read via findUnusedMedia) en het verwijderen\n * vraagt altijd eerst bevestiging met aantal en omvang.\n */\n async function scanOngebruikt() {\n setScant(true);\n try {\n const ids = await findUnusedMedia(siteId, items);\n setOngebruikt(ids);\n if (ids.length === 0) toast({ title: \"Niets op te ruimen\", description: \"Elke afbeelding in de bibliotheek wordt ergens gebruikt.\", tone: \"success\" });\n } catch (e) {\n toast({ title: \"Scan mislukt\", description: (e as Error).message, tone: \"error\" });\n } finally {\n setScant(false);\n }\n }\n\n async function ruimOngebruiktOp() {\n const doelen = items.filter((it) => ongebruikt?.includes(it.id));\n if (doelen.length === 0) return;\n const mb = (doelen.reduce((som, it) => som + (it.size ?? 0), 0) / (1024 * 1024)).toFixed(1);\n const ok = await confirm({\n title: `${doelen.length} ongebruikte afbeelding(en) verwijderen?`,\n description: `Samen ±${mb} MB, inclusief alle automatisch gemaakte varianten. Dit kan niet ongedaan worden gemaakt.`,\n confirmLabel: \"Opruimen\",\n tone: \"danger\",\n });\n if (!ok) return;\n setBusy(true);\n let gelukt = 0;\n const fouten: string[] = [];\n for (const it of doelen) {\n try { await verwijderItem(it); gelukt++; } catch (e) { fouten.push(`${it.name}: ${(e as Error).message}`); }\n }\n setOngebruikt(null);\n setBusy(false);\n if (gelukt) toast({ title: `${gelukt} afbeelding(en) opgeruimd`, description: `±${mb} MB opslag vrijgemaakt.`, tone: \"success\" });\n if (fouten.length) toast({ title: \"Niet alles kon weg\", description: fouten.join(\" · \"), tone: \"error\" });\n }\n\n const term = zoek.trim().toLowerCase();\n const aantalZonderAlt = items.filter((m) => !m.alt?.trim()).length;\n const getoond = sorteerMedia(\n items\n .filter((m) => !zonderAlt || !m.alt?.trim())\n .filter((m) => !term || `${m.name ?? \"\"} ${m.alt ?? \"\"}`.toLowerCase().includes(term)),\n sortering,\n );\n // Selectie alleen over wat er (nog) is: een elders verwijderde foto telt niet meer mee.\n const aantalGeselecteerd = items.filter((m) => geselecteerd.has(m.id)).length;\n const allesGetoondGeselecteerd = getoond.length > 0 && getoond.every((m) => geselecteerd.has(m.id));\n\n return (\n <div\n className={`grid gap-4 rounded-xl transition ${sleepOver ? \"outline-2 outline-dashed outline-brand-500 outline-offset-4\" : \"\"}`}\n // Slepen vanaf het bureaublad (7.24): de hele bibliotheek is een dropzone. Alleen bestanden\n // (geen tekst-drags), en de browser mag het bestand niet zelf openen.\n onDragOver={(e) => {\n if (e.dataTransfer.types.includes(\"Files\")) {\n e.preventDefault();\n setSleepOver(true);\n }\n }}\n onDragLeave={() => setSleepOver(false)}\n onDrop={(e) => {\n if (!e.dataTransfer.files?.length) return;\n e.preventDefault();\n setSleepOver(false);\n void onFiles(e.dataTransfer.files);\n }}\n >\n <div>\n <Button disabled={busy} onClick={() => inputRef.current?.click()}>\n {busy ? \"Uploaden…\" : \"Foto's uploaden\"}\n </Button>{\" \"}\n {!isDemoActive() && items.length > 0 && (\n ongebruikt === null ? (\n <Button variant=\"ghost\" disabled={scant || busy} onClick={() => void scanOngebruikt()}>\n {scant ? \"Zoeken…\" : \"🧹 Zoek ongebruikte foto's\"}\n </Button>\n ) : ongebruikt.length > 0 ? (\n <>\n <Button variant=\"secondary\" disabled={busy} onClick={() => void ruimOngebruiktOp()}>\n 🧹 {ongebruikt.length} ongebruikte verwijderen\n </Button>{\" \"}\n <Button variant=\"ghost\" onClick={() => setOngebruikt(null)}>Annuleren</Button>\n </>\n ) : null\n )}\n <p className=\"mt-1 text-xs text-slate-500\">\n Je kunt meerdere foto's tegelijk kiezen of ze hierheen slepen. Ze worden automatisch verkleind en\n geoptimaliseerd (webp) — dat houdt je site snel en bespaart opslag. Max. 15 MB per bestand.\n Klik op een foto om het <strong>focuspunt</strong> te kiezen: dat deel blijft in beeld als de foto\n wordt bijgesneden (bijvoorbeeld een brede kopfoto op een telefoon).\n </p>\n <div className=\"mt-3 flex flex-wrap items-center gap-3\">\n {items.length > 8 && (\n <div className=\"w-full max-w-xs\">\n <Input value={zoek} onChange={(e) => setZoek(e.target.value)} placeholder=\"Zoek op naam of beschrijving…\" aria-label=\"Zoek in mediabibliotheek\" />\n </div>\n )}\n {(aantalZonderAlt > 0 || zonderAlt) && (\n <Switch\n checked={zonderAlt}\n onCheckedChange={setZonderAlt}\n label={`Alleen zonder beschrijving (${aantalZonderAlt})`}\n />\n )}\n {items.length > 1 && (\n <div className=\"w-48\">\n <Select\n value={sortering}\n onValueChange={(v) => setSortering((v || \"nieuw\") as MediaSortering)}\n options={MEDIA_SORTERINGEN}\n placeholder=\"Sorteren\"\n />\n </div>\n )}\n </div>\n {aantalGeselecteerd > 0 && (\n <div\n role=\"region\"\n aria-label=\"Selectie\"\n className=\"sticky top-2 z-10 mt-3 flex flex-wrap items-center gap-2 rounded-lg border border-brand-500/40 bg-brand-50 px-3 py-2 text-sm shadow-sm\"\n >\n <strong>{aantalGeselecteerd} geselecteerd</strong>\n {!allesGetoondGeselecteerd && (\n <Button variant=\"ghost\" size=\"sm\" onClick={() => setGeselecteerd(new Set([...geselecteerd, ...getoond.map((m) => m.id)]))}>\n Alles selecteren ({getoond.length})\n </Button>\n )}\n <Button variant=\"ghost\" size=\"sm\" onClick={() => setGeselecteerd(new Set())}>\n Selectie wissen\n </Button>\n <span className=\"flex-1\" />\n <Button loading={busy} variant=\"danger\" size=\"sm\" disabled={busy} onClick={() => void verwijderSelectie()}>\n {`Verwijderen (${aantalGeselecteerd})`}\n </Button>\n </div>\n )}\n <input\n ref={inputRef}\n type=\"file\"\n accept=\"image/*\"\n multiple\n hidden\n onChange={(e) => {\n if (e.target.files?.length) void onFiles(e.target.files);\n e.target.value = \"\";\n }}\n />\n </div>\n <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-4\">\n {getoond.map((m) => (\n <Card key={m.id} className={`p-3 ${geselecteerd.has(m.id) ? \"ring-2 ring-brand-500\" : \"\"}`}>\n <div className=\"relative\">\n <FocusFoto item={m} onFocus={(f) => void zetFocus(m, f)} />\n {/* Los van de focusknop: klikken op het vinkje zet nooit een focuspunt. */}\n <label className=\"absolute top-1.5 left-1.5 flex h-7 w-7 cursor-pointer items-center justify-center rounded-md bg-white/90 shadow\">\n <input\n type=\"checkbox\"\n className=\"h-4 w-4 cursor-pointer accent-brand-600\"\n checked={geselecteerd.has(m.id)}\n onChange={() => wisselSelectie(m.id)}\n aria-label={`Selecteer ${m.name ?? \"foto\"}`}\n />\n </label>\n </div>\n <div className=\"mb-2 flex items-center justify-between gap-1\">\n <span className=\"min-w-0 text-xs text-slate-500\">\n <span className=\"block truncate\" title={m.name}>{m.name}</span>\n <span className=\"block truncate\">\n {[\n m.width && m.height ? `${m.width}×${m.height}` : \"\",\n leesbareGrootte(m.size),\n korteDatum((m as { createdAt?: unknown }).createdAt),\n ].filter(Boolean).join(\" · \")}\n </span>\n </span>\n {m.status === \"processing\" ? (\n <Badge tone=\"amber\">verwerken</Badge>\n ) : ongebruikt?.includes(m.id) ? (\n <Badge tone=\"amber\">ongebruikt</Badge>\n ) : (\n <Badge tone=\"green\">klaar</Badge>\n )}\n </div>\n <AltInput item={m} onSave={saveAlt} />\n <div className=\"flex flex-wrap gap-1\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"text-xs\"\n onClick={() =>\n void navigator.clipboard\n ?.writeText(m.url)\n .then(() => toast({ title: \"Adres gekopieerd\", tone: \"success\" }))\n .catch(() => toast({ title: \"Kopiëren lukte niet\", description: m.url, tone: \"error\" }))\n }\n >\n Kopieer adres\n </Button>\n <Button variant=\"ghost\" size=\"sm\" className=\"text-xs\" onClick={() => void download(m)}>\n Download\n </Button>\n <Button variant=\"dangerGhost\" size=\"sm\" className=\"text-xs\" onClick={() => void remove(m)}>\n Verwijderen\n </Button>\n </div>\n </Card>\n ))}\n {items.length === 0 && <LegeStaat titel=\"Nog geen foto's\" tekst=\"Sleep foto's hierheen of gebruik Uploaden. Ze worden vanzelf verkleind en in meerdere formaten klaargezet.\" />}\n {items.length > 0 && getoond.length === 0 && (\n <p className=\"text-sm text-slate-500\">\n {zonderAlt && !term ? \"Alle foto's hebben een beschrijving.\" : `Geen foto's gevonden voor \\u201c${zoek}\\u201d.`}\n </p>\n )}\n </div>\n {hasMore && (\n <div>\n <Button variant=\"secondary\" size=\"sm\" onClick={() => setPageCount((c) => c + 1)}>\n Meer laden…\n </Button>\n </div>\n )}\n </div>\n );\n}\n\n/** Controlled alt-veld: volgt externe updates (realtime snapshot) en slaat alleen échte wijzigingen op. */\nfunction AltInput({ item, onSave }: { item: MediaItem; onSave: (item: MediaItem, alt: string) => Promise<void> }) {\n const [value, setValue] = useState(item.alt ?? \"\");\n // Extern gewijzigd (andere tab/verwerking) → veld bijtrekken, behalve terwijl de klant zelf typt.\n const [focused, setFocused] = useState(false);\n useEffect(() => {\n if (!focused) setValue(item.alt ?? \"\");\n }, [item.alt, focused]);\n return (\n <Input\n value={value}\n placeholder=\"Beschrijving (alt-tekst)\"\n aria-label={`Beschrijving van ${item.name ?? \"de foto\"} voor schermlezers en Google`}\n className=\"mb-2 text-xs\"\n onChange={(e) => setValue(e.target.value)}\n onFocus={() => setFocused(true)}\n onBlur={() => {\n setFocused(false);\n // Pas sinds 8.0 komt onBlur hier echt aan; alleen opslaan als er iets veranderde.\n if (value !== (item.alt ?? \"\")) void onSave(item, value);\n }}\n />\n );\n}\n\n/**\n * Thumbnail met focuspunt-kiezer: klik = zet het punt op die plek, klik op het punt zelf = weg.\n * Het beeld toont `object-position` op het gekozen punt, zodat je meteen ziet wat het doet.\n */\nfunction FocusFoto({ item, onFocus }: { item: MediaItem; onFocus: (f: { x: number; y: number } | null) => void }) {\n const f = item.focal;\n return (\n <button\n type=\"button\"\n className=\"relative mb-2 block h-28 w-full cursor-crosshair overflow-hidden rounded-md focus-visible:ring-2 focus-visible:ring-brand-500 focus-visible:outline-none\"\n title={f ? \"Focuspunt: klik om te verplaatsen, klik op het punt om te verwijderen\" : \"Klik om het focuspunt te kiezen\"}\n aria-label={f ? `Focuspunt van ${item.name} aanpassen` : `Focuspunt van ${item.name} kiezen`}\n onClick={(e) => {\n const r = e.currentTarget.getBoundingClientRect();\n const x = Math.min(1, Math.max(0, (e.clientX - r.left) / r.width));\n const y = Math.min(1, Math.max(0, (e.clientY - r.top) / r.height));\n // Klik (vrijwel) op het bestaande punt = verwijderen.\n if (f && Math.abs(f.x - x) < 0.08 && Math.abs(f.y - y) < 0.12) onFocus(null);\n else onFocus({ x: Math.round(x * 100) / 100, y: Math.round(y * 100) / 100 });\n }}\n >\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img\n src={item.url}\n alt={item.alt ?? item.name}\n className=\"h-full w-full object-cover\"\n style={f ? { objectPosition: `${Math.round(f.x * 100)}% ${Math.round(f.y * 100)}%` } : undefined}\n />\n {f && (\n <span\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute h-4 w-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white bg-brand-500 shadow\"\n style={{ left: `${f.x * 100}%`, top: `${f.y * 100}%` }}\n />\n )}\n </button>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -2,6 +2,9 @@ export interface PickedMedia {
|
|
|
2
2
|
url: string;
|
|
3
3
|
mediaId: string;
|
|
4
4
|
alt?: string;
|
|
5
|
+
/** Afmetingen als de bibliotheek ze kent (8.10: waarschuwen bij een te kleine foto). */
|
|
6
|
+
width?: number;
|
|
7
|
+
height?: number;
|
|
5
8
|
}
|
|
6
9
|
/** Gecontroleerde mediabibliotheek-kiezer (open/onOpenChange extern) — zoek + klik om te kiezen, of
|
|
7
10
|
* upload direct. Gebruikt door de knop hieronder én door de inline beeld-vervangen in de editor. */
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
-
import { collection, limit, onSnapshot, orderBy, query } from "firebase/firestore";
|
|
4
|
+
import { collection, doc, limit, onSnapshot, orderBy, query, updateDoc } from "firebase/firestore";
|
|
5
5
|
import { getDb } from "../../firebase/client.js";
|
|
6
6
|
import { DEMO_EVENT, isDemoActive, listDemoMediaAll } from "../../demo/demoStore.js";
|
|
7
|
-
import { Button, Input } from "../ui/primitives.js";
|
|
7
|
+
import { Button, Field, Input } from "../ui/primitives.js";
|
|
8
8
|
import { Dialog, DialogContent } from "../ui/dialog.js";
|
|
9
9
|
import { useToast } from "../ui/toast.js";
|
|
10
10
|
import { uploadImage } from "../lib/upload.js";
|
|
@@ -17,6 +17,7 @@ function MediaPickerDialog({
|
|
|
17
17
|
}) {
|
|
18
18
|
const { toast } = useToast();
|
|
19
19
|
const [items, setItems] = useState([]);
|
|
20
|
+
const [geladen, setGeladen] = useState(false);
|
|
20
21
|
const [q, setQ] = useState("");
|
|
21
22
|
const [busy, setBusy] = useState(false);
|
|
22
23
|
const [pageCount, setPageCount] = useState(1);
|
|
@@ -25,13 +26,19 @@ function MediaPickerDialog({
|
|
|
25
26
|
useEffect(() => {
|
|
26
27
|
if (!open) return;
|
|
27
28
|
if (isDemoActive()) {
|
|
28
|
-
const sync = () =>
|
|
29
|
+
const sync = () => {
|
|
30
|
+
setItems(listDemoMediaAll(siteId));
|
|
31
|
+
setGeladen(true);
|
|
32
|
+
};
|
|
29
33
|
sync();
|
|
30
34
|
window.addEventListener(DEMO_EVENT, sync);
|
|
31
35
|
return () => window.removeEventListener(DEMO_EVENT, sync);
|
|
32
36
|
}
|
|
33
37
|
const db = getDb();
|
|
34
|
-
if (!db)
|
|
38
|
+
if (!db) {
|
|
39
|
+
setGeladen(true);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
35
42
|
const max = pageCount * PICKER_PAGE_SIZE;
|
|
36
43
|
const qy = query(collection(db, "sites", siteId, "media"), orderBy("createdAt", "desc"), limit(max + 1));
|
|
37
44
|
return onSnapshot(
|
|
@@ -39,10 +46,14 @@ function MediaPickerDialog({
|
|
|
39
46
|
(snap) => {
|
|
40
47
|
setHasMore(snap.docs.length > max);
|
|
41
48
|
setItems(snap.docs.slice(0, max).map((d) => ({ id: d.id, ...d.data() })));
|
|
49
|
+
setGeladen(true);
|
|
42
50
|
},
|
|
43
51
|
// Zonder onError worden lees-/rules-/App-Check-fouten stil geslikt → de kiezer toont dan
|
|
44
52
|
// onterecht "Nog geen afbeeldingen". Nu een duidelijke melding.
|
|
45
|
-
(err) =>
|
|
53
|
+
(err) => {
|
|
54
|
+
setGeladen(true);
|
|
55
|
+
toast({ title: "Kon mediabibliotheek niet laden", description: err.message, tone: "error" });
|
|
56
|
+
}
|
|
46
57
|
);
|
|
47
58
|
}, [open, siteId, toast, pageCount]);
|
|
48
59
|
const filtered = useMemo(() => {
|
|
@@ -50,8 +61,21 @@ function MediaPickerDialog({
|
|
|
50
61
|
if (!term) return items;
|
|
51
62
|
return items.filter((m) => `${m.name ?? ""} ${m.alt ?? ""}`.toLowerCase().includes(term));
|
|
52
63
|
}, [items, q]);
|
|
64
|
+
const [netGeupload, setNetGeupload] = useState(null);
|
|
65
|
+
const [altTekst, setAltTekst] = useState("");
|
|
66
|
+
async function gebruikUpload() {
|
|
67
|
+
if (!netGeupload) return;
|
|
68
|
+
const alt = altTekst.trim();
|
|
69
|
+
if (alt && !isDemoActive()) {
|
|
70
|
+
const db = getDb();
|
|
71
|
+
if (db) await updateDoc(doc(db, "sites", siteId, "media", netGeupload.mediaId), { alt }).catch((e) => console.warn("[xicms] alt-tekst bewaren mislukt:", e));
|
|
72
|
+
}
|
|
73
|
+
onSelect({ ...netGeupload, alt });
|
|
74
|
+
setNetGeupload(null);
|
|
75
|
+
onOpenChange(false);
|
|
76
|
+
}
|
|
53
77
|
function pick(m) {
|
|
54
|
-
onSelect({ url: m.url, mediaId: m.id, alt: m.alt });
|
|
78
|
+
onSelect({ url: m.url, mediaId: m.id, alt: m.alt, width: m.width, height: m.height });
|
|
55
79
|
onOpenChange(false);
|
|
56
80
|
}
|
|
57
81
|
async function onUpload(files) {
|
|
@@ -60,8 +84,8 @@ function MediaPickerDialog({
|
|
|
60
84
|
setBusy(true);
|
|
61
85
|
try {
|
|
62
86
|
const { url, mediaId } = await uploadImage(siteId, f);
|
|
63
|
-
|
|
64
|
-
|
|
87
|
+
setNetGeupload({ url, mediaId });
|
|
88
|
+
setAltTekst("");
|
|
65
89
|
} catch (err) {
|
|
66
90
|
toast({ title: "Upload mislukt", description: err.message, tone: "error" });
|
|
67
91
|
} finally {
|
|
@@ -69,30 +93,59 @@ function MediaPickerDialog({
|
|
|
69
93
|
}
|
|
70
94
|
}
|
|
71
95
|
if (!open) return null;
|
|
72
|
-
return /* @__PURE__ */ jsx(
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
96
|
+
return /* @__PURE__ */ jsx(
|
|
97
|
+
Dialog,
|
|
98
|
+
{
|
|
99
|
+
open,
|
|
100
|
+
onOpenChange: (v) => {
|
|
101
|
+
if (!v && netGeupload) {
|
|
102
|
+
onSelect({ ...netGeupload, alt: altTekst.trim() });
|
|
103
|
+
setNetGeupload(null);
|
|
104
|
+
}
|
|
105
|
+
onOpenChange(v);
|
|
106
|
+
},
|
|
107
|
+
children: /* @__PURE__ */ jsx(DialogContent, { title: netGeupload ? "Beschrijf de foto" : "Afbeelding kiezen", width: "min(92vw, 42rem)", children: netGeupload ? /* @__PURE__ */ jsxs(
|
|
108
|
+
"form",
|
|
84
109
|
{
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
110
|
+
className: "grid gap-3",
|
|
111
|
+
onSubmit: (e) => {
|
|
112
|
+
e.preventDefault();
|
|
113
|
+
void gebruikUpload();
|
|
114
|
+
},
|
|
115
|
+
children: [
|
|
116
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
|
|
117
|
+
/* @__PURE__ */ jsx("img", { src: netGeupload.url, alt: "", className: "h-24 w-24 shrink-0 rounded-lg object-cover" }),
|
|
118
|
+
/* @__PURE__ */ jsx(Field, { label: "Wat staat er op de foto?", hint: "E\xE9n korte zin, bv. 'Gazon met border vol lavendel'. Voor slechtzienden en voor Google; mag ook leeg.", children: /* @__PURE__ */ jsx(Input, { value: altTekst, onChange: (e) => setAltTekst(e.target.value), autoFocus: true, maxLength: 200 }) })
|
|
119
|
+
] }),
|
|
120
|
+
/* @__PURE__ */ jsx("div", { className: "flex justify-end gap-2", children: /* @__PURE__ */ jsx(Button, { type: "submit", children: "Foto gebruiken" }) })
|
|
121
|
+
]
|
|
122
|
+
}
|
|
123
|
+
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
124
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-3 flex items-center gap-2", children: [
|
|
125
|
+
/* @__PURE__ */ jsx(Input, { value: q, onChange: (e) => setQ(e.target.value), placeholder: "Zoek in bibliotheek\u2026", className: "h-9" }),
|
|
126
|
+
/* @__PURE__ */ jsx(Button, { type: "button", disabled: busy, onClick: () => inputRef.current?.click(), children: busy ? "Uploaden\u2026" : "Upload nieuwe" }),
|
|
127
|
+
/* @__PURE__ */ jsx("input", { ref: inputRef, type: "file", accept: "image/*", hidden: true, onChange: (e) => {
|
|
128
|
+
void onUpload(e.target.files);
|
|
129
|
+
e.target.value = "";
|
|
130
|
+
} })
|
|
131
|
+
] }),
|
|
132
|
+
/* @__PURE__ */ jsxs("div", { className: "max-h-[55vh] overflow-y-auto", children: [
|
|
133
|
+
!geladen && items.length === 0 ? /* @__PURE__ */ jsx("p", { className: "py-8 text-center text-sm text-slate-500", role: "status", children: "Foto's laden\u2026" }) : filtered.length === 0 ? /* @__PURE__ */ jsx("p", { className: "py-8 text-center text-sm text-slate-500", children: items.length === 0 ? "Nog geen afbeeldingen \u2014 upload er een." : "Geen resultaten." }) : /* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 gap-2 sm:grid-cols-4", children: filtered.map((m) => /* @__PURE__ */ jsx(
|
|
134
|
+
"button",
|
|
135
|
+
{
|
|
136
|
+
type: "button",
|
|
137
|
+
onClick: () => pick(m),
|
|
138
|
+
title: m.alt || m.name,
|
|
139
|
+
className: "group relative overflow-hidden rounded-md border border-slate-200 focus:outline-none focus:ring-2 focus:ring-indigo-500",
|
|
140
|
+
children: /* @__PURE__ */ jsx("img", { src: m.url, alt: m.alt ?? m.name, className: "aspect-square w-full object-cover transition group-hover:opacity-80" })
|
|
141
|
+
},
|
|
142
|
+
m.id
|
|
143
|
+
)) }),
|
|
144
|
+
hasMore && !q.trim() && /* @__PURE__ */ jsx("p", { className: "mt-2 text-center", children: /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setPageCount((c) => c + 1), children: "Meer laden\u2026" }) })
|
|
145
|
+
] })
|
|
146
|
+
] }) })
|
|
147
|
+
}
|
|
148
|
+
);
|
|
96
149
|
}
|
|
97
150
|
function MediaPickerButton({
|
|
98
151
|
siteId,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/media/MediaPicker.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useMemo, useRef, useState } from \"react\";\nimport { collection, limit, onSnapshot, orderBy, query } from \"firebase/firestore\";\nimport { getDb } from \"../../firebase/client\";\nimport { DEMO_EVENT, isDemoActive, listDemoMediaAll } from \"../../demo/demoStore\";\nimport type { MediaItem } from \"../../content/types\";\nimport { Button, Input } from \"../ui/primitives\";\nimport { Dialog, DialogContent } from \"../ui/dialog\";\nimport { useToast } from \"../ui/toast\";\nimport { uploadImage } from \"../lib/upload\";\n\nexport interface PickedMedia {\n url: string;\n mediaId: string;\n alt?: string;\n}\n\n/** Paginagrootte: begrensd laden i.p.v. de hele bibliotheek bij elke kiezer-open. */\nconst PICKER_PAGE_SIZE = 48;\n\n/** Gecontroleerde mediabibliotheek-kiezer (open/onOpenChange extern) — zoek + klik om te kiezen, of\n * upload direct. Gebruikt door de knop hieronder én door de inline beeld-vervangen in de editor. */\nexport function MediaPickerDialog({\n siteId,\n open,\n onOpenChange,\n onSelect,\n}: {\n siteId: string;\n open: boolean;\n onOpenChange: (v: boolean) => void;\n onSelect: (m: PickedMedia) => void;\n}) {\n const { toast } = useToast();\n const [items, setItems] = useState<MediaItem[]>([]);\n const [q, setQ] = useState(\"\");\n const [busy, setBusy] = useState(false);\n const [pageCount, setPageCount] = useState(1);\n const [hasMore, setHasMore] = useState(false);\n const inputRef = useRef<HTMLInputElement>(null);\n\n useEffect(() => {\n if (!open) return;\n // Demo-modus: lokale bibliotheek (geseede foto's + eigen \"uploads\") i.p.v. Firestore.\n if (isDemoActive()) {\n const sync = () => setItems(listDemoMediaAll(siteId));\n sync();\n window.addEventListener(DEMO_EVENT, sync);\n return () => window.removeEventListener(DEMO_EVENT, sync);\n }\n const db = getDb();\n if (!db) return;\n // Begrensd laden (+\"meer laden\"): geen volledige collectie-read bij elke kiezer-open.\n const max = pageCount * PICKER_PAGE_SIZE;\n const qy = query(collection(db, \"sites\", siteId, \"media\"), orderBy(\"createdAt\", \"desc\"), limit(max + 1));\n return onSnapshot(\n qy,\n (snap) => {\n setHasMore(snap.docs.length > max);\n setItems(snap.docs.slice(0, max).map((d) => ({ id: d.id, ...(d.data() as Omit<MediaItem, \"id\">) })));\n },\n // Zonder onError worden lees-/rules-/App-Check-fouten stil geslikt → de kiezer toont dan\n // onterecht \"Nog geen afbeeldingen\". Nu een duidelijke melding.\n (err) => toast({ title: \"Kon mediabibliotheek niet laden\", description: err.message, tone: \"error\" }),\n );\n }, [open, siteId, toast, pageCount]);\n\n const filtered = useMemo(() => {\n const term = q.trim().toLowerCase();\n if (!term) return items;\n return items.filter((m) => `${m.name ?? \"\"} ${m.alt ?? \"\"}`.toLowerCase().includes(term));\n }, [items, q]);\n\n function pick(m: MediaItem) {\n onSelect({ url: m.url, mediaId: m.id, alt: m.alt });\n onOpenChange(false);\n }\n\n async function onUpload(files: FileList | null) {\n const f = files?.[0];\n if (!f) return;\n setBusy(true);\n try {\n const { url, mediaId } = await uploadImage(siteId, f);\n onSelect({ url, mediaId, alt: \"\" });\n onOpenChange(false);\n } catch (err) {\n toast({ title: \"Upload mislukt\", description: (err as Error).message, tone: \"error\" });\n } finally {\n setBusy(false);\n }\n }\n\n // Dicht = niet gemount. Dit scheelt niet alleen een onzichtbare mediabibliotheek in de boom\n // (met zijn abonnementen en state): een permanent gemounte dichte dialoog bleek ook het\n // aanknopingspunt voor een scroll-lock-lek — zie het commentaar in EditorProvider bij\n // `visualEditorModule`. Wat er niet staat, kan niets vergrendelen.\n if (!open) return null;\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent title=\"Afbeelding kiezen\" width=\"min(92vw, 42rem)\">\n <div className=\"mb-3 flex items-center gap-2\">\n <Input value={q} onChange={(e) => setQ(e.target.value)} placeholder=\"Zoek in bibliotheek…\" className=\"h-9\" />\n <Button type=\"button\" disabled={busy} onClick={() => inputRef.current?.click()}>\n {busy ? \"Uploaden…\" : \"Upload nieuwe\"}\n </Button>\n <input ref={inputRef} type=\"file\" accept=\"image/*\" hidden onChange={(e) => { void onUpload(e.target.files); e.target.value = \"\"; }} />\n </div>\n <div className=\"max-h-[55vh] overflow-y-auto\">\n {filtered.length === 0 ? (\n <p className=\"py-8 text-center text-sm text-slate-500\">\n {items.length === 0 ? \"Nog geen afbeeldingen — upload er een.\" : \"Geen resultaten.\"}\n </p>\n ) : (\n <div className=\"grid grid-cols-3 gap-2 sm:grid-cols-4\">\n {filtered.map((m) => (\n <button\n key={m.id}\n type=\"button\"\n onClick={() => pick(m)}\n title={m.alt || m.name}\n className=\"group relative overflow-hidden rounded-md border border-slate-200 focus:outline-none focus:ring-2 focus:ring-indigo-500\"\n >\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img src={m.url} alt={m.alt ?? m.name} className=\"aspect-square w-full object-cover transition group-hover:opacity-80\" />\n </button>\n ))}\n </div>\n )}\n {hasMore && !q.trim() && (\n <p className=\"mt-2 text-center\">\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => setPageCount((c) => c + 1)}>\n Meer laden…\n </Button>\n </p>\n )}\n </div>\n </DialogContent>\n </Dialog>\n );\n}\n\n/**\n * Knop die de mediabibliotheek-kiezer opent: zoek + klik om een bestaande afbeelding te kiezen, of\n * upload er direct een nieuwe in. Vervangt het plakken van losse URL's.\n */\nexport function MediaPickerButton({\n siteId,\n onSelect,\n label = \"Kies / upload\",\n size = \"sm\",\n variant = \"secondary\",\n}: {\n siteId: string;\n onSelect: (m: PickedMedia) => void;\n label?: string;\n size?: \"sm\" | \"md\";\n variant?: \"primary\" | \"secondary\" | \"ghost\";\n}) {\n const [open, setOpen] = useState(false);\n return (\n <>\n <Button type=\"button\" variant={variant} size={size} onClick={() => setOpen(true)}>{label}</Button>\n <MediaPickerDialog siteId={siteId} open={open} onOpenChange={setOpen} onSelect={onSelect} />\n </>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/media/MediaPicker.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useMemo, useRef, useState } from \"react\";\nimport { collection, doc, limit, onSnapshot, orderBy, query, updateDoc } from \"firebase/firestore\";\nimport { getDb } from \"../../firebase/client\";\nimport { DEMO_EVENT, isDemoActive, listDemoMediaAll } from \"../../demo/demoStore\";\nimport type { MediaItem } from \"../../content/types\";\nimport { Button, Field, Input } from \"../ui/primitives\";\nimport { Dialog, DialogContent } from \"../ui/dialog\";\nimport { useToast } from \"../ui/toast\";\nimport { uploadImage } from \"../lib/upload\";\n\nexport interface PickedMedia {\n url: string;\n mediaId: string;\n alt?: string;\n /** Afmetingen als de bibliotheek ze kent (8.10: waarschuwen bij een te kleine foto). */\n width?: number;\n height?: number;\n}\n\n/** Paginagrootte: begrensd laden i.p.v. de hele bibliotheek bij elke kiezer-open. */\nconst PICKER_PAGE_SIZE = 48;\n\n/** Gecontroleerde mediabibliotheek-kiezer (open/onOpenChange extern) — zoek + klik om te kiezen, of\n * upload direct. Gebruikt door de knop hieronder én door de inline beeld-vervangen in de editor. */\nexport function MediaPickerDialog({\n siteId,\n open,\n onOpenChange,\n onSelect,\n}: {\n siteId: string;\n open: boolean;\n onOpenChange: (v: boolean) => void;\n onSelect: (m: PickedMedia) => void;\n}) {\n const { toast } = useToast();\n const [items, setItems] = useState<MediaItem[]>([]);\n /** 8.1: tot de eerste snapshot binnen is, is het \"laden\" — niet \"nog geen afbeeldingen\". */\n const [geladen, setGeladen] = useState(false);\n const [q, setQ] = useState(\"\");\n const [busy, setBusy] = useState(false);\n const [pageCount, setPageCount] = useState(1);\n const [hasMore, setHasMore] = useState(false);\n const inputRef = useRef<HTMLInputElement>(null);\n\n useEffect(() => {\n if (!open) return;\n // Demo-modus: lokale bibliotheek (geseede foto's + eigen \"uploads\") i.p.v. Firestore.\n if (isDemoActive()) {\n const sync = () => {\n setItems(listDemoMediaAll(siteId));\n setGeladen(true);\n };\n sync();\n window.addEventListener(DEMO_EVENT, sync);\n return () => window.removeEventListener(DEMO_EVENT, sync);\n }\n const db = getDb();\n if (!db) {\n setGeladen(true);\n return;\n }\n // Begrensd laden (+\"meer laden\"): geen volledige collectie-read bij elke kiezer-open.\n const max = pageCount * PICKER_PAGE_SIZE;\n const qy = query(collection(db, \"sites\", siteId, \"media\"), orderBy(\"createdAt\", \"desc\"), limit(max + 1));\n return onSnapshot(\n qy,\n (snap) => {\n setHasMore(snap.docs.length > max);\n setItems(snap.docs.slice(0, max).map((d) => ({ id: d.id, ...(d.data() as Omit<MediaItem, \"id\">) })));\n setGeladen(true);\n },\n // Zonder onError worden lees-/rules-/App-Check-fouten stil geslikt → de kiezer toont dan\n // onterecht \"Nog geen afbeeldingen\". Nu een duidelijke melding.\n (err) => {\n setGeladen(true);\n toast({ title: \"Kon mediabibliotheek niet laden\", description: err.message, tone: \"error\" });\n },\n );\n }, [open, siteId, toast, pageCount]);\n\n const filtered = useMemo(() => {\n const term = q.trim().toLowerCase();\n if (!term) return items;\n return items.filter((m) => `${m.name ?? \"\"} ${m.alt ?? \"\"}`.toLowerCase().includes(term));\n }, [items, q]);\n\n const [netGeupload, setNetGeupload] = useState<{ url: string; mediaId: string } | null>(null);\n const [altTekst, setAltTekst] = useState(\"\");\n\n async function gebruikUpload() {\n if (!netGeupload) return;\n const alt = altTekst.trim();\n if (alt && !isDemoActive()) {\n const db = getDb();\n if (db) await updateDoc(doc(db, \"sites\", siteId, \"media\", netGeupload.mediaId), { alt }).catch((e) => console.warn(\"[xicms] alt-tekst bewaren mislukt:\", e));\n }\n onSelect({ ...netGeupload, alt });\n setNetGeupload(null);\n onOpenChange(false);\n }\n\n function pick(m: MediaItem) {\n onSelect({ url: m.url, mediaId: m.id, alt: m.alt, width: m.width, height: m.height });\n onOpenChange(false);\n }\n\n async function onUpload(files: FileList | null) {\n const f = files?.[0];\n if (!f) return;\n setBusy(true);\n try {\n const { url, mediaId } = await uploadImage(siteId, f);\n // 8.2: eerst om een beschrijving vragen. Zonder die stap kreeg elke via de kiezer\n // geüploade foto `alt: \"\"` — voor schermlezers en Google een naamloze afbeelding.\n setNetGeupload({ url, mediaId });\n setAltTekst(\"\");\n } catch (err) {\n toast({ title: \"Upload mislukt\", description: (err as Error).message, tone: \"error\" });\n } finally {\n setBusy(false);\n }\n }\n\n // Dicht = niet gemount. Dit scheelt niet alleen een onzichtbare mediabibliotheek in de boom\n // (met zijn abonnementen en state): een permanent gemounte dichte dialoog bleek ook het\n // aanknopingspunt voor een scroll-lock-lek — zie het commentaar in EditorProvider bij\n // `visualEditorModule`. Wat er niet staat, kan niets vergrendelen.\n if (!open) return null;\n\n return (\n <Dialog\n open={open}\n onOpenChange={(v) => {\n // Sluit je de dialoog tijdens de beschrijf-stap, dan is de foto toch gekozen (zonder alt).\n if (!v && netGeupload) {\n onSelect({ ...netGeupload, alt: altTekst.trim() });\n setNetGeupload(null);\n }\n onOpenChange(v);\n }}\n >\n <DialogContent title={netGeupload ? \"Beschrijf de foto\" : \"Afbeelding kiezen\"} width=\"min(92vw, 42rem)\">\n {netGeupload ? (\n <form\n className=\"grid gap-3\"\n onSubmit={(e) => {\n e.preventDefault();\n void gebruikUpload();\n }}\n >\n <div className=\"flex items-start gap-3\">\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img src={netGeupload.url} alt=\"\" className=\"h-24 w-24 shrink-0 rounded-lg object-cover\" />\n <Field label=\"Wat staat er op de foto?\" hint=\"Eén korte zin, bv. 'Gazon met border vol lavendel'. Voor slechtzienden en voor Google; mag ook leeg.\">\n <Input value={altTekst} onChange={(e) => setAltTekst(e.target.value)} autoFocus maxLength={200} />\n </Field>\n </div>\n <div className=\"flex justify-end gap-2\">\n <Button type=\"submit\">Foto gebruiken</Button>\n </div>\n </form>\n ) : (\n <>\n <div className=\"mb-3 flex items-center gap-2\">\n <Input value={q} onChange={(e) => setQ(e.target.value)} placeholder=\"Zoek in bibliotheek…\" className=\"h-9\" />\n <Button type=\"button\" disabled={busy} onClick={() => inputRef.current?.click()}>\n {busy ? \"Uploaden…\" : \"Upload nieuwe\"}\n </Button>\n <input ref={inputRef} type=\"file\" accept=\"image/*\" hidden onChange={(e) => { void onUpload(e.target.files); e.target.value = \"\"; }} />\n </div>\n <div className=\"max-h-[55vh] overflow-y-auto\">\n {!geladen && items.length === 0 ? (\n <p className=\"py-8 text-center text-sm text-slate-500\" role=\"status\">Foto's laden…</p>\n ) : filtered.length === 0 ? (\n <p className=\"py-8 text-center text-sm text-slate-500\">\n {items.length === 0 ? \"Nog geen afbeeldingen — upload er een.\" : \"Geen resultaten.\"}\n </p>\n ) : (\n <div className=\"grid grid-cols-3 gap-2 sm:grid-cols-4\">\n {filtered.map((m) => (\n <button\n key={m.id}\n type=\"button\"\n onClick={() => pick(m)}\n title={m.alt || m.name}\n className=\"group relative overflow-hidden rounded-md border border-slate-200 focus:outline-none focus:ring-2 focus:ring-indigo-500\"\n >\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img src={m.url} alt={m.alt ?? m.name} className=\"aspect-square w-full object-cover transition group-hover:opacity-80\" />\n </button>\n ))}\n </div>\n )}\n {hasMore && !q.trim() && (\n <p className=\"mt-2 text-center\">\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => setPageCount((c) => c + 1)}>\n Meer laden…\n </Button>\n </p>\n )}\n </div>\n </>\n )}\n </DialogContent>\n </Dialog>\n );\n}\n\n/**\n * Knop die de mediabibliotheek-kiezer opent: zoek + klik om een bestaande afbeelding te kiezen, of\n * upload er direct een nieuwe in. Vervangt het plakken van losse URL's.\n */\nexport function MediaPickerButton({\n siteId,\n onSelect,\n label = \"Kies / upload\",\n size = \"sm\",\n variant = \"secondary\",\n}: {\n siteId: string;\n onSelect: (m: PickedMedia) => void;\n label?: string;\n size?: \"sm\" | \"md\";\n variant?: \"primary\" | \"secondary\" | \"ghost\";\n}) {\n const [open, setOpen] = useState(false);\n return (\n <>\n <Button type=\"button\" variant={variant} size={size} onClick={() => setOpen(true)}>{label}</Button>\n <MediaPickerDialog siteId={siteId} open={open} onOpenChange={setOpen} onSelect={onSelect} />\n </>\n );\n}\n"],"mappings":";AAyJY,SAYJ,UAVM,KAFF;AAvJZ,SAAS,WAAW,SAAS,QAAQ,gBAAgB;AACrD,SAAS,YAAY,KAAK,OAAO,YAAY,SAAS,OAAO,iBAAiB;AAC9E,SAAS,aAAa;AACtB,SAAS,YAAY,cAAc,wBAAwB;AAE3D,SAAS,QAAQ,OAAO,aAAa;AACrC,SAAS,QAAQ,qBAAqB;AACtC,SAAS,gBAAgB;AACzB,SAAS,mBAAmB;AAY5B,MAAM,mBAAmB;AAIlB,SAAS,kBAAkB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKG;AACD,QAAM,EAAE,MAAM,IAAI,SAAS;AAC3B,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAsB,CAAC,CAAC;AAElD,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAC5C,QAAM,CAAC,GAAG,IAAI,IAAI,SAAS,EAAE;AAC7B,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,KAAK;AACtC,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,CAAC;AAC5C,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAC5C,QAAM,WAAW,OAAyB,IAAI;AAE9C,YAAU,MAAM;AACd,QAAI,CAAC,KAAM;AAEX,QAAI,aAAa,GAAG;AAClB,YAAM,OAAO,MAAM;AACjB,iBAAS,iBAAiB,MAAM,CAAC;AACjC,mBAAW,IAAI;AAAA,MACjB;AACA,WAAK;AACL,aAAO,iBAAiB,YAAY,IAAI;AACxC,aAAO,MAAM,OAAO,oBAAoB,YAAY,IAAI;AAAA,IAC1D;AACA,UAAM,KAAK,MAAM;AACjB,QAAI,CAAC,IAAI;AACP,iBAAW,IAAI;AACf;AAAA,IACF;AAEA,UAAM,MAAM,YAAY;AACxB,UAAM,KAAK,MAAM,WAAW,IAAI,SAAS,QAAQ,OAAO,GAAG,QAAQ,aAAa,MAAM,GAAG,MAAM,MAAM,CAAC,CAAC;AACvG,WAAO;AAAA,MACL;AAAA,MACA,CAAC,SAAS;AACR,mBAAW,KAAK,KAAK,SAAS,GAAG;AACjC,iBAAS,KAAK,KAAK,MAAM,GAAG,GAAG,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,GAAI,EAAE,KAAK,EAA4B,EAAE,CAAC;AACnG,mBAAW,IAAI;AAAA,MACjB;AAAA;AAAA;AAAA,MAGA,CAAC,QAAQ;AACP,mBAAW,IAAI;AACf,cAAM,EAAE,OAAO,mCAAmC,aAAa,IAAI,SAAS,MAAM,QAAQ,CAAC;AAAA,MAC7F;AAAA,IACF;AAAA,EACF,GAAG,CAAC,MAAM,QAAQ,OAAO,SAAS,CAAC;AAEnC,QAAM,WAAW,QAAQ,MAAM;AAC7B,UAAM,OAAO,EAAE,KAAK,EAAE,YAAY;AAClC,QAAI,CAAC,KAAM,QAAO;AAClB,WAAO,MAAM,OAAO,CAAC,MAAM,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,YAAY,EAAE,SAAS,IAAI,CAAC;AAAA,EAC1F,GAAG,CAAC,OAAO,CAAC,CAAC;AAEb,QAAM,CAAC,aAAa,cAAc,IAAI,SAAkD,IAAI;AAC5F,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,EAAE;AAE3C,iBAAe,gBAAgB;AAC7B,QAAI,CAAC,YAAa;AAClB,UAAM,MAAM,SAAS,KAAK;AAC1B,QAAI,OAAO,CAAC,aAAa,GAAG;AAC1B,YAAM,KAAK,MAAM;AACjB,UAAI,GAAI,OAAM,UAAU,IAAI,IAAI,SAAS,QAAQ,SAAS,YAAY,OAAO,GAAG,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,QAAQ,KAAK,sCAAsC,CAAC,CAAC;AAAA,IAC7J;AACA,aAAS,EAAE,GAAG,aAAa,IAAI,CAAC;AAChC,mBAAe,IAAI;AACnB,iBAAa,KAAK;AAAA,EACpB;AAEA,WAAS,KAAK,GAAc;AAC1B,aAAS,EAAE,KAAK,EAAE,KAAK,SAAS,EAAE,IAAI,KAAK,EAAE,KAAK,OAAO,EAAE,OAAO,QAAQ,EAAE,OAAO,CAAC;AACpF,iBAAa,KAAK;AAAA,EACpB;AAEA,iBAAe,SAAS,OAAwB;AAC9C,UAAM,IAAI,QAAQ,CAAC;AACnB,QAAI,CAAC,EAAG;AACR,YAAQ,IAAI;AACZ,QAAI;AACF,YAAM,EAAE,KAAK,QAAQ,IAAI,MAAM,YAAY,QAAQ,CAAC;AAGpD,qBAAe,EAAE,KAAK,QAAQ,CAAC;AAC/B,kBAAY,EAAE;AAAA,IAChB,SAAS,KAAK;AACZ,YAAM,EAAE,OAAO,kBAAkB,aAAc,IAAc,SAAS,MAAM,QAAQ,CAAC;AAAA,IACvF,UAAE;AACA,cAAQ,KAAK;AAAA,IACf;AAAA,EACF;AAMA,MAAI,CAAC,KAAM,QAAO;AAElB,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,cAAc,CAAC,MAAM;AAEnB,YAAI,CAAC,KAAK,aAAa;AACrB,mBAAS,EAAE,GAAG,aAAa,KAAK,SAAS,KAAK,EAAE,CAAC;AACjD,yBAAe,IAAI;AAAA,QACrB;AACA,qBAAa,CAAC;AAAA,MAChB;AAAA,MAEA,8BAAC,iBAAc,OAAO,cAAc,sBAAsB,qBAAqB,OAAM,oBAClF,wBACC;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,UAAU,CAAC,MAAM;AACf,cAAE,eAAe;AACjB,iBAAK,cAAc;AAAA,UACrB;AAAA,UAEA;AAAA,iCAAC,SAAI,WAAU,0BAEb;AAAA,kCAAC,SAAI,KAAK,YAAY,KAAK,KAAI,IAAG,WAAU,8CAA6C;AAAA,cACzF,oBAAC,SAAM,OAAM,4BAA2B,MAAK,2GAC3C,8BAAC,SAAM,OAAO,UAAU,UAAU,CAAC,MAAM,YAAY,EAAE,OAAO,KAAK,GAAG,WAAS,MAAC,WAAW,KAAK,GAClG;AAAA,eACF;AAAA,YACA,oBAAC,SAAI,WAAU,0BACb,8BAAC,UAAO,MAAK,UAAS,4BAAc,GACtC;AAAA;AAAA;AAAA,MACF,IAEF,iCACA;AAAA,6BAAC,SAAI,WAAU,gCACb;AAAA,8BAAC,SAAM,OAAO,GAAG,UAAU,CAAC,MAAM,KAAK,EAAE,OAAO,KAAK,GAAG,aAAY,6BAAuB,WAAU,OAAM;AAAA,UAC3G,oBAAC,UAAO,MAAK,UAAS,UAAU,MAAM,SAAS,MAAM,SAAS,SAAS,MAAM,GAC1E,iBAAO,mBAAc,iBACxB;AAAA,UACA,oBAAC,WAAM,KAAK,UAAU,MAAK,QAAO,QAAO,WAAU,QAAM,MAAC,UAAU,CAAC,MAAM;AAAE,iBAAK,SAAS,EAAE,OAAO,KAAK;AAAG,cAAE,OAAO,QAAQ;AAAA,UAAI,GAAG;AAAA,WACtI;AAAA,QACA,qBAAC,SAAI,WAAU,gCACZ;AAAA,WAAC,WAAW,MAAM,WAAW,IAC5B,oBAAC,OAAE,WAAU,2CAA0C,MAAK,UAAS,gCAAa,IAChF,SAAS,WAAW,IACtB,oBAAC,OAAE,WAAU,2CACV,gBAAM,WAAW,IAAI,gDAA2C,oBACnE,IAEA,oBAAC,SAAI,WAAU,yCACZ,mBAAS,IAAI,CAAC,MACb;AAAA,YAAC;AAAA;AAAA,cAEC,MAAK;AAAA,cACL,SAAS,MAAM,KAAK,CAAC;AAAA,cACrB,OAAO,EAAE,OAAO,EAAE;AAAA,cAClB,WAAU;AAAA,cAGV,8BAAC,SAAI,KAAK,EAAE,KAAK,KAAK,EAAE,OAAO,EAAE,MAAM,WAAU,uEAAsE;AAAA;AAAA,YAPlH,EAAE;AAAA,UAQT,CACD,GACH;AAAA,UAED,WAAW,CAAC,EAAE,KAAK,KAClB,oBAAC,OAAE,WAAU,oBACX,8BAAC,UAAO,MAAK,UAAS,SAAQ,SAAQ,MAAK,MAAK,SAAS,MAAM,aAAa,CAAC,MAAM,IAAI,CAAC,GAAG,8BAE3F,GACF;AAAA,WAEJ;AAAA,SACA,GAEF;AAAA;AAAA,EACF;AAEJ;AAMO,SAAS,kBAAkB;AAAA,EAChC;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,UAAU;AACZ,GAMG;AACD,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,KAAK;AACtC,SACE,iCACE;AAAA,wBAAC,UAAO,MAAK,UAAS,SAAkB,MAAY,SAAS,MAAM,QAAQ,IAAI,GAAI,iBAAM;AAAA,IACzF,oBAAC,qBAAkB,QAAgB,MAAY,cAAc,SAAS,UAAoB;AAAA,KAC5F;AAEJ;","names":[]}
|