@xiwebdesign/xicms-core 6.4.7 → 7.0.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/SiteChrome.d.ts +12 -0
- package/dist/SiteChrome.js +24 -0
- package/dist/SiteChrome.js.map +1 -0
- package/dist/XiCMSProvider.d.ts +16 -8
- package/dist/XiCMSProvider.js +83 -58
- package/dist/XiCMSProvider.js.map +1 -1
- package/dist/admin/AdminApp.d.ts +1 -1
- package/dist/admin/AdminApp.js +79 -35
- package/dist/admin/AdminApp.js.map +1 -1
- package/dist/admin/AdminHelp.d.ts +8 -1
- package/dist/admin/AdminHelp.js +2 -2
- package/dist/admin/AdminHelp.js.map +1 -1
- package/dist/admin/NewsEditor.js +9 -9
- package/dist/admin/SuperadminPanel.js +5 -5
- package/dist/admin/blockEditors/GenericEditor.d.ts +2 -2
- package/dist/admin/blockEditors/GenericEditor.js +12 -11
- package/dist/admin/blockEditors/GenericEditor.js.map +1 -1
- package/dist/admin/blockEditors/StyleEditor.d.ts +1 -1
- package/dist/admin/blockEditors/StyleEditor.js +2 -2
- package/dist/admin/blockEditors/SymbolRefEditor.d.ts +1 -1
- package/dist/admin/blockEditors/SymbolRefEditor.js +5 -5
- package/dist/admin/blockEditors/index.d.ts +1 -1
- package/dist/admin/blockEditors/index.js +10 -10
- package/dist/admin/customBlocks/CustomBlocksPanel.js +7 -7
- package/dist/admin/editors/RichTextEditor.js +8 -7
- package/dist/admin/editors/RichTextEditor.js.map +1 -1
- package/dist/admin/editors/fontSizeExtension.js +1 -1
- package/dist/admin/editors/fontSizeExtension.js.map +1 -1
- package/dist/admin/forms/FormsPanel.js +9 -9
- package/dist/admin/index.d.ts +16 -16
- package/dist/admin/index.js +16 -16
- package/dist/admin/lib/mediaUsage.js +1 -1
- package/dist/admin/lib/pagesClient.d.ts +1 -1
- package/dist/admin/lib/pagesClient.js +11 -7
- package/dist/admin/lib/pagesClient.js.map +1 -1
- package/dist/admin/lib/sanitizeDraft.d.ts +9 -0
- package/dist/admin/lib/sanitizeDraft.js +28 -0
- package/dist/admin/lib/sanitizeDraft.js.map +1 -0
- package/dist/admin/lib/symbolsClient.d.ts +1 -1
- package/dist/admin/lib/symbolsClient.js +3 -3
- package/dist/admin/lib/upload.js +3 -3
- package/dist/admin/lib/useNestedDialog.d.ts +15 -0
- package/dist/admin/lib/useNestedDialog.js +23 -0
- package/dist/admin/lib/useNestedDialog.js.map +1 -0
- package/dist/admin/lib/zodFields.d.ts +4 -18
- package/dist/admin/lib/zodFields.js +54 -35
- package/dist/admin/lib/zodFields.js.map +1 -1
- package/dist/admin/media/MediaManager.js +7 -7
- package/dist/admin/media/MediaPicker.js +7 -7
- package/dist/admin/media/MediaPicker.js.map +1 -1
- package/dist/admin/pages/PageEditor.d.ts +1 -1
- package/dist/admin/pages/PageEditor.js +13 -13
- package/dist/admin/pages/PagesPanel.js +30 -30
- package/dist/admin/pages/PagesPanel.js.map +1 -1
- package/dist/admin/panelRegistry.js +7 -7
- package/dist/admin/patterns/PatternInserter.d.ts +1 -1
- package/dist/admin/patterns/PatternInserter.js +5 -5
- package/dist/admin/patterns/PatternInserter.js.map +1 -1
- package/dist/admin/settings/MailSettings.js +5 -5
- package/dist/admin/settings/MailTemplates.d.ts +1 -1
- package/dist/admin/settings/MailTemplates.js +5 -5
- package/dist/admin/settings/SettingsPanel.js +20 -20
- package/dist/admin/settings/SettingsPanel.js.map +1 -1
- package/dist/admin/settings/SnapshotsCard.js +5 -5
- package/dist/admin/settings/moduleSettingsForm.d.ts +1 -1
- package/dist/admin/settings/moduleSettingsForm.js +4 -4
- package/dist/admin/settings/moduleSettingsForm.js.map +1 -1
- package/dist/admin/shop/CategoriesTab.js +8 -8
- package/dist/admin/shop/ConnectionsTab.js +3 -3
- package/dist/admin/shop/DiscountsTab.js +8 -8
- package/dist/admin/shop/OrdersTab.js +10 -10
- package/dist/admin/shop/OrdersTab.js.map +1 -1
- package/dist/admin/shop/ProductsTab.js +10 -10
- package/dist/admin/shop/ShopPanel.js +9 -9
- package/dist/admin/shop/ShopSettingsTab.js +4 -4
- package/dist/admin/shop/index.d.ts +2 -2
- package/dist/admin/shop/index.js +2 -2
- package/dist/admin/shop/lib.js +2 -2
- package/dist/admin/stats/StatsPanel.js +2 -2
- package/dist/admin/ui/ArrayEditor.js +1 -1
- package/dist/admin/ui/PanelIcon.d.ts +3 -0
- package/dist/admin/ui/PanelIcon.js +34 -0
- package/dist/admin/ui/PanelIcon.js.map +1 -0
- package/dist/admin/ui/dialog.d.ts +21 -6
- package/dist/admin/ui/dialog.js +55 -34
- package/dist/admin/ui/dialog.js.map +1 -1
- package/dist/admin/ui/index.d.ts +7 -7
- package/dist/admin/ui/index.js +7 -7
- package/dist/admin/ui/primitives.js +1 -1
- package/dist/admin/ui/tabs.js +1 -1
- package/dist/admin/visual/TranslationPanel.d.ts +1 -1
- package/dist/admin/visual/TranslationPanel.js +3 -3
- package/dist/admin/visual/VisualEditor.js +47 -25
- package/dist/admin/visual/VisualEditor.js.map +1 -1
- package/dist/admin.css +2 -1
- package/dist/agentFiles/catalog.d.ts +2 -2
- package/dist/agentFiles/catalog.js +6 -5
- package/dist/agentFiles/catalog.js.map +1 -1
- package/dist/agentFiles/files.d.ts +5 -5
- package/dist/agentFiles/files.js +17 -17
- package/dist/agentFiles/files.js.map +1 -1
- package/dist/all.d.ts +4 -4
- package/dist/all.js +6 -6
- package/dist/auth/AuthProvider.d.ts +2 -2
- package/dist/auth/AuthProvider.js +51 -28
- package/dist/auth/AuthProvider.js.map +1 -1
- package/dist/auth/LoginForm.js +1 -1
- package/dist/auth/roles.js +4 -1
- package/dist/auth/roles.js.map +1 -1
- package/dist/blocks/BlockRenderer.d.ts +1 -1
- package/dist/blocks/BlockRenderer.js +3 -3
- package/dist/blocks/blockStyle.d.ts +1 -1
- package/dist/blocks/builtins/Accordion.d.ts +1 -1
- package/dist/blocks/builtins/Accordion.js +13 -6
- package/dist/blocks/builtins/Accordion.js.map +1 -1
- package/dist/blocks/builtins/Agenda.d.ts +1 -1
- package/dist/blocks/builtins/Agenda.js +5 -5
- package/dist/blocks/builtins/Banner.d.ts +1 -1
- package/dist/blocks/builtins/Banner.js +2 -2
- package/dist/blocks/builtins/BeforeAfter.d.ts +1 -1
- package/dist/blocks/builtins/BeforeAfter.js +4 -4
- package/dist/blocks/builtins/BeforeAfter.js.map +1 -1
- package/dist/blocks/builtins/Booking.d.ts +1 -1
- package/dist/blocks/builtins/Booking.js +5 -5
- package/dist/blocks/builtins/Breadcrumbs.d.ts +1 -1
- package/dist/blocks/builtins/Breadcrumbs.js +3 -3
- package/dist/blocks/builtins/Cards.d.ts +1 -1
- package/dist/blocks/builtins/Cards.js +5 -5
- package/dist/blocks/builtins/Cards.js.map +1 -1
- package/dist/blocks/builtins/Columns.d.ts +1 -1
- package/dist/blocks/builtins/Columns.js +4 -4
- package/dist/blocks/builtins/Contact.d.ts +3 -3
- package/dist/blocks/builtins/Contact.js +4 -4
- package/dist/blocks/builtins/Countdown.d.ts +1 -1
- package/dist/blocks/builtins/Countdown.js +4 -4
- package/dist/blocks/builtins/Countdown.js.map +1 -1
- package/dist/blocks/builtins/Cta.d.ts +1 -1
- package/dist/blocks/builtins/Cta.js +3 -3
- package/dist/blocks/builtins/Downloads.d.ts +1 -1
- package/dist/blocks/builtins/Downloads.js +4 -4
- package/dist/blocks/builtins/Elements.d.ts +3 -3
- package/dist/blocks/builtins/Elements.js +3 -3
- package/dist/blocks/builtins/Embed.d.ts +1 -1
- package/dist/blocks/builtins/Embed.js +3 -3
- package/dist/blocks/builtins/Faq.d.ts +1 -1
- package/dist/blocks/builtins/Faq.js +14 -6
- package/dist/blocks/builtins/Faq.js.map +1 -1
- package/dist/blocks/builtins/FormBlock.d.ts +1 -1
- package/dist/blocks/builtins/FormBlock.js +28 -18
- package/dist/blocks/builtins/FormBlock.js.map +1 -1
- package/dist/blocks/builtins/Gallery.d.ts +2 -2
- package/dist/blocks/builtins/Gallery.js +5 -5
- package/dist/blocks/builtins/Hero.d.ts +1 -1
- package/dist/blocks/builtins/Hero.js +4 -4
- package/dist/blocks/builtins/ImageBlock.d.ts +1 -1
- package/dist/blocks/builtins/ImageBlock.js +3 -3
- package/dist/blocks/builtins/Jobs.d.ts +2 -2
- package/dist/blocks/builtins/Jobs.js +5 -5
- package/dist/blocks/builtins/Layout.d.ts +2 -2
- package/dist/blocks/builtins/Layout.js +4 -4
- package/dist/blocks/builtins/MapBlock.d.ts +1 -1
- package/dist/blocks/builtins/MapBlock.js +3 -3
- package/dist/blocks/builtins/NewsListBlock.d.ts +1 -1
- package/dist/blocks/builtins/NewsListBlock.js +4 -4
- package/dist/blocks/builtins/Newsletter.d.ts +1 -1
- package/dist/blocks/builtins/Newsletter.js +7 -7
- package/dist/blocks/builtins/Newsletter.js.map +1 -1
- package/dist/blocks/builtins/OpenNowBadge.d.ts +1 -1
- package/dist/blocks/builtins/OpenNowBadge.js +8 -3
- package/dist/blocks/builtins/OpenNowBadge.js.map +1 -1
- package/dist/blocks/builtins/OpeningHours.d.ts +1 -1
- package/dist/blocks/builtins/OpeningHours.js +6 -6
- package/dist/blocks/builtins/PdfBlock.d.ts +1 -1
- package/dist/blocks/builtins/PdfBlock.js +3 -3
- package/dist/blocks/builtins/Portfolio.d.ts +1 -1
- package/dist/blocks/builtins/Portfolio.js +5 -5
- package/dist/blocks/builtins/Pricing.d.ts +2 -2
- package/dist/blocks/builtins/Pricing.js +5 -5
- package/dist/blocks/builtins/Pricing.js.map +1 -1
- package/dist/blocks/builtins/RichTextBlock.d.ts +1 -1
- package/dist/blocks/builtins/RichTextBlock.js +2 -2
- package/dist/blocks/builtins/RichTextBlock.js.map +1 -1
- package/dist/blocks/builtins/Search.d.ts +1 -1
- package/dist/blocks/builtins/Search.js +9 -7
- package/dist/blocks/builtins/Search.js.map +1 -1
- package/dist/blocks/builtins/Sections.d.ts +4 -4
- package/dist/blocks/builtins/Sections.js +4 -4
- package/dist/blocks/builtins/Slider.d.ts +1 -1
- package/dist/blocks/builtins/Slider.js +3 -3
- package/dist/blocks/builtins/Spacer.d.ts +1 -1
- package/dist/blocks/builtins/Spacer.js +2 -2
- package/dist/blocks/builtins/SymbolRef.d.ts +1 -1
- package/dist/blocks/builtins/SymbolRef.js +31 -17
- package/dist/blocks/builtins/SymbolRef.js.map +1 -1
- package/dist/blocks/builtins/Team.d.ts +1 -1
- package/dist/blocks/builtins/Team.js +4 -4
- package/dist/blocks/builtins/Testimonials.d.ts +2 -2
- package/dist/blocks/builtins/Testimonials.js +4 -4
- package/dist/blocks/builtins/Video.d.ts +2 -2
- package/dist/blocks/builtins/Video.js +4 -4
- package/dist/blocks/builtins/Video.js.map +1 -1
- package/dist/blocks/builtins/WhatsappChat.d.ts +1 -1
- package/dist/blocks/builtins/WhatsappChat.js +2 -2
- package/dist/blocks/builtins/Widgets.d.ts +12 -12
- package/dist/blocks/builtins/Widgets.js +19 -19
- package/dist/blocks/builtins/Widgets.js.map +1 -1
- package/dist/blocks/builtins/consentEmbed.js +2 -2
- package/dist/blocks/builtins/index.d.ts +40 -40
- package/dist/blocks/builtins/index.js +40 -40
- package/dist/blocks/builtins/lib/submitForm.js +5 -3
- package/dist/blocks/builtins/lib/submitForm.js.map +1 -1
- package/dist/blocks/context.d.ts +1 -1
- package/dist/blocks/customTemplate/definition.d.ts +3 -3
- package/dist/blocks/customTemplate/definition.js +6 -6
- package/dist/blocks/customTemplate/definition.js.map +1 -1
- package/dist/blocks/customTemplate/interpret.js +1 -1
- package/dist/blocks/index.d.ts +5 -5
- package/dist/blocks/index.js +5 -5
- package/dist/blocks/registry.d.ts +17 -7
- package/dist/blocks/registry.js +2 -1
- package/dist/blocks/registry.js.map +1 -1
- package/dist/components/EditBar.js +3 -3
- package/dist/components/FeedbackWidget.js +4 -4
- package/dist/components/News.d.ts +1 -1
- package/dist/components/News.js +50 -37
- package/dist/components/News.js.map +1 -1
- package/dist/components/PageView.js +11 -8
- package/dist/components/PageView.js.map +1 -1
- package/dist/components/ResponsiveImage.d.ts +1 -1
- package/dist/components/site/SiteFooter.d.ts +1 -1
- package/dist/components/site/SiteFooter.js +2 -2
- package/dist/config.d.ts +1 -1
- package/dist/demo/DemoStudio.js +3 -3
- package/dist/demo/demoStore.d.ts +3 -3
- package/dist/demo/index.d.ts +2 -2
- package/dist/demo/index.js +2 -2
- package/dist/editor/EditorProvider.js +1 -1
- package/dist/firebase/admin.d.ts +0 -2
- package/dist/firebase/admin.js +0 -6
- package/dist/firebase/admin.js.map +1 -1
- package/dist/firebase/appcheck.d.ts +1 -1
- package/dist/firebase/appcheck.js +5 -3
- package/dist/firebase/appcheck.js.map +1 -1
- package/dist/firebase/client.d.ts +2 -1
- package/dist/firebase/client.js +2 -14
- package/dist/firebase/client.js.map +1 -1
- package/dist/firebase/config.d.ts +31 -0
- package/dist/firebase/config.js +39 -0
- package/dist/firebase/config.js.map +1 -0
- package/dist/firebase/feedback.js +5 -3
- package/dist/firebase/feedback.js.map +1 -1
- package/dist/i18n/overlay.d.ts +2 -2
- package/dist/i18n/overlay.js +6 -4
- package/dist/i18n/overlay.js.map +1 -1
- package/dist/index.d.ts +45 -44
- package/dist/index.js +35 -33
- package/dist/index.js.map +1 -1
- package/dist/lock/DemoLock.js +1 -1
- package/dist/modules/builtins.d.ts +1 -1
- package/dist/modules/builtins.js +53 -55
- package/dist/modules/builtins.js.map +1 -1
- package/dist/modules/consent/ConsentBanner.js +1 -1
- package/dist/modules/consent/useConsent.js +1 -1
- package/dist/modules/context.d.ts +1 -1
- package/dist/modules/contract.d.ts +1 -1
- package/dist/modules/defineModule.d.ts +1 -1
- package/dist/modules/index.d.ts +9 -9
- package/dist/modules/index.js +8 -8
- package/dist/modules/overrides.d.ts +1 -1
- package/dist/modules/registry.d.ts +2 -7
- package/dist/modules/registry.js +2 -1
- package/dist/modules/registry.js.map +1 -1
- package/dist/modules/shop/blocks/Cart.d.ts +1 -1
- package/dist/modules/shop/blocks/Cart.js +6 -6
- package/dist/modules/shop/blocks/Checkout.d.ts +1 -1
- package/dist/modules/shop/blocks/Checkout.js +6 -6
- package/dist/modules/shop/blocks/Donation.d.ts +1 -1
- package/dist/modules/shop/blocks/Donation.js +5 -5
- package/dist/modules/shop/blocks/OrderStatus.d.ts +1 -1
- package/dist/modules/shop/blocks/OrderStatus.js +5 -5
- package/dist/modules/shop/blocks/ProductDetail.d.ts +1 -1
- package/dist/modules/shop/blocks/ProductDetail.js +8 -8
- package/dist/modules/shop/blocks/ProductGrid.d.ts +3 -3
- package/dist/modules/shop/blocks/ProductGrid.js +7 -7
- package/dist/modules/shop/blocks/ShopCategories.d.ts +1 -1
- package/dist/modules/shop/blocks/ShopCategories.js +6 -6
- package/dist/modules/shop/index.d.ts +5 -5
- package/dist/modules/shop/index.js +5 -5
- package/dist/modules/shop/lib.d.ts +2 -2
- package/dist/modules/shop/lib.js +18 -13
- package/dist/modules/shop/lib.js.map +1 -1
- package/dist/modules/shop/module.d.ts +1 -1
- package/dist/modules/shop/module.js +9 -9
- package/dist/modules/shop/settings.d.ts +18 -50
- package/dist/modules/shop/settings.js +16 -16
- package/dist/modules/shop/settings.js.map +1 -1
- package/dist/modules/shop/strings.js +1 -1
- package/dist/modules/shop/types.d.ts +1 -1
- package/dist/modules/types.d.ts +2 -2
- package/dist/patterns/builtins.d.ts +1 -1
- package/dist/patterns/builtins.js +1 -1
- package/dist/patterns/index.d.ts +2 -2
- package/dist/patterns/index.js +2 -2
- package/dist/render/PageRenderer.d.ts +2 -2
- package/dist/render/PageRenderer.js +1 -1
- package/dist/render/PublishedPage.d.ts +1 -1
- package/dist/render/PublishedPage.js +2 -2
- package/dist/schema/blocks.d.ts +6793 -15095
- package/dist/schema/blocks.js +335 -355
- package/dist/schema/blocks.js.map +1 -1
- package/dist/schema/customBlocks.d.ts +10 -29
- package/dist/schema/customBlocks.js +14 -13
- package/dist/schema/customBlocks.js.map +1 -1
- package/dist/schema/fields.d.ts +10 -16
- package/dist/schema/fields.js +37 -37
- package/dist/schema/fields.js.map +1 -1
- package/dist/schema/forms.d.ts +62 -111
- package/dist/schema/forms.js +17 -17
- package/dist/schema/forms.js.map +1 -1
- package/dist/schema/index.d.ts +7 -7
- package/dist/schema/index.js +7 -7
- package/dist/schema/mail.d.ts +39 -87
- package/dist/schema/mail.js +4 -4
- package/dist/schema/mail.js.map +1 -1
- package/dist/schema/pages.d.ts +152 -674
- package/dist/schema/pages.js +26 -15
- package/dist/schema/pages.js.map +1 -1
- package/dist/schema/seo.d.ts +9 -27
- package/dist/schema/seo.js +7 -7
- package/dist/schema/seo.js.map +1 -1
- package/dist/schema/settings.d.ts +218 -1074
- package/dist/schema/settings.js +55 -55
- package/dist/schema/settings.js.map +1 -1
- package/dist/schema/shop.d.ts +470 -1214
- package/dist/schema/shop.js +124 -124
- package/dist/schema/shop.js.map +1 -1
- package/dist/schema/siteConfig.d.ts +73 -597
- package/dist/schema/siteConfig.js +17 -17
- package/dist/schema/siteConfig.js.map +1 -1
- package/dist/schema/symbols.d.ts +34 -191
- package/dist/schema/symbols.js +8 -8
- package/dist/schema/symbols.js.map +1 -1
- package/dist/server/auth.d.ts +17 -0
- package/dist/server/auth.js +11 -0
- package/dist/server/auth.js.map +1 -0
- package/dist/server/bundle.d.ts +15 -6
- package/dist/server/bundle.js +5 -1
- package/dist/server/bundle.js.map +1 -1
- package/dist/server/draft.d.ts +16 -0
- package/dist/server/draft.js +12 -0
- package/dist/server/draft.js.map +1 -0
- package/dist/server/env.js +8 -8
- package/dist/server/env.js.map +1 -1
- package/dist/server/index.d.ts +10 -8
- package/dist/server/index.js +12 -9
- package/dist/server/index.js.map +1 -1
- package/dist/server/pages.d.ts +14 -7
- package/dist/server/pages.js +183 -17
- package/dist/server/pages.js.map +1 -1
- package/dist/server/revalidate.d.ts +6 -0
- package/dist/server/revalidate.js +40 -0
- package/dist/server/revalidate.js.map +1 -0
- package/dist/server/seo.d.ts +3 -3
- package/dist/server/seo.js +1 -1
- package/dist/server/shop.d.ts +3 -3
- package/dist/server/shop.js +6 -3
- package/dist/server/shop.js.map +1 -1
- package/dist/settings/useSiteSettings.d.ts +2 -2
- package/dist/settings/useSiteSettings.js +48 -28
- package/dist/settings/useSiteSettings.js.map +1 -1
- package/dist/styles.css +4 -1
- package/dist/theme/presets.d.ts +1 -1
- package/package.json +35 -34
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/News.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { collection, getDocs, limit as fbLimit, onSnapshot, orderBy, query, where } from \"firebase/firestore\";\nimport { getDb } from \"../firebase/client\";\nimport { useSiteConfig } from \"../config\";\nimport DOMPurify from \"dompurify\";\nimport { useAuth } from \"../auth/AuthProvider\";\nimport { canEdit } from \"../auth/roles\";\nimport { useBlockData } from \"../blocks/context\";\nimport { isDemoActive, getDemoNews } from \"../demo/demoStore\";\nimport { Img } from \"../media/ImageContext\";\nimport type { NewsArticle } from \"../content/types\";\nimport { jsonLdHtml } from \"../blocks/builtins/jsonld\";\n\n/**\n * Lijst van nieuwsartikelen voor de huidige site.\n * - `live: false` (standaard publiek): één keer ophalen met getDocs → geen per-bezoeker listener\n * (scheelt Firestore-kosten op druk bezochte sites).\n * - `live: true` (admin): realtime via onSnapshot, zodat wijzigingen meteen zichtbaar zijn.\n */\nexport function useNews(opts: { onlyPublished?: boolean; live?: boolean; max?: number } = {}): {\n articles: NewsArticle[];\n loading: boolean;\n} {\n const { siteId } = useSiteConfig();\n // Via useAuth (niet via het deprecated ContentProvider-pad — News hoort niet aan legacy te hangen).\n const { role } = useAuth();\n const editor = canEdit(role);\n // Server-gefetchte nieuwsdata (content-bundle 5.7): als de publieke route deze via\n // <BlockDataProvider data={{ news }}> meegeeft, renderen we daaruit (SSR, geen browser-Firestore).\n // Afwezig (admin/preview/dev, of geen bundle) → val terug op het Firestore-pad hieronder.\n const serverNews = useBlockData<NewsArticle[]>(\"news\");\n const onlyPublished = opts.onlyPublished ?? !editor;\n const live = opts.live ?? editor; // editors zien live; publiek één keer\n // Begrens de query: zonder cap zou een nieuws-rijke site ALLE artikelen downloaden bij elk bezoek.\n const cap = Math.min(opts.max ?? 50, 50);\n // Bundle-data staat al bij de EERSTE render klaar (server + hydration, identiek): de nieuwsinhoud\n // zit zo in de SSR-HTML (SEO) i.p.v. pas na een client-effect (geen \"laden…\"-flits).\n const seed = serverNews ? (onlyPublished ? serverNews.filter((a) => a.published) : serverNews).slice(0, cap) : [];\n const [articles, setArticles] = useState<NewsArticle[]>(seed);\n const [loading, setLoading] = useState(!serverNews);\n\n useEffect(() => {\n // Bundle-data aanwezig? Render daaruit — geen listener, geen getDocs.\n if (serverNews) {\n const all = onlyPublished ? serverNews.filter((a) => a.published) : serverNews;\n setArticles(all.slice(0, cap));\n setLoading(false);\n return;\n }\n // Demo-modus: nieuws komt uit de lokale seed (geen Firebase), zodat nieuws-blokken/widgets\n // in de demo ook gevuld zijn.\n if (isDemoActive()) {\n const all = getDemoNews(siteId);\n setArticles((onlyPublished ? all.filter((a) => a.published) : all).slice(0, cap));\n setLoading(false);\n return;\n }\n const db = getDb();\n if (!db) {\n setLoading(false);\n return;\n }\n const base = collection(db, \"sites\", siteId, \"news\");\n const q = onlyPublished\n ? query(base, where(\"published\", \"==\", true), orderBy(\"date\", \"desc\"), fbLimit(cap))\n : query(base, orderBy(\"date\", \"desc\"), fbLimit(cap));\n const map = (snap: { docs: { id: string; data: () => unknown }[] }) =>\n setArticles(snap.docs.map((d) => ({ id: d.id, ...(d.data() as Omit<NewsArticle, \"id\">) })));\n\n if (!live) {\n let active = true;\n getDocs(q)\n .then((snap) => {\n if (active) {\n map(snap);\n setLoading(false);\n }\n })\n .catch(() => active && setLoading(false));\n return () => {\n active = false;\n };\n }\n\n const unsub = onSnapshot(\n q,\n (snap) => {\n map(snap);\n setLoading(false);\n },\n () => setLoading(false),\n );\n return () => unsub();\n }, [siteId, onlyPublished, live, cap, serverNews]);\n\n return { articles, loading };\n}\n\ninterface NewsListProps {\n /** Basispad voor links naar een artikel, bv. \"/nieuws\". */\n basePath?: string;\n className?: string;\n emptyLabel?: string;\n /** Maximaal aantal artikelen om te tonen. */\n limit?: number;\n /** Toon eerst dit aantal en laad de rest met een \"Meer laden\"-knop (0/leeg = alles ineens). */\n pageSize?: number;\n}\n\n/** Standaardweergave van nieuwsartikelen als kaartenlijst, optioneel gepagineerd. */\nexport function NewsList({ basePath = \"/nieuws\", className, emptyLabel = \"Nog geen nieuws.\", limit, pageSize }: NewsListProps) {\n const { articles, loading } = useNews({ max: limit });\n const paged = typeof pageSize === \"number\" && pageSize > 0;\n const [visible, setVisible] = useState(paged ? pageSize : Infinity);\n\n if (loading) return <p>Nieuws laden…</p>;\n if (articles.length === 0) return <p>{emptyLabel}</p>;\n\n const all = typeof limit === \"number\" ? articles.slice(0, limit) : articles;\n const shown = paged ? all.slice(0, visible) : all;\n\n return (\n <div className={className} style={{ display: \"grid\", gap: \"1rem\" }}>\n {shown.map((a, i) => (\n <article key={a.id} className=\"xicms-card\" style={{ padding: \"1.25rem\" }}>\n {a.coverUrl && (\n // Eerste cover boven de vouw = vaak de LCP → priority (geen lazy-load-vertraging).\n <Img src={a.coverUrl} alt=\"\" width={1200} height={700} sizes=\"(max-width: 720px) 100vw, 33vw\" priority={i === 0} style={{ width: \"100%\", height: \"auto\", borderRadius: 8, marginBottom: \"0.75rem\" }} />\n )}\n <p style={{ fontSize: 12, opacity: 0.7, margin: 0 }}>{a.date}</p>\n {/* h2: de lijst staat direct onder de pagina-h1 — h3 zou een kopniveau overslaan. */}\n <h2 style={{ margin: \"0.25rem 0\", fontSize: \"1.35rem\" }}>\n <a href={`${basePath}/${a.slug ?? a.id}`}>{a.title}</a>\n {!a.published && <span style={{ marginLeft: 8, fontSize: 12, color: \"#b45309\" }}>(concept)</span>}\n </h2>\n {a.excerpt && <p style={{ margin: 0, opacity: 0.85 }}>{a.excerpt}</p>}\n </article>\n ))}\n {paged && shown.length < all.length && (\n <div style={{ textAlign: \"center\" }}>\n <button type=\"button\" className=\"xicms-btn\" onClick={() => setVisible((v) => v + (pageSize as number))}>\n Meer laden ({all.length - shown.length})\n </button>\n </div>\n )}\n </div>\n );\n}\n\n/** BlogPosting-JSON-LD voor een artikel — puur, zodat het testbaar is. */\nexport function articleBlogPostingJsonLd(article: NewsArticle, opts: { siteName?: string; url?: string } = {}): Record<string, unknown> {\n return {\n \"@context\": \"https://schema.org\",\n \"@type\": \"BlogPosting\",\n headline: article.title,\n datePublished: article.date,\n ...(article.excerpt ? { description: article.excerpt } : {}),\n ...(article.coverUrl ? { image: article.coverUrl } : {}),\n ...(opts.url ? { url: opts.url, mainEntityOfPage: opts.url } : {}),\n ...(opts.siteName ? { publisher: { \"@type\": \"Organization\", name: opts.siteName } } : {}),\n };\n}\n\n/** Detailweergave van één artikel, met BlogPosting-JSON-LD voor zoekmachines. */\nexport function NewsArticleView({ article, jsonLd = true }: { article: NewsArticle; jsonLd?: boolean }) {\n const { name } = useSiteConfig();\n // Rich-text wordt bij het RENDEREN gesanitized (DOMPurify) — de render is de veiligheidsgrens,\n // ook als er ooit ongesaneerde HTML in Firestore zou belanden. DOMPurify heeft echter een echte\n // DOM nodig: op de server (SSR/prerender) is sanitize geen functie. Daarom rendert de eerste\n // render (server én client-hydration, identiek) de platte-tekst-fallback en wisselt het effect\n // — client-only — naar de gesanitizede rich-text.\n const [html, setHtml] = useState<string | null>(null);\n useEffect(() => {\n setHtml(article.bodyHtml ? DOMPurify.sanitize(article.bodyHtml) : null);\n }, [article.bodyHtml]);\n // Fallback tot de sanitizer klaar is: kale tekst (tags gestript, gerenderd als tekstnodes — veilig).\n const fallbackText = article.body ?? (article.bodyHtml ? article.bodyHtml.replace(/<[^>]*>/g, \" \") : \"\");\n return (\n <article>\n <p style={{ fontSize: 12, opacity: 0.6 }}>{article.date}</p>\n <h1>{article.title}</h1>\n {article.coverUrl && (\n <Img src={article.coverUrl} alt=\"\" width={1200} height={700} sizes=\"(max-width: 900px) 100vw, 800px\" priority style={{ width: \"100%\", height: \"auto\", borderRadius: 12, margin: \"1rem 0\" }} />\n )}\n {html ? (\n // eslint-disable-next-line react/no-danger\n <div className=\"xicms-richtext\" dangerouslySetInnerHTML={{ __html: html }} />\n ) : (\n fallbackText.split(/\\n{2,}/).map((p, i) => (\n <p key={i} style={{ whiteSpace: \"pre-wrap\" }}>\n {p}\n </p>\n ))\n )}\n {jsonLd && (\n <script\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{ __html: jsonLdHtml(articleBlogPostingJsonLd(article, { siteName: name })) }}\n />\n )}\n </article>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/News.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { useSiteConfig } from \"../config\";\nimport DOMPurify from \"dompurify\";\nimport { useAuth } from \"../auth/AuthProvider\";\nimport { canEdit } from \"../auth/roles\";\nimport { useBlockData } from \"../blocks/context\";\nimport { isDemoActive, getDemoNews } from \"../demo/demoStore\";\nimport { Img } from \"../media/ImageContext\";\nimport type { NewsArticle } from \"../content/types\";\nimport { jsonLdHtml } from \"../blocks/builtins/jsonld\";\n\n/**\n * Lijst van nieuwsartikelen voor de huidige site.\n * - `live: false` (standaard publiek): één keer ophalen met getDocs → geen per-bezoeker listener\n * (scheelt Firestore-kosten op druk bezochte sites).\n * - `live: true` (admin): realtime via onSnapshot, zodat wijzigingen meteen zichtbaar zijn.\n */\nexport function useNews(opts: { onlyPublished?: boolean; live?: boolean; max?: number } = {}): {\n articles: NewsArticle[];\n loading: boolean;\n} {\n const { siteId } = useSiteConfig();\n // Via useAuth (niet via het deprecated ContentProvider-pad — News hoort niet aan legacy te hangen).\n const { role } = useAuth();\n const editor = canEdit(role);\n // Server-gefetchte nieuwsdata (content-bundle 5.7): als de publieke route deze via\n // <BlockDataProvider data={{ news }}> meegeeft, renderen we daaruit (SSR, geen browser-Firestore).\n // Afwezig (admin/preview/dev, of geen bundle) → val terug op het Firestore-pad hieronder.\n const serverNews = useBlockData<NewsArticle[]>(\"news\");\n const onlyPublished = opts.onlyPublished ?? !editor;\n const live = opts.live ?? editor; // editors zien live; publiek één keer\n // Begrens de query: zonder cap zou een nieuws-rijke site ALLE artikelen downloaden bij elk bezoek.\n const cap = Math.min(opts.max ?? 50, 50);\n // Bundle-data staat al bij de EERSTE render klaar (server + hydration, identiek): de nieuwsinhoud\n // zit zo in de SSR-HTML (SEO) i.p.v. pas na een client-effect (geen \"laden…\"-flits).\n const seed = serverNews ? (onlyPublished ? serverNews.filter((a) => a.published) : serverNews).slice(0, cap) : [];\n const [articles, setArticles] = useState<NewsArticle[]>(seed);\n const [loading, setLoading] = useState(!serverNews);\n\n useEffect(() => {\n // Bundle-data aanwezig? Render daaruit — geen listener, geen getDocs.\n if (serverNews) {\n const all = onlyPublished ? serverNews.filter((a) => a.published) : serverNews;\n setArticles(all.slice(0, cap));\n setLoading(false);\n return;\n }\n // Demo-modus: nieuws komt uit de lokale seed (geen Firebase), zodat nieuws-blokken/widgets\n // in de demo ook gevuld zijn.\n if (isDemoActive()) {\n const all = getDemoNews(siteId);\n setArticles((onlyPublished ? all.filter((a) => a.published) : all).slice(0, cap));\n setLoading(false);\n return;\n }\n // Firestore DYNAMISCH. Met bundle-data (hierboven) komen we hier niet eens; zonder bundle\n // laadt de SDK pas wanneer er echt nieuws opgehaald moet worden. Statisch importeren zette hem\n // in de bundel van élke publieke pagina, ook die zonder nieuws.\n let afgebroken = false;\n let unsub: (() => void) | undefined;\n void (async () => {\n const [{ getDb }, fs] = await Promise.all([\n import(\"../firebase/client\"),\n import(\"firebase/firestore\"),\n ]);\n const db = getDb();\n if (!db || afgebroken) {\n setLoading(false);\n return;\n }\n const base = fs.collection(db, \"sites\", siteId, \"news\");\n const q = onlyPublished\n ? fs.query(base, fs.where(\"published\", \"==\", true), fs.orderBy(\"date\", \"desc\"), fs.limit(cap))\n : fs.query(base, fs.orderBy(\"date\", \"desc\"), fs.limit(cap));\n const map = (snap: { docs: { id: string; data: () => unknown }[] }) =>\n setArticles(snap.docs.map((d) => ({ id: d.id, ...(d.data() as Omit<NewsArticle, \"id\">) })));\n\n if (!live) {\n try {\n const snap = await fs.getDocs(q);\n if (!afgebroken) {\n map(snap);\n setLoading(false);\n }\n } catch {\n if (!afgebroken) setLoading(false);\n }\n return;\n }\n unsub = fs.onSnapshot(\n q,\n (snap) => {\n map(snap);\n setLoading(false);\n },\n () => setLoading(false),\n );\n })();\n return () => {\n afgebroken = true;\n unsub?.();\n };\n }, [siteId, onlyPublished, live, cap, serverNews]);\n\n return { articles, loading };\n}\n\ninterface NewsListProps {\n /** Basispad voor links naar een artikel, bv. \"/nieuws\". */\n basePath?: string;\n className?: string;\n emptyLabel?: string;\n /** Maximaal aantal artikelen om te tonen. */\n limit?: number;\n /** Toon eerst dit aantal en laad de rest met een \"Meer laden\"-knop (0/leeg = alles ineens). */\n pageSize?: number;\n}\n\n/** Standaardweergave van nieuwsartikelen als kaartenlijst, optioneel gepagineerd. */\nexport function NewsList({ basePath = \"/nieuws\", className, emptyLabel = \"Nog geen nieuws.\", limit, pageSize }: NewsListProps) {\n const { articles, loading } = useNews({ max: limit });\n const paged = typeof pageSize === \"number\" && pageSize > 0;\n const [visible, setVisible] = useState(paged ? pageSize : Infinity);\n\n if (loading) return <p>Nieuws laden…</p>;\n if (articles.length === 0) return <p>{emptyLabel}</p>;\n\n const all = typeof limit === \"number\" ? articles.slice(0, limit) : articles;\n const shown = paged ? all.slice(0, visible) : all;\n\n return (\n <div className={className} style={{ display: \"grid\", gap: \"1rem\" }}>\n {shown.map((a, i) => (\n <article key={a.id} className=\"xicms-card\" style={{ padding: \"1.25rem\" }}>\n {a.coverUrl && (\n // Eerste cover boven de vouw = vaak de LCP → priority (geen lazy-load-vertraging).\n <Img src={a.coverUrl} alt=\"\" width={1200} height={700} sizes=\"(max-width: 720px) 100vw, 33vw\" priority={i === 0} style={{ width: \"100%\", height: \"auto\", borderRadius: 8, marginBottom: \"0.75rem\" }} />\n )}\n <p style={{ fontSize: 12, opacity: 0.7, margin: 0 }}>{a.date}</p>\n {/* h2: de lijst staat direct onder de pagina-h1 — h3 zou een kopniveau overslaan. */}\n <h2 style={{ margin: \"0.25rem 0\", fontSize: \"1.35rem\" }}>\n <a href={`${basePath}/${a.slug ?? a.id}`}>{a.title}</a>\n {!a.published && <span style={{ marginLeft: 8, fontSize: 12, color: \"#b45309\" }}>(concept)</span>}\n </h2>\n {a.excerpt && <p style={{ margin: 0, opacity: 0.85 }}>{a.excerpt}</p>}\n </article>\n ))}\n {paged && shown.length < all.length && (\n <div style={{ textAlign: \"center\" }}>\n <button type=\"button\" className=\"xicms-btn\" onClick={() => setVisible((v) => v + (pageSize as number))}>\n Meer laden ({all.length - shown.length})\n </button>\n </div>\n )}\n </div>\n );\n}\n\n/** BlogPosting-JSON-LD voor een artikel — puur, zodat het testbaar is. */\nexport function articleBlogPostingJsonLd(article: NewsArticle, opts: { siteName?: string; url?: string } = {}): Record<string, unknown> {\n return {\n \"@context\": \"https://schema.org\",\n \"@type\": \"BlogPosting\",\n headline: article.title,\n datePublished: article.date,\n ...(article.excerpt ? { description: article.excerpt } : {}),\n ...(article.coverUrl ? { image: article.coverUrl } : {}),\n ...(opts.url ? { url: opts.url, mainEntityOfPage: opts.url } : {}),\n ...(opts.siteName ? { publisher: { \"@type\": \"Organization\", name: opts.siteName } } : {}),\n };\n}\n\n/** Detailweergave van één artikel, met BlogPosting-JSON-LD voor zoekmachines. */\nexport function NewsArticleView({ article, jsonLd = true }: { article: NewsArticle; jsonLd?: boolean }) {\n const { name } = useSiteConfig();\n // De render is de veiligheidsgrens: rich-text gaat door DOMPurify. Die heeft echter een echte DOM\n // nodig, dus op de server is hij geen optie — waardoor de opmaak vroeger pas ná hydratie verscheen.\n // Een crawler zag dan alleen kale tekst, en de lezer een layoutverschuiving.\n //\n // Komt het artikel uit de content-bundle (server-side meegegeven via BlockDataProvider), dan is\n // het al server-side gesaneerd in `getBundleNews` en mogen we het direct renderen — óók in de\n // SSR-HTML. Alleen client-gefetchte artikelen (admin/preview/dev, of geen bundle) wachten nog op\n // DOMPurify; die kunnen immers ongesaneerd uit Firestore komen.\n const serverNews = useBlockData<NewsArticle[]>(\"news\");\n const trusted = Boolean(\n article.bodyHtml && serverNews?.some((a) => a.slug === article.slug && a.bodyHtml === article.bodyHtml),\n );\n const [html, setHtml] = useState<string | null>(trusted ? (article.bodyHtml ?? null) : null);\n useEffect(() => {\n // Ook voor vertrouwde HTML: opnieuw saneren is idempotent en houdt de client-grens hard.\n setHtml(article.bodyHtml ? DOMPurify.sanitize(article.bodyHtml) : null);\n }, [article.bodyHtml]);\n // Fallback tot de sanitizer klaar is: kale tekst (tags gestript, gerenderd als tekstnodes — veilig).\n const fallbackText = article.body ?? (article.bodyHtml ? article.bodyHtml.replace(/<[^>]*>/g, \" \") : \"\");\n return (\n <article>\n <p style={{ fontSize: 12, opacity: 0.6 }}>{article.date}</p>\n <h1>{article.title}</h1>\n {article.coverUrl && (\n <Img src={article.coverUrl} alt=\"\" width={1200} height={700} sizes=\"(max-width: 900px) 100vw, 800px\" priority style={{ width: \"100%\", height: \"auto\", borderRadius: 12, margin: \"1rem 0\" }} />\n )}\n {html ? (\n // eslint-disable-next-line react/no-danger\n <div className=\"xicms-richtext\" dangerouslySetInnerHTML={{ __html: html }} />\n ) : (\n fallbackText.split(/\\n{2,}/).map((p, i) => (\n <p key={i} style={{ whiteSpace: \"pre-wrap\" }}>\n {p}\n </p>\n ))\n )}\n {jsonLd && (\n <script\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{ __html: jsonLdHtml(articleBlogPostingJsonLd(article, { siteName: name })) }}\n />\n )}\n </article>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useEffect } from "react";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { useSiteConfig } from "../config";
|
|
3
|
+
import { functionsRegion } from "../firebase/region.js";
|
|
4
|
+
import { useSiteConfig } from "../config.js";
|
|
6
5
|
function PageView({ path }) {
|
|
7
6
|
const { siteId } = useSiteConfig();
|
|
8
7
|
useEffect(() => {
|
|
@@ -13,12 +12,16 @@ function PageView({ path }) {
|
|
|
13
12
|
sessionStorage.setItem(key, "1");
|
|
14
13
|
} catch {
|
|
15
14
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
void (async () => {
|
|
16
|
+
const [{ getFirebaseApp }, { getFunctions, httpsCallable }] = await Promise.all([
|
|
17
|
+
import("../firebase/client.js"),
|
|
18
|
+
import("firebase/functions")
|
|
19
|
+
]);
|
|
20
|
+
const app = getFirebaseApp();
|
|
21
|
+
if (!app) return;
|
|
19
22
|
const fn = httpsCallable(getFunctions(app, functionsRegion()), "trackView");
|
|
20
|
-
|
|
21
|
-
}).catch(() => {
|
|
23
|
+
await fn({ siteId, path });
|
|
24
|
+
})().catch(() => {
|
|
22
25
|
});
|
|
23
26
|
}, [siteId, path]);
|
|
24
27
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/PageView.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect } from \"react\";\nimport {
|
|
1
|
+
{"version":3,"sources":["../../src/components/PageView.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect } from \"react\";\nimport { functionsRegion } from \"../firebase/region\";\nimport { useSiteConfig } from \"../config\";\n\n/**\n * Registreert één pageview voor de huidige site/pad — max. één keer per browsersessie per pad\n * (sessionStorage). Privacy-vriendelijk en goedkoop. No-op zonder Firebase. Plaats dit in de\n * publieke layout van een klantsite (binnen <XiCMSProvider>).\n */\nexport function PageView({ path }: { path: string }) {\n const { siteId } = useSiteConfig();\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const key = `xicms_view:${siteId}:${path}`;\n try {\n if (sessionStorage.getItem(key)) return;\n sessionStorage.setItem(key, \"1\");\n } catch {\n /* private mode e.d. — ga gewoon door */\n }\n // Firebase pas laden wanneer er écht getracked wordt: statisch importeren zette de hele SDK\n // in de bundel van elke publieke pagina, voor één fire-and-forget-call.\n void (async () => {\n const [{ getFirebaseApp }, { getFunctions, httpsCallable }] = await Promise.all([\n import(\"../firebase/client\"),\n import(\"firebase/functions\"),\n ]);\n const app = getFirebaseApp();\n if (!app) return;\n const fn = httpsCallable(getFunctions(app, functionsRegion()), \"trackView\");\n await fn({ siteId, path });\n })().catch(() => {\n /* stats mogen nooit de site breken */\n });\n }, [siteId, path]);\n\n return null;\n}\n"],"mappings":";AAEA,SAAS,iBAAiB;AAC1B,SAAS,uBAAuB;AAChC,SAAS,qBAAqB;AAOvB,SAAS,SAAS,EAAE,KAAK,GAAqB;AACnD,QAAM,EAAE,OAAO,IAAI,cAAc;AAEjC,YAAU,MAAM;AACd,QAAI,OAAO,WAAW,YAAa;AACnC,UAAM,MAAM,cAAc,MAAM,IAAI,IAAI;AACxC,QAAI;AACF,UAAI,eAAe,QAAQ,GAAG,EAAG;AACjC,qBAAe,QAAQ,KAAK,GAAG;AAAA,IACjC,QAAQ;AAAA,IAER;AAGA,UAAM,YAAY;AAChB,YAAM,CAAC,EAAE,eAAe,GAAG,EAAE,cAAc,cAAc,CAAC,IAAI,MAAM,QAAQ,IAAI;AAAA,QAC9E,OAAO,oBAAoB;AAAA,QAC3B,OAAO,oBAAoB;AAAA,MAC7B,CAAC;AACD,YAAM,MAAM,eAAe;AAC3B,UAAI,CAAC,IAAK;AACV,YAAM,KAAK,cAAc,aAAa,KAAK,gBAAgB,CAAC,GAAG,WAAW;AAC1E,YAAM,GAAG,EAAE,QAAQ,KAAK,CAAC;AAAA,IAC3B,GAAG,EAAE,MAAM,MAAM;AAAA,IAEjB,CAAC;AAAA,EACH,GAAG,CAAC,QAAQ,IAAI,CAAC;AAEjB,SAAO;AACT;","names":[]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CSSProperties } from "react";
|
|
2
|
-
import type { MediaItem } from "../content/types";
|
|
2
|
+
import type { MediaItem } from "../content/types.js";
|
|
3
3
|
/**
|
|
4
4
|
* Rendert een afbeelding met srcset/webp uit de door de pipeline gegenereerde varianten.
|
|
5
5
|
* Server-safe. Valt terug op de originele `url` als er nog geen varianten zijn.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
-
import type { FooterSettings } from "../../schema/settings";
|
|
2
|
+
import type { FooterSettings } from "../../schema/settings.js";
|
|
3
3
|
/**
|
|
4
4
|
* Door de klant in /beheer bewerkbare footer. Unstyled-by-default: alle opmaak hangt aan
|
|
5
5
|
* `xicms-footer*`-classNames + thema-CSS-variabelen, zodat een site hem volledig kan herstijlen
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useSiteSettings } from "../../settings/useSiteSettings";
|
|
4
|
-
import { useSiteConfig } from "../../config";
|
|
3
|
+
import { useSiteSettings } from "../../settings/useSiteSettings.js";
|
|
4
|
+
import { useSiteConfig } from "../../config.js";
|
|
5
5
|
function SiteFooter({
|
|
6
6
|
className,
|
|
7
7
|
renderColumn
|
package/dist/config.d.ts
CHANGED
package/dist/demo/DemoStudio.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useState } from "react";
|
|
4
|
-
import { useAuth } from "../auth/AuthProvider";
|
|
5
|
-
import { useSiteConfig } from "../config";
|
|
6
|
-
import { DEMO_EVENT, isDemoLoggedIn, resetDemo } from "./demoStore";
|
|
4
|
+
import { useAuth } from "../auth/AuthProvider.js";
|
|
5
|
+
import { useSiteConfig } from "../config.js";
|
|
6
|
+
import { DEMO_EVENT, isDemoLoggedIn, resetDemo } from "./demoStore.js";
|
|
7
7
|
function DemoStudio() {
|
|
8
8
|
const { demo, demoSignIn } = useAuth();
|
|
9
9
|
const { siteId } = useSiteConfig();
|
package/dist/demo/demoStore.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { Page } from "../schema/pages";
|
|
2
|
-
import type { MediaItem, NewsArticle } from "../content/types";
|
|
3
|
-
import type { FormDefinition } from "../schema/forms";
|
|
1
|
+
import type { Page } from "../schema/pages.js";
|
|
2
|
+
import type { MediaItem, NewsArticle } from "../content/types.js";
|
|
3
|
+
import type { FormDefinition } from "../schema/forms.js";
|
|
4
4
|
export declare const DEMO_EVENT = "xicms:demo";
|
|
5
5
|
/** Zet de demo-bewerkmodus aan/uit (door de provider, op basis van config.demoEditing). */
|
|
6
6
|
export declare function setDemoActive(on: boolean): void;
|
package/dist/demo/index.d.ts
CHANGED
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
* Alleen voor showcase-/demo-sites (XiCMS.nl): seed registreren, de lokaal-bewerkte pagina
|
|
4
4
|
* resolven en demo-nieuws/-formulieren/-media aanbieden. Klantsites importeren dit nooit.
|
|
5
5
|
*/
|
|
6
|
-
export { registerSeed, resolveDemoPage, loadDemoDraft, demoIdForPath, registerDemoNews, registerDemoForm, registerDemoMedia, DEMO_EVENT, } from "./demoStore";
|
|
7
|
-
export { DemoStudio } from "./DemoStudio";
|
|
6
|
+
export { registerSeed, resolveDemoPage, loadDemoDraft, demoIdForPath, registerDemoNews, registerDemoForm, registerDemoMedia, DEMO_EVENT, } from "./demoStore.js";
|
|
7
|
+
export { DemoStudio } from "./DemoStudio.js";
|
package/dist/demo/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { createContext, useContext, useState, lazy, Suspense } from "react";
|
|
4
|
-
const VisualEditor = lazy(() => import("../admin/visual/VisualEditor"));
|
|
4
|
+
const VisualEditor = lazy(() => import("../admin/visual/VisualEditor.js"));
|
|
5
5
|
const Ctx = createContext({ open: false, openEditor: () => {
|
|
6
6
|
}, closeEditor: () => {
|
|
7
7
|
} });
|
package/dist/firebase/admin.d.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import "server-only";
|
|
2
2
|
import { type App } from "firebase-admin/app";
|
|
3
|
-
import { type Auth } from "firebase-admin/auth";
|
|
4
3
|
import { type Firestore } from "firebase-admin/firestore";
|
|
5
4
|
export declare function getAdminApp(): App | null;
|
|
6
|
-
export declare function getAdminAuth(): Auth | null;
|
|
7
5
|
export declare function getAdminDb(): Firestore | null;
|
package/dist/firebase/admin.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import "server-only";
|
|
2
2
|
import { cert, getApp, getApps, initializeApp } from "firebase-admin/app";
|
|
3
|
-
import { getAuth } from "firebase-admin/auth";
|
|
4
3
|
import { getFirestore } from "firebase-admin/firestore";
|
|
5
4
|
function readServiceAccount() {
|
|
6
5
|
const projectId = process.env.FIREBASE_PROJECT_ID;
|
|
@@ -15,17 +14,12 @@ function getAdminApp() {
|
|
|
15
14
|
if (!sa) return null;
|
|
16
15
|
return initializeApp({ credential: cert(sa) });
|
|
17
16
|
}
|
|
18
|
-
function getAdminAuth() {
|
|
19
|
-
const app = getAdminApp();
|
|
20
|
-
return app ? getAuth(app) : null;
|
|
21
|
-
}
|
|
22
17
|
function getAdminDb() {
|
|
23
18
|
const app = getAdminApp();
|
|
24
19
|
return app ? getFirestore(app) : null;
|
|
25
20
|
}
|
|
26
21
|
export {
|
|
27
22
|
getAdminApp,
|
|
28
|
-
getAdminAuth,
|
|
29
23
|
getAdminDb
|
|
30
24
|
};
|
|
31
25
|
//# sourceMappingURL=admin.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/firebase/admin.ts"],"sourcesContent":["import \"server-only\";\n\nimport { cert, getApp, getApps, initializeApp, type App } from \"firebase-admin/app\";\nimport {
|
|
1
|
+
{"version":3,"sources":["../../src/firebase/admin.ts"],"sourcesContent":["import \"server-only\";\n\nimport { cert, getApp, getApps, initializeApp, type App } from \"firebase-admin/app\";\nimport { getFirestore, type Firestore } from \"firebase-admin/firestore\";\n\n/**\n * Server-side Firebase Admin (alleen voor server actions / route handlers).\n * NOOIT importeren in client-componenten — importeer dit via \"@xiwebdesign/xicms-core/server\".\n *\n * BEWUST ZONDER `firebase-admin/auth` — die leeft in ./adminAuth.ts. Reden: `server/pages.ts`\n * importeert `getAdminDb` voor élke publieke pagina, en zolang auth in ditzelfde bestand stond\n * belandde de hele auth-tak in de route-bundel van iedere site. Dat is niet alleen ballast maar\n * FATAAL op Vercel: firebase-admin/auth trekt jwks-rsa aan, die `require(\"jose\")` doet terwijl\n * jose 6 puur ESM is. Turbopack laadt externals strikt als CJS, dus élke server-render van een\n * publieke pagina eindigde in ERR_REQUIRE_ESM → HTTP 500. Geprerenderde pagina's overleefden dat\n * (die draaien niet op de server), waardoor het pas opvalt zodra er echte CMS-content is.\n *\n * Credentials komen uit env:\n * FIREBASE_PROJECT_ID\n * FIREBASE_CLIENT_EMAIL\n * FIREBASE_PRIVATE_KEY (met letterlijke \\n; die vervangen we hieronder)\n */\nfunction readServiceAccount() {\n const projectId = process.env.FIREBASE_PROJECT_ID;\n const clientEmail = process.env.FIREBASE_CLIENT_EMAIL;\n const privateKey = process.env.FIREBASE_PRIVATE_KEY?.replace(/\\\\n/g, \"\\n\");\n if (!projectId || !clientEmail || !privateKey) return null;\n return { projectId, clientEmail, privateKey };\n}\n\nexport function getAdminApp(): App | null {\n if (getApps().length) return getApp();\n const sa = readServiceAccount();\n if (!sa) return null;\n return initializeApp({ credential: cert(sa) });\n}\n\nexport function getAdminDb(): Firestore | null {\n const app = getAdminApp();\n return app ? getFirestore(app) : null;\n}\n"],"mappings":"AAAA,OAAO;AAEP,SAAS,MAAM,QAAQ,SAAS,qBAA+B;AAC/D,SAAS,oBAAoC;AAmB7C,SAAS,qBAAqB;AAC5B,QAAM,YAAY,QAAQ,IAAI;AAC9B,QAAM,cAAc,QAAQ,IAAI;AAChC,QAAM,aAAa,QAAQ,IAAI,sBAAsB,QAAQ,QAAQ,IAAI;AACzE,MAAI,CAAC,aAAa,CAAC,eAAe,CAAC,WAAY,QAAO;AACtD,SAAO,EAAE,WAAW,aAAa,WAAW;AAC9C;AAEO,SAAS,cAA0B;AACxC,MAAI,QAAQ,EAAE,OAAQ,QAAO,OAAO;AACpC,QAAM,KAAK,mBAAmB;AAC9B,MAAI,CAAC,GAAI,QAAO;AAChB,SAAO,cAAc,EAAE,YAAY,KAAK,EAAE,EAAE,CAAC;AAC/C;AAEO,SAAS,aAA+B;AAC7C,QAAM,MAAM,YAAY;AACxB,SAAO,MAAM,aAAa,GAAG,IAAI;AACnC;","names":[]}
|
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
* Zo blijven lokaal draaien en de demo zonder App Check werken (env-gated, net als de rest
|
|
4
4
|
* van de Firebase-config). Roep dit één keer aan vanuit de client (XiCMSProvider doet dit).
|
|
5
5
|
*/
|
|
6
|
-
export declare function initAppCheck(): void
|
|
6
|
+
export declare function initAppCheck(): Promise<void>;
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { initializeAppCheck, ReCaptchaV3Provider } from "firebase/app-check";
|
|
3
|
-
import { getFirebaseApp } from "./client";
|
|
4
2
|
let inited = false;
|
|
5
|
-
function initAppCheck() {
|
|
3
|
+
async function initAppCheck() {
|
|
6
4
|
if (inited || typeof window === "undefined") return;
|
|
7
5
|
const key = process.env.NEXT_PUBLIC_FIREBASE_APPCHECK_KEY;
|
|
8
6
|
if (!key) return;
|
|
7
|
+
const [{ getFirebaseApp }, { initializeAppCheck, ReCaptchaV3Provider }] = await Promise.all([
|
|
8
|
+
import("./client.js"),
|
|
9
|
+
import("firebase/app-check")
|
|
10
|
+
]);
|
|
9
11
|
const app = getFirebaseApp();
|
|
10
12
|
if (!app) return;
|
|
11
13
|
if (process.env.NEXT_PUBLIC_FIREBASE_APPCHECK_DEBUG === "true") {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/firebase/appcheck.ts"],"sourcesContent":["\"use client\";\n\
|
|
1
|
+
{"version":3,"sources":["../../src/firebase/appcheck.ts"],"sourcesContent":["\"use client\";\n\n\nlet inited = false;\n\n/**\n * Initialiseert Firebase App Check — alleen als `NEXT_PUBLIC_FIREBASE_APPCHECK_KEY` is gezet.\n * Zo blijven lokaal draaien en de demo zonder App Check werken (env-gated, net als de rest\n * van de Firebase-config). Roep dit één keer aan vanuit de client (XiCMSProvider doet dit).\n */\nexport async function initAppCheck(): Promise<void> {\n if (inited || typeof window === \"undefined\") return;\n const key = process.env.NEXT_PUBLIC_FIREBASE_APPCHECK_KEY;\n if (!key) return;\n // SDK dynamisch: App Check hoort bij het praten mét Firebase, en dat doet een bezoeker niet.\n const [{ getFirebaseApp }, { initializeAppCheck, ReCaptchaV3Provider }] = await Promise.all([\n import(\"./client\"),\n import(\"firebase/app-check\"),\n ]);\n const app = getFirebaseApp();\n if (!app) return;\n\n // Debug-token voor lokaal/emulator (zet NEXT_PUBLIC_FIREBASE_APPCHECK_DEBUG=true).\n if (process.env.NEXT_PUBLIC_FIREBASE_APPCHECK_DEBUG === \"true\") {\n (globalThis as unknown as { FIREBASE_APPCHECK_DEBUG_TOKEN?: boolean }).FIREBASE_APPCHECK_DEBUG_TOKEN = true;\n }\n\n initializeAppCheck(app, {\n provider: new ReCaptchaV3Provider(key),\n isTokenAutoRefreshEnabled: true,\n });\n inited = true;\n}\n"],"mappings":";AAGA,IAAI,SAAS;AAOb,eAAsB,eAA8B;AAClD,MAAI,UAAU,OAAO,WAAW,YAAa;AAC7C,QAAM,MAAM,QAAQ,IAAI;AACxB,MAAI,CAAC,IAAK;AAEV,QAAM,CAAC,EAAE,eAAe,GAAG,EAAE,oBAAoB,oBAAoB,CAAC,IAAI,MAAM,QAAQ,IAAI;AAAA,IAC1F,OAAO,UAAU;AAAA,IACjB,OAAO,oBAAoB;AAAA,EAC7B,CAAC;AACD,QAAM,MAAM,eAAe;AAC3B,MAAI,CAAC,IAAK;AAGV,MAAI,QAAQ,IAAI,wCAAwC,QAAQ;AAC9D,IAAC,WAAsE,gCAAgC;AAAA,EACzG;AAEA,qBAAmB,KAAK;AAAA,IACtB,UAAU,IAAI,oBAAoB,GAAG;AAAA,IACrC,2BAA2B;AAAA,EAC7B,CAAC;AACD,WAAS;AACX;","names":[]}
|
|
@@ -2,7 +2,8 @@ import { type FirebaseApp } from "firebase/app";
|
|
|
2
2
|
import { type Auth } from "firebase/auth";
|
|
3
3
|
import { type Firestore } from "firebase/firestore";
|
|
4
4
|
import { type FirebaseStorage } from "firebase/storage";
|
|
5
|
-
|
|
5
|
+
import { isFirebaseConfigured } from "./config.js";
|
|
6
|
+
export { isFirebaseConfigured };
|
|
6
7
|
export declare function getFirebaseApp(): FirebaseApp | null;
|
|
7
8
|
export declare function getFirebaseAuth(): Auth | null;
|
|
8
9
|
export declare function getDb(): Firestore | null;
|
package/dist/firebase/client.js
CHANGED
|
@@ -3,20 +3,8 @@ import { initializeApp, getApps, getApp } from "firebase/app";
|
|
|
3
3
|
import { getAuth } from "firebase/auth";
|
|
4
4
|
import { getFirestore } from "firebase/firestore";
|
|
5
5
|
import { getStorage } from "firebase/storage";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
|
|
9
|
-
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
|
|
10
|
-
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
|
|
11
|
-
storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
|
|
12
|
-
messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
|
|
13
|
-
appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
function isFirebaseConfigured() {
|
|
17
|
-
const cfg = readConfig();
|
|
18
|
-
return Boolean(cfg.apiKey && cfg.projectId);
|
|
19
|
-
}
|
|
6
|
+
import { isFirebaseConfigured, readFirebaseConfig } from "./config.js";
|
|
7
|
+
const readConfig = readFirebaseConfig;
|
|
20
8
|
function getFirebaseApp() {
|
|
21
9
|
if (!isFirebaseConfigured()) return null;
|
|
22
10
|
if (getApps().length) return getApp();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/firebase/client.ts"],"sourcesContent":["\"use client\";\n\nimport { initializeApp, getApps, getApp, type FirebaseApp } from \"firebase/app\";\nimport { getAuth, type Auth } from \"firebase/auth\";\nimport { getFirestore, type Firestore } from \"firebase/firestore\";\nimport { getStorage, type FirebaseStorage } from \"firebase/storage\";\n\n/**\n * Client-side Firebase, volledig env-gedreven (NEXT_PUBLIC_*). Als de env niet is ingevuld\n * geven alle helpers `null` terug, zodat een site ook zónder Firebase blijft werken (dan\n * toont hij alleen de standaardteksten en is bewerken uitgeschakeld).\n */\
|
|
1
|
+
{"version":3,"sources":["../../src/firebase/client.ts"],"sourcesContent":["\"use client\";\n\nimport { initializeApp, getApps, getApp, type FirebaseApp } from \"firebase/app\";\nimport { getAuth, type Auth } from \"firebase/auth\";\nimport { getFirestore, type Firestore } from \"firebase/firestore\";\nimport { getStorage, type FirebaseStorage } from \"firebase/storage\";\nimport { isFirebaseConfigured, readFirebaseConfig } from \"./config\";\n\n/**\n * Client-side Firebase, volledig env-gedreven (NEXT_PUBLIC_*). Als de env niet is ingevuld\n * geven alle helpers `null` terug, zodat een site ook zónder Firebase blijft werken (dan\n * toont hij alleen de standaardteksten en is bewerken uitgeschakeld).\n */\nconst readConfig = readFirebaseConfig;\n\n// Blijft hier geëxporteerd zodat bestaande imports werken; de SDK-vrije bron staat in ./config.\nexport { isFirebaseConfigured };\n\nexport function getFirebaseApp(): FirebaseApp | null {\n if (!isFirebaseConfigured()) return null;\n if (getApps().length) return getApp();\n return initializeApp(readConfig() as Record<string, string>);\n}\n\nexport function getFirebaseAuth(): Auth | null {\n const app = getFirebaseApp();\n return app ? getAuth(app) : null;\n}\n\nexport function getDb(): Firestore | null {\n const app = getFirebaseApp();\n return app ? getFirestore(app) : null;\n}\n\nexport function getBucket(): FirebaseStorage | null {\n const app = getFirebaseApp();\n // Zonder storageBucket geeft getStorage() een client zónder default bucket terug, waarna\n // storageRef() een cryptisch `storage/no-default-bucket` gooit (of de upload stil hangt). Geef\n // hier `null` zodat de aanroeper de nette \"Storage niet geconfigureerd\"-melding kan tonen.\n if (!app || !readConfig().storageBucket) return null;\n return getStorage(app);\n}\n"],"mappings":";AAEA,SAAS,eAAe,SAAS,cAAgC;AACjE,SAAS,eAA0B;AACnC,SAAS,oBAAoC;AAC7C,SAAS,kBAAwC;AACjD,SAAS,sBAAsB,0BAA0B;AAOzD,MAAM,aAAa;AAKZ,SAAS,iBAAqC;AACnD,MAAI,CAAC,qBAAqB,EAAG,QAAO;AACpC,MAAI,QAAQ,EAAE,OAAQ,QAAO,OAAO;AACpC,SAAO,cAAc,WAAW,CAA2B;AAC7D;AAEO,SAAS,kBAA+B;AAC7C,QAAM,MAAM,eAAe;AAC3B,SAAO,MAAM,QAAQ,GAAG,IAAI;AAC9B;AAEO,SAAS,QAA0B;AACxC,QAAM,MAAM,eAAe;AAC3B,SAAO,MAAM,aAAa,GAAG,IAAI;AACnC;AAEO,SAAS,YAAoC;AAClD,QAAM,MAAM,eAAe;AAI3B,MAAI,CAAC,OAAO,CAAC,WAAW,EAAE,cAAe,QAAO;AAChD,SAAO,WAAW,GAAG;AACvB;","names":[]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Firebase-CONFIGURATIE zonder de SDK.
|
|
3
|
+
*
|
|
4
|
+
* `firebase/client.ts` importeert `firebase/app`, `-auth`, `-firestore` en `-storage`. Elke module
|
|
5
|
+
* die hem statisch importeerde trok die keten mee de PUBLIEKE bundel in — ~1 MB JavaScript voor
|
|
6
|
+
* iedere bezoeker, terwijl die de site uit de content-bundle leest en nooit met Firebase praat.
|
|
7
|
+
*
|
|
8
|
+
* Alles wat alleen de env hoeft te lezen staat daarom hier. `client.ts` en de SDK-modules worden
|
|
9
|
+
* voortaan DYNAMISCH geïmporteerd, op het moment dat er echt iets met Firebase moet gebeuren.
|
|
10
|
+
*/
|
|
11
|
+
export declare function readFirebaseConfig(): {
|
|
12
|
+
apiKey: string | undefined;
|
|
13
|
+
authDomain: string | undefined;
|
|
14
|
+
projectId: string | undefined;
|
|
15
|
+
storageBucket: string | undefined;
|
|
16
|
+
messagingSenderId: string | undefined;
|
|
17
|
+
appId: string | undefined;
|
|
18
|
+
};
|
|
19
|
+
/** Is Firebase ingericht? Puur env — laadt geen SDK. */
|
|
20
|
+
export declare function isFirebaseConfigured(): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Onthoudt dát er een Firebase-sessie is, zodat een anonieme bezoeker de auth-SDK niet hoeft te
|
|
23
|
+
* downloaden om te ontdekken dat hij niet is ingelogd.
|
|
24
|
+
*
|
|
25
|
+
* Verlies van de vlag is onschuldig: de bezoeker lijkt dan uitgelogd, en zodra hij inlogt of het
|
|
26
|
+
* beheer opent wordt de SDK alsnog geladen — `onAuthStateChanged` vindt de bestaande sessie dan
|
|
27
|
+
* meteen terug, zonder wachtwoord.
|
|
28
|
+
*/
|
|
29
|
+
export declare const AUTH_HINT_KEY = "xicms.auth";
|
|
30
|
+
export declare function hasAuthHint(): boolean;
|
|
31
|
+
export declare function setAuthHint(active: boolean): void;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
function readFirebaseConfig() {
|
|
2
|
+
return {
|
|
3
|
+
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
|
|
4
|
+
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
|
|
5
|
+
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
|
|
6
|
+
storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
|
|
7
|
+
messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
|
|
8
|
+
appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
function isFirebaseConfigured() {
|
|
12
|
+
const cfg = readFirebaseConfig();
|
|
13
|
+
return Boolean(cfg.apiKey && cfg.projectId);
|
|
14
|
+
}
|
|
15
|
+
const AUTH_HINT_KEY = "xicms.auth";
|
|
16
|
+
function hasAuthHint() {
|
|
17
|
+
if (typeof window === "undefined") return false;
|
|
18
|
+
try {
|
|
19
|
+
return window.localStorage.getItem(AUTH_HINT_KEY) === "1";
|
|
20
|
+
} catch {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function setAuthHint(active) {
|
|
25
|
+
if (typeof window === "undefined") return;
|
|
26
|
+
try {
|
|
27
|
+
if (active) window.localStorage.setItem(AUTH_HINT_KEY, "1");
|
|
28
|
+
else window.localStorage.removeItem(AUTH_HINT_KEY);
|
|
29
|
+
} catch {
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export {
|
|
33
|
+
AUTH_HINT_KEY,
|
|
34
|
+
hasAuthHint,
|
|
35
|
+
isFirebaseConfigured,
|
|
36
|
+
readFirebaseConfig,
|
|
37
|
+
setAuthHint
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/firebase/config.ts"],"sourcesContent":["/**\n * Firebase-CONFIGURATIE zonder de SDK.\n *\n * `firebase/client.ts` importeert `firebase/app`, `-auth`, `-firestore` en `-storage`. Elke module\n * die hem statisch importeerde trok die keten mee de PUBLIEKE bundel in — ~1 MB JavaScript voor\n * iedere bezoeker, terwijl die de site uit de content-bundle leest en nooit met Firebase praat.\n *\n * Alles wat alleen de env hoeft te lezen staat daarom hier. `client.ts` en de SDK-modules worden\n * voortaan DYNAMISCH geïmporteerd, op het moment dat er echt iets met Firebase moet gebeuren.\n */\nexport function readFirebaseConfig() {\n return {\n apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,\n authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,\n projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,\n storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,\n messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,\n appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,\n };\n}\n\n/** Is Firebase ingericht? Puur env — laadt geen SDK. */\nexport function isFirebaseConfigured(): boolean {\n const cfg = readFirebaseConfig();\n return Boolean(cfg.apiKey && cfg.projectId);\n}\n\n/**\n * Onthoudt dát er een Firebase-sessie is, zodat een anonieme bezoeker de auth-SDK niet hoeft te\n * downloaden om te ontdekken dat hij niet is ingelogd.\n *\n * Verlies van de vlag is onschuldig: de bezoeker lijkt dan uitgelogd, en zodra hij inlogt of het\n * beheer opent wordt de SDK alsnog geladen — `onAuthStateChanged` vindt de bestaande sessie dan\n * meteen terug, zonder wachtwoord.\n */\nexport const AUTH_HINT_KEY = \"xicms.auth\";\n\nexport function hasAuthHint(): boolean {\n if (typeof window === \"undefined\") return false;\n try {\n return window.localStorage.getItem(AUTH_HINT_KEY) === \"1\";\n } catch {\n // Opslag geweigerd (private mode): dan maar laden — liever een zwaardere pagina dan een\n // editor die niet bij zijn beheer kan.\n return true;\n }\n}\n\nexport function setAuthHint(active: boolean): void {\n if (typeof window === \"undefined\") return;\n try {\n if (active) window.localStorage.setItem(AUTH_HINT_KEY, \"1\");\n else window.localStorage.removeItem(AUTH_HINT_KEY);\n } catch {\n /* opslag onbeschikbaar: hint vervalt, gedrag blijft correct (zie hasAuthHint) */\n }\n}\n"],"mappings":"AAUO,SAAS,qBAAqB;AACnC,SAAO;AAAA,IACL,QAAQ,QAAQ,IAAI;AAAA,IACpB,YAAY,QAAQ,IAAI;AAAA,IACxB,WAAW,QAAQ,IAAI;AAAA,IACvB,eAAe,QAAQ,IAAI;AAAA,IAC3B,mBAAmB,QAAQ,IAAI;AAAA,IAC/B,OAAO,QAAQ,IAAI;AAAA,EACrB;AACF;AAGO,SAAS,uBAAgC;AAC9C,QAAM,MAAM,mBAAmB;AAC/B,SAAO,QAAQ,IAAI,UAAU,IAAI,SAAS;AAC5C;AAUO,MAAM,gBAAgB;AAEtB,SAAS,cAAuB;AACrC,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,MAAI;AACF,WAAO,OAAO,aAAa,QAAQ,aAAa,MAAM;AAAA,EACxD,QAAQ;AAGN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,YAAY,QAAuB;AACjD,MAAI,OAAO,WAAW,YAAa;AACnC,MAAI;AACF,QAAI,OAAQ,QAAO,aAAa,QAAQ,eAAe,GAAG;AAAA,QACrD,QAAO,aAAa,WAAW,aAAa;AAAA,EACnD,QAAQ;AAAA,EAER;AACF;","names":[]}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { addDoc, collection, serverTimestamp } from "firebase/firestore";
|
|
3
|
-
import { ref, uploadBytes } from "firebase/storage";
|
|
4
|
-
import { getDb, getBucket } from "./client";
|
|
5
2
|
async function uploadFeedbackShot(siteId, blob) {
|
|
3
|
+
const [{ getBucket }, { ref, uploadBytes }] = await Promise.all([import("./client.js"), import("firebase/storage")]);
|
|
6
4
|
const bucket = getBucket();
|
|
7
5
|
if (!bucket) return void 0;
|
|
8
6
|
const path = `sites/${siteId}/feedback/${crypto.randomUUID()}.png`;
|
|
@@ -10,6 +8,10 @@ async function uploadFeedbackShot(siteId, blob) {
|
|
|
10
8
|
return path;
|
|
11
9
|
}
|
|
12
10
|
async function addFeedback(siteId, payload) {
|
|
11
|
+
const [{ getDb }, { addDoc, collection, serverTimestamp }] = await Promise.all([
|
|
12
|
+
import("./client.js"),
|
|
13
|
+
import("firebase/firestore")
|
|
14
|
+
]);
|
|
13
15
|
const db = getDb();
|
|
14
16
|
if (!db) throw new Error("Firebase niet beschikbaar.");
|
|
15
17
|
const clean = { status: "open", createdAt: serverTimestamp() };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/firebase/feedback.ts"],"sourcesContent":["\"use client\";\n\
|
|
1
|
+
{"version":3,"sources":["../../src/firebase/feedback.ts"],"sourcesContent":["\"use client\";\n\n\n/**\n * Feedback per onderdeel (review door een ingelogde editor/siteadmin). Opslag per site in\n * `sites/{siteId}/feedback/{id}` (multi-tenant), screenshots in de privé-Storage\n * `sites/{siteId}/feedback/{uuid}.png`. Editor-gated door de firestore/storage-rules. Wordt\n * verzameld in het agency-dashboard via de listSiteFeedback-callable.\n */\nexport interface FeedbackPayload {\n message: string;\n blockKey: string;\n blockType?: string;\n pageRoute?: string;\n authorUid?: string;\n authorEmail?: string;\n screenshotPath?: string;\n}\n\n/** Uploadt een screenshot-blob en geeft het Storage-pad terug (of undefined als Storage ontbreekt). */\nexport async function uploadFeedbackShot(siteId: string, blob: Blob): Promise<string | undefined> {\n // Dynamisch: alleen wie feedback stuurt betaalt voor de SDK.\n const [{ getBucket }, { ref, uploadBytes }] = await Promise.all([import(\"./client\"), import(\"firebase/storage\")]);\n const bucket = getBucket();\n if (!bucket) return undefined;\n const path = `sites/${siteId}/feedback/${crypto.randomUUID()}.png`;\n await uploadBytes(ref(bucket, path), blob, { contentType: \"image/png\" });\n return path;\n}\n\n/** Schrijft een feedback-item. `status` start op \"open\"; het dashboard zet later \"verwerkt\". */\nexport async function addFeedback(siteId: string, payload: FeedbackPayload): Promise<void> {\n const [{ getDb }, { addDoc, collection, serverTimestamp }] = await Promise.all([\n import(\"./client\"),\n import(\"firebase/firestore\"),\n ]);\n const db = getDb();\n if (!db) throw new Error(\"Firebase niet beschikbaar.\");\n // Undefined velden weglaten (Firestore accepteert geen undefined).\n const clean: Record<string, unknown> = { status: \"open\", createdAt: serverTimestamp() };\n for (const [k, v] of Object.entries(payload)) if (v !== undefined && v !== \"\") clean[k] = v;\n await addDoc(collection(db, \"sites\", siteId, \"feedback\"), clean);\n}\n"],"mappings":";AAoBA,eAAsB,mBAAmB,QAAgB,MAAyC;AAEhG,QAAM,CAAC,EAAE,UAAU,GAAG,EAAE,KAAK,YAAY,CAAC,IAAI,MAAM,QAAQ,IAAI,CAAC,OAAO,UAAU,GAAG,OAAO,kBAAkB,CAAC,CAAC;AAChH,QAAM,SAAS,UAAU;AACzB,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,OAAO,SAAS,MAAM,aAAa,OAAO,WAAW,CAAC;AAC5D,QAAM,YAAY,IAAI,QAAQ,IAAI,GAAG,MAAM,EAAE,aAAa,YAAY,CAAC;AACvE,SAAO;AACT;AAGA,eAAsB,YAAY,QAAgB,SAAyC;AACzF,QAAM,CAAC,EAAE,MAAM,GAAG,EAAE,QAAQ,YAAY,gBAAgB,CAAC,IAAI,MAAM,QAAQ,IAAI;AAAA,IAC7E,OAAO,UAAU;AAAA,IACjB,OAAO,oBAAoB;AAAA,EAC7B,CAAC;AACD,QAAM,KAAK,MAAM;AACjB,MAAI,CAAC,GAAI,OAAM,IAAI,MAAM,4BAA4B;AAErD,QAAM,QAAiC,EAAE,QAAQ,QAAQ,WAAW,gBAAgB,EAAE;AACtF,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,OAAO,EAAG,KAAI,MAAM,UAAa,MAAM,GAAI,OAAM,CAAC,IAAI;AAC1F,QAAM,OAAO,WAAW,IAAI,SAAS,QAAQ,UAAU,GAAG,KAAK;AACjE;","names":[]}
|
package/dist/i18n/overlay.d.ts
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Server-safe: geen React, geen Firebase — bruikbaar in server/pages.ts én in de editor.
|
|
9
9
|
*/
|
|
10
|
-
import
|
|
10
|
+
import * as z from "zod/mini";
|
|
11
11
|
/** `{ [locale]: { "hero1.title": "Willkommen", "seo.description": "…" } }` */
|
|
12
|
-
export declare const TranslationsSchema: z.
|
|
12
|
+
export declare const TranslationsSchema: z.ZodMiniRecord<z.ZodMiniString<string>, z.ZodMiniRecord<z.ZodMiniString<string>, z.ZodMiniString<string>>>;
|
|
13
13
|
export type Translations = z.infer<typeof TranslationsSchema>;
|
|
14
14
|
export interface LocaleConfig {
|
|
15
15
|
/** Alle talen van de site, default-taal eerst (bv. ["nl", "de", "en"]). */
|
package/dist/i18n/overlay.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as z from "zod/mini";
|
|
2
2
|
const TranslationsSchema = z.record(
|
|
3
|
-
z.string().
|
|
4
|
-
z.record(z.string().
|
|
5
|
-
|
|
3
|
+
z.string().check(z.minLength(2)).check(z.maxLength(10)),
|
|
4
|
+
z.record(z.string().check(z.minLength(1)).check(z.maxLength(300)), z.string().check(z.maxLength(2e4))).check((ctx) => {
|
|
5
|
+
if (Object.keys(ctx.value).length > 2e3) {
|
|
6
|
+
ctx.issues.push({ code: "custom", input: ctx.value, message: "te veel vertaalsleutels (max 2000 per taal)" });
|
|
7
|
+
}
|
|
6
8
|
})
|
|
7
9
|
);
|
|
8
10
|
function siteLocales(site) {
|
package/dist/i18n/overlay.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/i18n/overlay.ts"],"sourcesContent":["/**\n * Localisatie (5.0) — primo's locale-per-waarde vertaald naar een ADDITIEVE overlay:\n * de blokkenstructuur blijft canoniek (de default-taal), en per pagina/symbool ligt er\n * een `translations`-map overheen: `{ [locale]: { \"<blokKey>.<veldpad>\": \"vertaalde tekst\" } }`.\n * Speciale sleutels: `title` (paginatitel) en `seo.<veld>` (SEO-overlay). Waarden zijn altijd\n * strings — structuurwijzigingen (blokken toevoegen/verplaatsen) gebeuren alléén canoniek.\n *\n * Server-safe: geen React, geen Firebase — bruikbaar in server/pages.ts én in de editor.\n */\nimport { z } from \"zod\";\n\n/** `{ [locale]: { \"hero1.title\": \"Willkommen\", \"seo.description\": \"…\" } }` */\nexport const TranslationsSchema = z.record(\n z.string().min(2).max(10),\n z.record(z.string().min(1).max(300), z.string().max(20_000)).refine((m) => Object.keys(m).length <= 2000, {\n message: \"te veel vertaalsleutels (max 2000 per taal)\",\n }),\n);\nexport type Translations = z.infer<typeof TranslationsSchema>;\n\nexport interface LocaleConfig {\n /** Alle talen van de site, default-taal eerst (bv. [\"nl\", \"de\", \"en\"]). */\n locales: string[];\n defaultLocale: string;\n}\n\n/** Normaliseert de site-config naar een LocaleConfig (default: alleen `lang` ?? \"nl\"). */\nexport function siteLocales(site: { locales?: string[]; defaultLocale?: string; lang?: string }): LocaleConfig {\n const defaultLocale = site.defaultLocale ?? site.lang ?? \"nl\";\n const locales = [defaultLocale, ...(site.locales ?? []).filter((l) => l !== defaultLocale)];\n return { locales: [...new Set(locales)], defaultLocale };\n}\n\n/**\n * Herkent een taalprefix in Next-catch-all-segmenten: `/de/over-ons` → locale \"de\", pad \"/over-ons\".\n * De default-taal heeft géén prefix (canonieke URL's blijven ongewijzigd).\n */\nexport function parseLocaleSlugs(\n slugs: string[] | undefined,\n cfg: LocaleConfig,\n): { locale: string; slugs: string[] } {\n const list = slugs ?? [];\n const first = list[0];\n if (first && first !== cfg.defaultLocale && cfg.locales.includes(first)) {\n return { locale: first, slugs: list.slice(1) };\n }\n return { locale: cfg.defaultLocale, slugs: list };\n}\n\n/** Pad mét taalprefix voor niet-default talen: (\"/over-ons\", \"de\") → \"/de/over-ons\". */\nexport function localizedPath(path: string, locale: string, defaultLocale: string): string {\n if (locale === defaultLocale) return path;\n return `/${locale}${path === \"/\" ? \"\" : path}`;\n}\n\n/* Zelfde gepunte-pad-helpers als de visuele editor: immutable schrijven op \"items.0.title\". */\nfunction getPath(obj: unknown, keys: string[]): unknown {\n return keys.reduce<unknown>((o, k) => (o == null ? undefined : (o as Record<string, unknown>)[k]), obj);\n}\nfunction setPath<T>(obj: T, keys: string[], value: unknown): T {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const clone = (v: unknown): any => (Array.isArray(v) ? [...v] : { ...(v as object) });\n const root = clone(obj);\n let cur = root;\n for (let i = 0; i < keys.length - 1; i++) {\n cur[keys[i]] = clone(cur[keys[i]]);\n cur = cur[keys[i]];\n }\n cur[keys[keys.length - 1]] = value;\n return root as T;\n}\n\n/* Geneste blok-containers (columns/withSidebar/tabs) — structureel herkend zodat de overlay ook\n * blokken BINNEN een indelingsblok bereikt. Elke geneste blok heeft z'n eigen unieke `_key`, dus de\n * vertaalsleutel blijft gewoon \"<blokKey>.<veldpad>\"; we hoeven alleen dieper te zoeken. */\ntype AnyBlock = { _key?: string } & Record<string, unknown>;\n\n/** Past de overlay toe op de geneste blok-lijsten van één (indelings)blok. Referentie-behoudend:\n * ongewijzigd → zelfde object terug (nul overhead voor pagina's zonder vertaalde geneste blokken). */\nfunction localizeNestedBlock(b: AnyBlock, overlay: Record<string, string>): AnyBlock {\n let cur = b;\n const localizeSlot = <S extends { blocks?: AnyBlock[] }>(slot: S): S => {\n if (!Array.isArray(slot?.blocks)) return slot;\n const nb = localizeBlocks(slot.blocks, overlay);\n return nb === slot.blocks ? slot : { ...slot, blocks: nb };\n };\n if (Array.isArray(cur.columns)) {\n const cols = cur.columns as Array<{ blocks?: AnyBlock[] }>;\n const next = cols.map(localizeSlot);\n if (next.some((c, i) => c !== cols[i])) cur = { ...cur, columns: next };\n }\n if (Array.isArray(cur.tabs)) {\n const tabs = cur.tabs as Array<{ blocks?: AnyBlock[] }>;\n const next = tabs.map(localizeSlot);\n if (next.some((t, i) => t !== tabs[i])) cur = { ...cur, tabs: next };\n }\n for (const field of [\"main\", \"aside\"] as const) {\n if (Array.isArray(cur[field])) {\n const nb = localizeBlocks(cur[field] as AnyBlock[], overlay);\n if (nb !== cur[field]) cur = { ...cur, [field]: nb };\n }\n }\n return cur;\n}\n\n/**\n * Past één taal-overlay toe op een lijst blokken — inclusief blokken genest in indelingsblokken\n * (columns/withSidebar/tabs). Alleen bestaande, tekstuele velden worden overschreven: het doelpad\n * moet oplosbaar zijn en de canonieke waarde moet een string (of leeg) zijn — zo kan een verouderde\n * vertaalsleutel nooit de structuur breken (overlay-drift).\n */\nexport function localizeBlocks<T extends { _key?: string }>(blocks: T[], overlay: Record<string, string> | undefined): T[] {\n if (!overlay || blocks.length === 0) return blocks;\n let out = blocks;\n const replace = (idx: number, value: T) => {\n if (out === blocks) out = [...blocks];\n out[idx] = value;\n };\n for (const [key, value] of Object.entries(overlay)) {\n if (typeof value !== \"string\") continue;\n const [blockKey, ...rest] = key.split(\".\");\n if (rest.length === 0) continue;\n const idx = out.findIndex((b) => b?._key === blockKey);\n if (idx >= 0) {\n const parent = rest.length > 1 ? getPath(out[idx], rest.slice(0, -1)) : out[idx];\n if (parent == null || typeof parent !== \"object\") continue;\n const current = (parent as Record<string, unknown>)[rest[rest.length - 1]];\n if (current !== undefined && typeof current !== \"string\") continue;\n replace(idx, setPath(out[idx], rest, value));\n }\n }\n // Recursie in indelingsblokken: pas dezelfde overlay toe op geneste blok-lijsten. Alleen bij een\n // daadwerkelijke wijziging (referentie-check) wordt het container-blok gekloond — ongewijzigde\n // pagina's houden nul overhead.\n for (let i = 0; i < out.length; i++) {\n const updated = localizeNestedBlock(out[i] as AnyBlock, overlay);\n if (updated !== (out[i] as AnyBlock)) replace(i, updated as T);\n }\n return out;\n}\n\n/**\n * Past een taal-overlay toe op een hele pagina (titel + seo + blokken). Geeft de pagina\n * ongewijzigd terug voor de default-taal of zonder vertalingen — nul overhead voor\n * ééntalige sites.\n */\nexport function localizePage<\n T extends { title?: string; seo?: Record<string, unknown>; blocks?: unknown[]; translations?: Translations },\n>(page: T, locale: string | undefined, defaultLocale: string): T {\n if (!locale || locale === defaultLocale) return page;\n const overlay = page.translations?.[locale];\n if (!overlay) return page;\n let out: T = { ...page, blocks: localizeBlocks((page.blocks ?? []) as { _key?: string }[], overlay) };\n for (const [key, value] of Object.entries(overlay)) {\n if (typeof value !== \"string\") continue;\n if (key === \"title\") out = { ...out, title: value };\n else if (key.startsWith(\"seo.\")) out = { ...out, seo: { ...(out.seo ?? {}), [key.slice(4)]: value } };\n }\n return out;\n}\n\n/**\n * Verzamelt ALLE blok-keys, inclusief blokken genest in indelingsblokken (columns/withSidebar/tabs).\n * Gedeeld door pruneTranslations hier en sanitizeTranslations in de Cloud Functions — beide moeten\n * geneste vertalingen als geldig zien, anders worden ze bij publiceren als \"wees\" verwijderd.\n */\nexport function collectBlockKeys(blocks: Array<{ _key?: string }> | undefined, into: Set<string> = new Set()): Set<string> {\n for (const b of blocks ?? []) {\n if (!b || typeof b !== \"object\") continue;\n if (typeof b._key === \"string\") into.add(b._key);\n const a = b as AnyBlock;\n if (Array.isArray(a.columns)) for (const c of a.columns as Array<{ blocks?: AnyBlock[] }>) collectBlockKeys(c?.blocks, into);\n if (Array.isArray(a.tabs)) for (const t of a.tabs as Array<{ blocks?: AnyBlock[] }>) collectBlockKeys(t?.blocks, into);\n if (Array.isArray(a.main)) collectBlockKeys(a.main as AnyBlock[], into);\n if (Array.isArray(a.aside)) collectBlockKeys(a.aside as AnyBlock[], into);\n }\n return into;\n}\n\n/**\n * Ruimt wees-vertalingen op: sleutels die naar een niet (meer) bestaand blok wijzen vervallen\n * (`title` en `seo.*` blijven altijd). Draait bij publiceren zodat de overlay niet oneindig\n * aangroeit wanneer blokken verwijderd worden. Geneste blokken tellen als bestaand.\n */\nexport function pruneTranslations(\n translations: Translations | undefined,\n blocks: Array<{ _key?: string }> | undefined,\n): Translations | undefined {\n if (!translations) return undefined;\n const keys = collectBlockKeys(blocks);\n const out: Translations = {};\n for (const [locale, overlay] of Object.entries(translations)) {\n const kept = Object.fromEntries(\n Object.entries(overlay).filter(([k]) => k === \"title\" || k.startsWith(\"seo.\") || keys.has(k.split(\".\")[0])),\n );\n if (Object.keys(kept).length > 0) out[locale] = kept;\n }\n return Object.keys(out).length > 0 ? out : undefined;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/i18n/overlay.ts"],"sourcesContent":["/**\n * Localisatie (5.0) — primo's locale-per-waarde vertaald naar een ADDITIEVE overlay:\n * de blokkenstructuur blijft canoniek (de default-taal), en per pagina/symbool ligt er\n * een `translations`-map overheen: `{ [locale]: { \"<blokKey>.<veldpad>\": \"vertaalde tekst\" } }`.\n * Speciale sleutels: `title` (paginatitel) en `seo.<veld>` (SEO-overlay). Waarden zijn altijd\n * strings — structuurwijzigingen (blokken toevoegen/verplaatsen) gebeuren alléén canoniek.\n *\n * Server-safe: geen React, geen Firebase — bruikbaar in server/pages.ts én in de editor.\n */\nimport * as z from \"zod/mini\";\n\n/** `{ [locale]: { \"hero1.title\": \"Willkommen\", \"seo.description\": \"…\" } }` */\nexport const TranslationsSchema = z.record(\n z.string().check(z.minLength(2)).check(z.maxLength(10)),\n z.record(z.string().check(z.minLength(1)).check(z.maxLength(300)), z.string().check(z.maxLength(20_000))).check((ctx) => {\n if (Object.keys(ctx.value).length > 2000) {\n ctx.issues.push({ code: \"custom\", input: ctx.value, message: \"te veel vertaalsleutels (max 2000 per taal)\" });\n }\n }),\n);\nexport type Translations = z.infer<typeof TranslationsSchema>;\n\nexport interface LocaleConfig {\n /** Alle talen van de site, default-taal eerst (bv. [\"nl\", \"de\", \"en\"]). */\n locales: string[];\n defaultLocale: string;\n}\n\n/** Normaliseert de site-config naar een LocaleConfig (default: alleen `lang` ?? \"nl\"). */\nexport function siteLocales(site: { locales?: string[]; defaultLocale?: string; lang?: string }): LocaleConfig {\n const defaultLocale = site.defaultLocale ?? site.lang ?? \"nl\";\n const locales = [defaultLocale, ...(site.locales ?? []).filter((l) => l !== defaultLocale)];\n return { locales: [...new Set(locales)], defaultLocale };\n}\n\n/**\n * Herkent een taalprefix in Next-catch-all-segmenten: `/de/over-ons` → locale \"de\", pad \"/over-ons\".\n * De default-taal heeft géén prefix (canonieke URL's blijven ongewijzigd).\n */\nexport function parseLocaleSlugs(\n slugs: string[] | undefined,\n cfg: LocaleConfig,\n): { locale: string; slugs: string[] } {\n const list = slugs ?? [];\n const first = list[0];\n if (first && first !== cfg.defaultLocale && cfg.locales.includes(first)) {\n return { locale: first, slugs: list.slice(1) };\n }\n return { locale: cfg.defaultLocale, slugs: list };\n}\n\n/** Pad mét taalprefix voor niet-default talen: (\"/over-ons\", \"de\") → \"/de/over-ons\". */\nexport function localizedPath(path: string, locale: string, defaultLocale: string): string {\n if (locale === defaultLocale) return path;\n return `/${locale}${path === \"/\" ? \"\" : path}`;\n}\n\n/* Zelfde gepunte-pad-helpers als de visuele editor: immutable schrijven op \"items.0.title\". */\nfunction getPath(obj: unknown, keys: string[]): unknown {\n return keys.reduce<unknown>((o, k) => (o == null ? undefined : (o as Record<string, unknown>)[k]), obj);\n}\nfunction setPath<T>(obj: T, keys: string[], value: unknown): T {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const clone = (v: unknown): any => (Array.isArray(v) ? [...v] : { ...(v as object) });\n const root = clone(obj);\n let cur = root;\n for (let i = 0; i < keys.length - 1; i++) {\n cur[keys[i]] = clone(cur[keys[i]]);\n cur = cur[keys[i]];\n }\n cur[keys[keys.length - 1]] = value;\n return root as T;\n}\n\n/* Geneste blok-containers (columns/withSidebar/tabs) — structureel herkend zodat de overlay ook\n * blokken BINNEN een indelingsblok bereikt. Elke geneste blok heeft z'n eigen unieke `_key`, dus de\n * vertaalsleutel blijft gewoon \"<blokKey>.<veldpad>\"; we hoeven alleen dieper te zoeken. */\ntype AnyBlock = { _key?: string } & Record<string, unknown>;\n\n/** Past de overlay toe op de geneste blok-lijsten van één (indelings)blok. Referentie-behoudend:\n * ongewijzigd → zelfde object terug (nul overhead voor pagina's zonder vertaalde geneste blokken). */\nfunction localizeNestedBlock(b: AnyBlock, overlay: Record<string, string>): AnyBlock {\n let cur = b;\n const localizeSlot = <S extends { blocks?: AnyBlock[] }>(slot: S): S => {\n if (!Array.isArray(slot?.blocks)) return slot;\n const nb = localizeBlocks(slot.blocks, overlay);\n return nb === slot.blocks ? slot : { ...slot, blocks: nb };\n };\n if (Array.isArray(cur.columns)) {\n const cols = cur.columns as Array<{ blocks?: AnyBlock[] }>;\n const next = cols.map(localizeSlot);\n if (next.some((c, i) => c !== cols[i])) cur = { ...cur, columns: next };\n }\n if (Array.isArray(cur.tabs)) {\n const tabs = cur.tabs as Array<{ blocks?: AnyBlock[] }>;\n const next = tabs.map(localizeSlot);\n if (next.some((t, i) => t !== tabs[i])) cur = { ...cur, tabs: next };\n }\n for (const field of [\"main\", \"aside\"] as const) {\n if (Array.isArray(cur[field])) {\n const nb = localizeBlocks(cur[field] as AnyBlock[], overlay);\n if (nb !== cur[field]) cur = { ...cur, [field]: nb };\n }\n }\n return cur;\n}\n\n/**\n * Past één taal-overlay toe op een lijst blokken — inclusief blokken genest in indelingsblokken\n * (columns/withSidebar/tabs). Alleen bestaande, tekstuele velden worden overschreven: het doelpad\n * moet oplosbaar zijn en de canonieke waarde moet een string (of leeg) zijn — zo kan een verouderde\n * vertaalsleutel nooit de structuur breken (overlay-drift).\n */\nexport function localizeBlocks<T extends { _key?: string }>(blocks: T[], overlay: Record<string, string> | undefined): T[] {\n if (!overlay || blocks.length === 0) return blocks;\n let out = blocks;\n const replace = (idx: number, value: T) => {\n if (out === blocks) out = [...blocks];\n out[idx] = value;\n };\n for (const [key, value] of Object.entries(overlay)) {\n if (typeof value !== \"string\") continue;\n const [blockKey, ...rest] = key.split(\".\");\n if (rest.length === 0) continue;\n const idx = out.findIndex((b) => b?._key === blockKey);\n if (idx >= 0) {\n const parent = rest.length > 1 ? getPath(out[idx], rest.slice(0, -1)) : out[idx];\n if (parent == null || typeof parent !== \"object\") continue;\n const current = (parent as Record<string, unknown>)[rest[rest.length - 1]];\n if (current !== undefined && typeof current !== \"string\") continue;\n replace(idx, setPath(out[idx], rest, value));\n }\n }\n // Recursie in indelingsblokken: pas dezelfde overlay toe op geneste blok-lijsten. Alleen bij een\n // daadwerkelijke wijziging (referentie-check) wordt het container-blok gekloond — ongewijzigde\n // pagina's houden nul overhead.\n for (let i = 0; i < out.length; i++) {\n const updated = localizeNestedBlock(out[i] as AnyBlock, overlay);\n if (updated !== (out[i] as AnyBlock)) replace(i, updated as T);\n }\n return out;\n}\n\n/**\n * Past een taal-overlay toe op een hele pagina (titel + seo + blokken). Geeft de pagina\n * ongewijzigd terug voor de default-taal of zonder vertalingen — nul overhead voor\n * ééntalige sites.\n */\nexport function localizePage<\n T extends { title?: string; seo?: Record<string, unknown>; blocks?: unknown[]; translations?: Translations },\n>(page: T, locale: string | undefined, defaultLocale: string): T {\n if (!locale || locale === defaultLocale) return page;\n const overlay = page.translations?.[locale];\n if (!overlay) return page;\n let out: T = { ...page, blocks: localizeBlocks((page.blocks ?? []) as { _key?: string }[], overlay) };\n for (const [key, value] of Object.entries(overlay)) {\n if (typeof value !== \"string\") continue;\n if (key === \"title\") out = { ...out, title: value };\n else if (key.startsWith(\"seo.\")) out = { ...out, seo: { ...(out.seo ?? {}), [key.slice(4)]: value } };\n }\n return out;\n}\n\n/**\n * Verzamelt ALLE blok-keys, inclusief blokken genest in indelingsblokken (columns/withSidebar/tabs).\n * Gedeeld door pruneTranslations hier en sanitizeTranslations in de Cloud Functions — beide moeten\n * geneste vertalingen als geldig zien, anders worden ze bij publiceren als \"wees\" verwijderd.\n */\nexport function collectBlockKeys(blocks: Array<{ _key?: string }> | undefined, into: Set<string> = new Set()): Set<string> {\n for (const b of blocks ?? []) {\n if (!b || typeof b !== \"object\") continue;\n if (typeof b._key === \"string\") into.add(b._key);\n const a = b as AnyBlock;\n if (Array.isArray(a.columns)) for (const c of a.columns as Array<{ blocks?: AnyBlock[] }>) collectBlockKeys(c?.blocks, into);\n if (Array.isArray(a.tabs)) for (const t of a.tabs as Array<{ blocks?: AnyBlock[] }>) collectBlockKeys(t?.blocks, into);\n if (Array.isArray(a.main)) collectBlockKeys(a.main as AnyBlock[], into);\n if (Array.isArray(a.aside)) collectBlockKeys(a.aside as AnyBlock[], into);\n }\n return into;\n}\n\n/**\n * Ruimt wees-vertalingen op: sleutels die naar een niet (meer) bestaand blok wijzen vervallen\n * (`title` en `seo.*` blijven altijd). Draait bij publiceren zodat de overlay niet oneindig\n * aangroeit wanneer blokken verwijderd worden. Geneste blokken tellen als bestaand.\n */\nexport function pruneTranslations(\n translations: Translations | undefined,\n blocks: Array<{ _key?: string }> | undefined,\n): Translations | undefined {\n if (!translations) return undefined;\n const keys = collectBlockKeys(blocks);\n const out: Translations = {};\n for (const [locale, overlay] of Object.entries(translations)) {\n const kept = Object.fromEntries(\n Object.entries(overlay).filter(([k]) => k === \"title\" || k.startsWith(\"seo.\") || keys.has(k.split(\".\")[0])),\n );\n if (Object.keys(kept).length > 0) out[locale] = kept;\n }\n return Object.keys(out).length > 0 ? out : undefined;\n}\n"],"mappings":"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","names":[]}
|