@xiwebdesign/xicms-core 4.17.1
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.d.ts +58 -0
- package/dist/XiCMSProvider.js +165 -0
- package/dist/XiCMSProvider.js.map +1 -0
- package/dist/admin/AdminApp.d.ts +4 -0
- package/dist/admin/AdminApp.js +62 -0
- package/dist/admin/AdminApp.js.map +1 -0
- package/dist/admin/AdminHelp.d.ts +2 -0
- package/dist/admin/AdminHelp.js +65 -0
- package/dist/admin/AdminHelp.js.map +1 -0
- package/dist/admin/NewsEditor.d.ts +2 -0
- package/dist/admin/NewsEditor.js +171 -0
- package/dist/admin/NewsEditor.js.map +1 -0
- package/dist/admin/SuperadminPanel.d.ts +4 -0
- package/dist/admin/SuperadminPanel.js +101 -0
- package/dist/admin/SuperadminPanel.js.map +1 -0
- package/dist/admin/blockEditors/StyleEditor.d.ts +10 -0
- package/dist/admin/blockEditors/StyleEditor.js +78 -0
- package/dist/admin/blockEditors/StyleEditor.js.map +1 -0
- package/dist/admin/blockEditors/index.d.ts +16 -0
- package/dist/admin/blockEditors/index.js +1602 -0
- package/dist/admin/blockEditors/index.js.map +1 -0
- package/dist/admin/editors/RichTextEditor.d.ts +10 -0
- package/dist/admin/editors/RichTextEditor.js +86 -0
- package/dist/admin/editors/RichTextEditor.js.map +1 -0
- package/dist/admin/forms/FormsPanel.d.ts +3 -0
- package/dist/admin/forms/FormsPanel.js +203 -0
- package/dist/admin/forms/FormsPanel.js.map +1 -0
- package/dist/admin/index.d.ts +21 -0
- package/dist/admin/index.js +38 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/lib/compressImage.d.ts +12 -0
- package/dist/admin/lib/compressImage.js +53 -0
- package/dist/admin/lib/compressImage.js.map +1 -0
- package/dist/admin/lib/mediaUsage.d.ts +9 -0
- package/dist/admin/lib/mediaUsage.js +25 -0
- package/dist/admin/lib/mediaUsage.js.map +1 -0
- package/dist/admin/lib/pagesClient.d.ts +38 -0
- package/dist/admin/lib/pagesClient.js +143 -0
- package/dist/admin/lib/pagesClient.js.map +1 -0
- package/dist/admin/lib/slugify.d.ts +2 -0
- package/dist/admin/lib/slugify.js +7 -0
- package/dist/admin/lib/slugify.js.map +1 -0
- package/dist/admin/lib/upload.d.ts +13 -0
- package/dist/admin/lib/upload.js +40 -0
- package/dist/admin/lib/upload.js.map +1 -0
- package/dist/admin/lib/zodFields.d.ts +27 -0
- package/dist/admin/lib/zodFields.js +48 -0
- package/dist/admin/lib/zodFields.js.map +1 -0
- package/dist/admin/media/MediaManager.d.ts +3 -0
- package/dist/admin/media/MediaManager.js +132 -0
- package/dist/admin/media/MediaManager.js.map +1 -0
- package/dist/admin/media/MediaPicker.d.ts +24 -0
- package/dist/admin/media/MediaPicker.js +93 -0
- package/dist/admin/media/MediaPicker.js.map +1 -0
- package/dist/admin/pages/PageEditor.d.ts +5 -0
- package/dist/admin/pages/PageEditor.js +239 -0
- package/dist/admin/pages/PageEditor.js.map +1 -0
- package/dist/admin/pages/PagesPanel.d.ts +3 -0
- package/dist/admin/pages/PagesPanel.js +92 -0
- package/dist/admin/pages/PagesPanel.js.map +1 -0
- package/dist/admin/panelRegistry.d.ts +20 -0
- package/dist/admin/panelRegistry.js +23 -0
- package/dist/admin/panelRegistry.js.map +1 -0
- package/dist/admin/patterns/PatternInserter.d.ts +10 -0
- package/dist/admin/patterns/PatternInserter.js +58 -0
- package/dist/admin/patterns/PatternInserter.js.map +1 -0
- package/dist/admin/settings/SettingsPanel.d.ts +3 -0
- package/dist/admin/settings/SettingsPanel.js +304 -0
- package/dist/admin/settings/SettingsPanel.js.map +1 -0
- package/dist/admin/settings/moduleSettingsForm.d.ts +12 -0
- package/dist/admin/settings/moduleSettingsForm.js +45 -0
- package/dist/admin/settings/moduleSettingsForm.js.map +1 -0
- package/dist/admin/stats/StatsPanel.d.ts +4 -0
- package/dist/admin/stats/StatsPanel.js +80 -0
- package/dist/admin/stats/StatsPanel.js.map +1 -0
- package/dist/admin/ui/ArrayEditor.d.ts +16 -0
- package/dist/admin/ui/ArrayEditor.js +38 -0
- package/dist/admin/ui/ArrayEditor.js.map +1 -0
- package/dist/admin/ui/ConfirmDialog.d.ts +22 -0
- package/dist/admin/ui/ConfirmDialog.js +47 -0
- package/dist/admin/ui/ConfirmDialog.js.map +1 -0
- package/dist/admin/ui/cn.d.ts +3 -0
- package/dist/admin/ui/cn.js +9 -0
- package/dist/admin/ui/cn.js.map +1 -0
- package/dist/admin/ui/dialog.d.ts +9 -0
- package/dist/admin/ui/dialog.js +47 -0
- package/dist/admin/ui/dialog.js.map +1 -0
- package/dist/admin/ui/index.d.ts +7 -0
- package/dist/admin/ui/index.js +29 -0
- package/dist/admin/ui/index.js.map +1 -0
- package/dist/admin/ui/primitives.d.ts +37 -0
- package/dist/admin/ui/primitives.js +137 -0
- package/dist/admin/ui/primitives.js.map +1 -0
- package/dist/admin/ui/select.d.ts +11 -0
- package/dist/admin/ui/select.js +27 -0
- package/dist/admin/ui/select.js.map +1 -0
- package/dist/admin/ui/switch.d.ts +6 -0
- package/dist/admin/ui/switch.js +22 -0
- package/dist/admin/ui/switch.js.map +1 -0
- package/dist/admin/ui/tabs.d.ts +5 -0
- package/dist/admin/ui/tabs.js +36 -0
- package/dist/admin/ui/tabs.js.map +1 -0
- package/dist/admin/ui/toast.d.ts +19 -0
- package/dist/admin/ui/toast.js +24 -0
- package/dist/admin/ui/toast.js.map +1 -0
- package/dist/admin/visual/VisualEditor.d.ts +10 -0
- package/dist/admin/visual/VisualEditor.js +597 -0
- package/dist/admin/visual/VisualEditor.js.map +1 -0
- package/dist/admin/visual/reorder.d.ts +6 -0
- package/dist/admin/visual/reorder.js +12 -0
- package/dist/admin/visual/reorder.js.map +1 -0
- package/dist/admin.css +1 -0
- package/dist/auth/AuthProvider.d.ts +30 -0
- package/dist/auth/AuthProvider.js +110 -0
- package/dist/auth/AuthProvider.js.map +1 -0
- package/dist/auth/LoginForm.d.ts +4 -0
- package/dist/auth/LoginForm.js +122 -0
- package/dist/auth/LoginForm.js.map +1 -0
- package/dist/auth/roles.d.ts +15 -0
- package/dist/auth/roles.js +21 -0
- package/dist/auth/roles.js.map +1 -0
- package/dist/blocks/BlockErrorBoundary.d.ts +32 -0
- package/dist/blocks/BlockErrorBoundary.js +39 -0
- package/dist/blocks/BlockErrorBoundary.js.map +1 -0
- package/dist/blocks/BlockRenderer.d.ts +15 -0
- package/dist/blocks/BlockRenderer.js +24 -0
- package/dist/blocks/BlockRenderer.js.map +1 -0
- package/dist/blocks/blockStyle.d.ts +14 -0
- package/dist/blocks/blockStyle.js +14 -0
- package/dist/blocks/blockStyle.js.map +1 -0
- package/dist/blocks/builtins/Accordion.d.ts +2 -0
- package/dist/blocks/builtins/Accordion.js +45 -0
- package/dist/blocks/builtins/Accordion.js.map +1 -0
- package/dist/blocks/builtins/Agenda.d.ts +23 -0
- package/dist/blocks/builtins/Agenda.js +102 -0
- package/dist/blocks/builtins/Agenda.js.map +1 -0
- package/dist/blocks/builtins/Banner.d.ts +2 -0
- package/dist/blocks/builtins/Banner.js +45 -0
- package/dist/blocks/builtins/Banner.js.map +1 -0
- package/dist/blocks/builtins/BeforeAfter.d.ts +2 -0
- package/dist/blocks/builtins/BeforeAfter.js +44 -0
- package/dist/blocks/builtins/BeforeAfter.js.map +1 -0
- package/dist/blocks/builtins/Booking.d.ts +2 -0
- package/dist/blocks/builtins/Booking.js +27 -0
- package/dist/blocks/builtins/Booking.js.map +1 -0
- package/dist/blocks/builtins/Breadcrumbs.d.ts +10 -0
- package/dist/blocks/builtins/Breadcrumbs.js +73 -0
- package/dist/blocks/builtins/Breadcrumbs.js.map +1 -0
- package/dist/blocks/builtins/Cards.d.ts +2 -0
- package/dist/blocks/builtins/Cards.js +94 -0
- package/dist/blocks/builtins/Cards.js.map +1 -0
- package/dist/blocks/builtins/Columns.d.ts +2 -0
- package/dist/blocks/builtins/Columns.js +22 -0
- package/dist/blocks/builtins/Columns.js.map +1 -0
- package/dist/blocks/builtins/Contact.d.ts +7 -0
- package/dist/blocks/builtins/Contact.js +121 -0
- package/dist/blocks/builtins/Contact.js.map +1 -0
- package/dist/blocks/builtins/Countdown.d.ts +18 -0
- package/dist/blocks/builtins/Countdown.js +65 -0
- package/dist/blocks/builtins/Countdown.js.map +1 -0
- package/dist/blocks/builtins/Cta.d.ts +2 -0
- package/dist/blocks/builtins/Cta.js +52 -0
- package/dist/blocks/builtins/Cta.js.map +1 -0
- package/dist/blocks/builtins/Downloads.d.ts +2 -0
- package/dist/blocks/builtins/Downloads.js +32 -0
- package/dist/blocks/builtins/Downloads.js.map +1 -0
- package/dist/blocks/builtins/Elements.d.ts +4 -0
- package/dist/blocks/builtins/Elements.js +64 -0
- package/dist/blocks/builtins/Elements.js.map +1 -0
- package/dist/blocks/builtins/Embed.d.ts +2 -0
- package/dist/blocks/builtins/Embed.js +30 -0
- package/dist/blocks/builtins/Embed.js.map +1 -0
- package/dist/blocks/builtins/Faq.d.ts +6 -0
- package/dist/blocks/builtins/Faq.js +110 -0
- package/dist/blocks/builtins/Faq.js.map +1 -0
- package/dist/blocks/builtins/FormBlock.d.ts +2 -0
- package/dist/blocks/builtins/FormBlock.js +221 -0
- package/dist/blocks/builtins/FormBlock.js.map +1 -0
- package/dist/blocks/builtins/Gallery.d.ts +23 -0
- package/dist/blocks/builtins/Gallery.js +177 -0
- package/dist/blocks/builtins/Gallery.js.map +1 -0
- package/dist/blocks/builtins/Hero.d.ts +2 -0
- package/dist/blocks/builtins/Hero.js +47 -0
- package/dist/blocks/builtins/Hero.js.map +1 -0
- package/dist/blocks/builtins/ImageBlock.d.ts +2 -0
- package/dist/blocks/builtins/ImageBlock.js +24 -0
- package/dist/blocks/builtins/ImageBlock.js.map +1 -0
- package/dist/blocks/builtins/Jobs.d.ts +8 -0
- package/dist/blocks/builtins/Jobs.js +65 -0
- package/dist/blocks/builtins/Jobs.js.map +1 -0
- package/dist/blocks/builtins/Layout.d.ts +3 -0
- package/dist/blocks/builtins/Layout.js +67 -0
- package/dist/blocks/builtins/Layout.js.map +1 -0
- package/dist/blocks/builtins/MapBlock.d.ts +2 -0
- package/dist/blocks/builtins/MapBlock.js +31 -0
- package/dist/blocks/builtins/MapBlock.js.map +1 -0
- package/dist/blocks/builtins/NewsListBlock.d.ts +2 -0
- package/dist/blocks/builtins/NewsListBlock.js +24 -0
- package/dist/blocks/builtins/NewsListBlock.js.map +1 -0
- package/dist/blocks/builtins/Newsletter.d.ts +2 -0
- package/dist/blocks/builtins/Newsletter.js +84 -0
- package/dist/blocks/builtins/Newsletter.js.map +1 -0
- package/dist/blocks/builtins/OpenNowBadge.d.ts +9 -0
- package/dist/blocks/builtins/OpenNowBadge.js +20 -0
- package/dist/blocks/builtins/OpenNowBadge.js.map +1 -0
- package/dist/blocks/builtins/OpeningHours.d.ts +2 -0
- package/dist/blocks/builtins/OpeningHours.js +47 -0
- package/dist/blocks/builtins/OpeningHours.js.map +1 -0
- package/dist/blocks/builtins/PdfBlock.d.ts +2 -0
- package/dist/blocks/builtins/PdfBlock.js +97 -0
- package/dist/blocks/builtins/PdfBlock.js.map +1 -0
- package/dist/blocks/builtins/Portfolio.d.ts +2 -0
- package/dist/blocks/builtins/Portfolio.js +94 -0
- package/dist/blocks/builtins/Portfolio.js.map +1 -0
- package/dist/blocks/builtins/Pricing.d.ts +6 -0
- package/dist/blocks/builtins/Pricing.js +77 -0
- package/dist/blocks/builtins/Pricing.js.map +1 -0
- package/dist/blocks/builtins/RichTextBlock.d.ts +2 -0
- package/dist/blocks/builtins/RichTextBlock.js +18 -0
- package/dist/blocks/builtins/RichTextBlock.js.map +1 -0
- package/dist/blocks/builtins/Search.d.ts +2 -0
- package/dist/blocks/builtins/Search.js +99 -0
- package/dist/blocks/builtins/Search.js.map +1 -0
- package/dist/blocks/builtins/Sections.d.ts +5 -0
- package/dist/blocks/builtins/Sections.js +183 -0
- package/dist/blocks/builtins/Sections.js.map +1 -0
- package/dist/blocks/builtins/Slider.d.ts +2 -0
- package/dist/blocks/builtins/Slider.js +131 -0
- package/dist/blocks/builtins/Slider.js.map +1 -0
- package/dist/blocks/builtins/Spacer.d.ts +2 -0
- package/dist/blocks/builtins/Spacer.js +19 -0
- package/dist/blocks/builtins/Spacer.js.map +1 -0
- package/dist/blocks/builtins/Team.d.ts +2 -0
- package/dist/blocks/builtins/Team.js +70 -0
- package/dist/blocks/builtins/Team.js.map +1 -0
- package/dist/blocks/builtins/Testimonials.d.ts +8 -0
- package/dist/blocks/builtins/Testimonials.js +82 -0
- package/dist/blocks/builtins/Testimonials.js.map +1 -0
- package/dist/blocks/builtins/Video.d.ts +9 -0
- package/dist/blocks/builtins/Video.js +77 -0
- package/dist/blocks/builtins/Video.js.map +1 -0
- package/dist/blocks/builtins/WhatsappChat.d.ts +8 -0
- package/dist/blocks/builtins/WhatsappChat.js +51 -0
- package/dist/blocks/builtins/WhatsappChat.js.map +1 -0
- package/dist/blocks/builtins/Widgets.d.ts +13 -0
- package/dist/blocks/builtins/Widgets.js +308 -0
- package/dist/blocks/builtins/Widgets.js.map +1 -0
- package/dist/blocks/builtins/consentEmbed.d.ts +17 -0
- package/dist/blocks/builtins/consentEmbed.js +31 -0
- package/dist/blocks/builtins/consentEmbed.js.map +1 -0
- package/dist/blocks/builtins/index.d.ts +46 -0
- package/dist/blocks/builtins/index.js +121 -0
- package/dist/blocks/builtins/index.js.map +1 -0
- package/dist/blocks/builtins/lib/bookingUrl.d.ts +8 -0
- package/dist/blocks/builtins/lib/bookingUrl.js +50 -0
- package/dist/blocks/builtins/lib/bookingUrl.js.map +1 -0
- package/dist/blocks/builtins/lib/fileIcon.d.ts +2 -0
- package/dist/blocks/builtins/lib/fileIcon.js +45 -0
- package/dist/blocks/builtins/lib/fileIcon.js.map +1 -0
- package/dist/blocks/builtins/lib/ics.d.ts +17 -0
- package/dist/blocks/builtins/lib/ics.js +50 -0
- package/dist/blocks/builtins/lib/ics.js.map +1 -0
- package/dist/blocks/builtins/lib/openingHours.d.ts +20 -0
- package/dist/blocks/builtins/lib/openingHours.js +79 -0
- package/dist/blocks/builtins/lib/openingHours.js.map +1 -0
- package/dist/blocks/builtins/lib/searchRank.d.ts +22 -0
- package/dist/blocks/builtins/lib/searchRank.js +45 -0
- package/dist/blocks/builtins/lib/searchRank.js.map +1 -0
- package/dist/blocks/builtins/lib/submitForm.d.ts +6 -0
- package/dist/blocks/builtins/lib/submitForm.js +14 -0
- package/dist/blocks/builtins/lib/submitForm.js.map +1 -0
- package/dist/blocks/context.d.ts +8 -0
- package/dist/blocks/context.js +20 -0
- package/dist/blocks/context.js.map +1 -0
- package/dist/blocks/index.d.ts +9 -0
- package/dist/blocks/index.js +21 -0
- package/dist/blocks/index.js.map +1 -0
- package/dist/blocks/registry.d.ts +75 -0
- package/dist/blocks/registry.js +71 -0
- package/dist/blocks/registry.js.map +1 -0
- package/dist/blocks/ui/index.d.ts +35 -0
- package/dist/blocks/ui/index.js +48 -0
- package/dist/blocks/ui/index.js.map +1 -0
- package/dist/components/EditBar.d.ts +2 -0
- package/dist/components/EditBar.js +20 -0
- package/dist/components/EditBar.js.map +1 -0
- package/dist/components/EditableImage.d.ts +18 -0
- package/dist/components/EditableImage.js +62 -0
- package/dist/components/EditableImage.js.map +1 -0
- package/dist/components/EditableRichText.d.ts +14 -0
- package/dist/components/EditableRichText.js +26 -0
- package/dist/components/EditableRichText.js.map +1 -0
- package/dist/components/EditableText.d.ts +21 -0
- package/dist/components/EditableText.js +25 -0
- package/dist/components/EditableText.js.map +1 -0
- package/dist/components/News.d.ts +38 -0
- package/dist/components/News.js +128 -0
- package/dist/components/News.js.map +1 -0
- package/dist/components/PageView.d.ts +8 -0
- package/dist/components/PageView.js +28 -0
- package/dist/components/PageView.js.map +1 -0
- package/dist/components/ResponsiveImage.d.ts +16 -0
- package/dist/components/ResponsiveImage.js +39 -0
- package/dist/components/ResponsiveImage.js.map +1 -0
- package/dist/components/site/SiteFooter.d.ts +12 -0
- package/dist/components/site/SiteFooter.js +34 -0
- package/dist/components/site/SiteFooter.js.map +1 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.js +17 -0
- package/dist/config.js.map +1 -0
- package/dist/content/ContentProvider.d.ts +23 -0
- package/dist/content/ContentProvider.js +86 -0
- package/dist/content/ContentProvider.js.map +1 -0
- package/dist/content/types.d.ts +41 -0
- package/dist/content/types.js +1 -0
- package/dist/content/types.js.map +1 -0
- package/dist/demo/DemoStudio.d.ts +7 -0
- package/dist/demo/DemoStudio.js +54 -0
- package/dist/demo/DemoStudio.js.map +1 -0
- package/dist/demo/demoStore.d.ts +26 -0
- package/dist/demo/demoStore.js +115 -0
- package/dist/demo/demoStore.js.map +1 -0
- package/dist/editor/EditorProvider.d.ts +12 -0
- package/dist/editor/EditorProvider.js +22 -0
- package/dist/editor/EditorProvider.js.map +1 -0
- package/dist/firebase/admin.d.ts +7 -0
- package/dist/firebase/admin.js +31 -0
- package/dist/firebase/admin.js.map +1 -0
- package/dist/firebase/appcheck.d.ts +6 -0
- package/dist/firebase/appcheck.js +23 -0
- package/dist/firebase/appcheck.js.map +1 -0
- package/dist/firebase/client.d.ts +9 -0
- package/dist/firebase/client.js +44 -0
- package/dist/firebase/client.js.map +1 -0
- package/dist/firebase/region.d.ts +5 -0
- package/dist/firebase/region.js +7 -0
- package/dist/firebase/region.js.map +1 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.js +172 -0
- package/dist/index.js.map +1 -0
- package/dist/layers.css +13 -0
- package/dist/lock/DemoLock.d.ts +5 -0
- package/dist/lock/DemoLock.js +35 -0
- package/dist/lock/DemoLock.js.map +1 -0
- package/dist/media/ImageContext.d.ts +41 -0
- package/dist/media/ImageContext.js +74 -0
- package/dist/media/ImageContext.js.map +1 -0
- package/dist/modules/builtins.d.ts +46 -0
- package/dist/modules/builtins.js +462 -0
- package/dist/modules/builtins.js.map +1 -0
- package/dist/modules/consent/ConsentBanner.d.ts +42 -0
- package/dist/modules/consent/ConsentBanner.js +119 -0
- package/dist/modules/consent/ConsentBanner.js.map +1 -0
- package/dist/modules/consent/useConsent.d.ts +28 -0
- package/dist/modules/consent/useConsent.js +52 -0
- package/dist/modules/consent/useConsent.js.map +1 -0
- package/dist/modules/context.d.ts +20 -0
- package/dist/modules/context.js +31 -0
- package/dist/modules/context.js.map +1 -0
- package/dist/modules/contract.d.ts +9 -0
- package/dist/modules/contract.js +38 -0
- package/dist/modules/contract.js.map +1 -0
- package/dist/modules/defineModule.d.ts +11 -0
- package/dist/modules/defineModule.js +7 -0
- package/dist/modules/defineModule.js.map +1 -0
- package/dist/modules/index.d.ts +13 -0
- package/dist/modules/index.js +44 -0
- package/dist/modules/index.js.map +1 -0
- package/dist/modules/overrides.d.ts +17 -0
- package/dist/modules/overrides.js +25 -0
- package/dist/modules/overrides.js.map +1 -0
- package/dist/modules/registry.d.ts +40 -0
- package/dist/modules/registry.js +79 -0
- package/dist/modules/registry.js.map +1 -0
- package/dist/modules/types.d.ts +66 -0
- package/dist/modules/types.js +1 -0
- package/dist/modules/types.js.map +1 -0
- package/dist/patterns/builtins.d.ts +31 -0
- package/dist/patterns/builtins.js +415 -0
- package/dist/patterns/builtins.js.map +1 -0
- package/dist/patterns/index.d.ts +6 -0
- package/dist/patterns/index.js +9 -0
- package/dist/patterns/index.js.map +1 -0
- package/dist/patterns/patterns.d.ts +28 -0
- package/dist/patterns/patterns.js +53 -0
- package/dist/patterns/patterns.js.map +1 -0
- package/dist/render/PageRenderer.d.ts +13 -0
- package/dist/render/PageRenderer.js +14 -0
- package/dist/render/PageRenderer.js.map +1 -0
- package/dist/render/PublishedPage.d.ts +13 -0
- package/dist/render/PublishedPage.js +16 -0
- package/dist/render/PublishedPage.js.map +1 -0
- package/dist/schema/blocks.d.ts +15052 -0
- package/dist/schema/blocks.js +794 -0
- package/dist/schema/blocks.js.map +1 -0
- package/dist/schema/forms.d.ts +111 -0
- package/dist/schema/forms.js +47 -0
- package/dist/schema/forms.js.map +1 -0
- package/dist/schema/index.d.ts +11 -0
- package/dist/schema/index.js +16 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/schema/pages.d.ts +674 -0
- package/dist/schema/pages.js +37 -0
- package/dist/schema/pages.js.map +1 -0
- package/dist/schema/seo.d.ts +29 -0
- package/dist/schema/seo.js +17 -0
- package/dist/schema/seo.js.map +1 -0
- package/dist/schema/settings.d.ts +1126 -0
- package/dist/schema/settings.js +128 -0
- package/dist/schema/settings.js.map +1 -0
- package/dist/schema/siteConfig.d.ts +722 -0
- package/dist/schema/siteConfig.js +39 -0
- package/dist/schema/siteConfig.js.map +1 -0
- package/dist/server/env.d.ts +10 -0
- package/dist/server/env.js +36 -0
- package/dist/server/env.js.map +1 -0
- package/dist/server/index.d.ts +10 -0
- package/dist/server/index.js +51 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/pages.d.ts +15 -0
- package/dist/server/pages.js +99 -0
- package/dist/server/pages.js.map +1 -0
- package/dist/server/sanitize.d.ts +2 -0
- package/dist/server/sanitize.js +55 -0
- package/dist/server/sanitize.js.map +1 -0
- package/dist/server/seo.d.ts +116 -0
- package/dist/server/seo.js +179 -0
- package/dist/server/seo.js.map +1 -0
- package/dist/settings/useSiteSettings.d.ts +25 -0
- package/dist/settings/useSiteSettings.js +68 -0
- package/dist/settings/useSiteSettings.js.map +1 -0
- package/dist/styles.css +2495 -0
- package/dist/theme/presets.d.ts +12 -0
- package/dist/theme/presets.js +147 -0
- package/dist/theme/presets.js.map +1 -0
- package/package.json +112 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import {
|
|
5
|
+
addDoc,
|
|
6
|
+
collection,
|
|
7
|
+
deleteDoc,
|
|
8
|
+
doc,
|
|
9
|
+
onSnapshot,
|
|
10
|
+
orderBy,
|
|
11
|
+
query,
|
|
12
|
+
setDoc
|
|
13
|
+
} from "firebase/firestore";
|
|
14
|
+
import { getDb } from "../../firebase/client";
|
|
15
|
+
import { FormSchema } from "../../schema/forms";
|
|
16
|
+
import { Button, Card, Field, Input, Badge } from "../ui/primitives";
|
|
17
|
+
import { Select } from "../ui/select";
|
|
18
|
+
import { Switch } from "../ui/switch";
|
|
19
|
+
import { useToast } from "../ui/toast";
|
|
20
|
+
import { useConfirm } from "../ui/ConfirmDialog";
|
|
21
|
+
const FIELD_TYPES = [
|
|
22
|
+
{ value: "text", label: "Tekst" },
|
|
23
|
+
{ value: "email", label: "E-mail" },
|
|
24
|
+
{ value: "tel", label: "Telefoon" },
|
|
25
|
+
{ value: "textarea", label: "Tekstvak" },
|
|
26
|
+
{ value: "select", label: "Keuzelijst" },
|
|
27
|
+
{ value: "radio", label: "Keuzerondjes" },
|
|
28
|
+
{ value: "checkbox", label: "Vinkje" },
|
|
29
|
+
{ value: "date", label: "Datum" },
|
|
30
|
+
{ value: "number", label: "Getal" }
|
|
31
|
+
];
|
|
32
|
+
function FormsPanel({ siteId }) {
|
|
33
|
+
const { toast } = useToast();
|
|
34
|
+
const [forms, setForms] = useState([]);
|
|
35
|
+
const [selected, setSelected] = useState(null);
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
const db = getDb();
|
|
38
|
+
if (!db) return;
|
|
39
|
+
return onSnapshot(
|
|
40
|
+
collection(db, "sites", siteId, "forms"),
|
|
41
|
+
(snap) => setForms(snap.docs.map((d) => ({ id: d.id, ...d.data() })))
|
|
42
|
+
);
|
|
43
|
+
}, [siteId]);
|
|
44
|
+
const current = forms.find((f) => f.id === selected) ?? null;
|
|
45
|
+
async function createForm() {
|
|
46
|
+
const db = getDb();
|
|
47
|
+
if (!db) return;
|
|
48
|
+
const ref = await addDoc(collection(db, "sites", siteId, "forms"), {
|
|
49
|
+
name: "Nieuw formulier",
|
|
50
|
+
fields: [{ name: "naam", label: "Naam", type: "text", required: true }],
|
|
51
|
+
submitLabel: "Versturen",
|
|
52
|
+
successMessage: "Bedankt! We nemen snel contact op."
|
|
53
|
+
});
|
|
54
|
+
setSelected(ref.id);
|
|
55
|
+
}
|
|
56
|
+
return /* @__PURE__ */ jsxs("div", { className: "grid gap-4 md:grid-cols-[260px_1fr]", children: [
|
|
57
|
+
/* @__PURE__ */ jsxs(Card, { className: "h-fit", children: [
|
|
58
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-3 flex items-center justify-between", children: [
|
|
59
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Formulieren" }),
|
|
60
|
+
/* @__PURE__ */ jsx(Button, { size: "sm", onClick: createForm, children: "+ Nieuw" })
|
|
61
|
+
] }),
|
|
62
|
+
/* @__PURE__ */ jsxs("ul", { className: "grid gap-1", children: [
|
|
63
|
+
forms.map((f) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
64
|
+
"button",
|
|
65
|
+
{
|
|
66
|
+
onClick: () => setSelected(f.id),
|
|
67
|
+
className: `w-full truncate rounded-lg px-3 py-2 text-left text-sm ${f.id === selected ? "bg-brand-50 text-brand-700" : "hover:bg-slate-100"}`,
|
|
68
|
+
children: [
|
|
69
|
+
f.name,
|
|
70
|
+
/* @__PURE__ */ jsxs("span", { className: "ml-1 text-xs text-slate-400", children: [
|
|
71
|
+
"#",
|
|
72
|
+
f.id.slice(0, 5)
|
|
73
|
+
] })
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
) }, f.id)),
|
|
77
|
+
forms.length === 0 && /* @__PURE__ */ jsx("li", { className: "px-3 py-2 text-sm text-slate-500", children: "Nog geen formulieren." })
|
|
78
|
+
] })
|
|
79
|
+
] }),
|
|
80
|
+
current ? /* @__PURE__ */ jsx(FormDetail, { siteId, form: current, onDeleted: () => setSelected(null), onToast: toast }, current.id) : /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx("p", { className: "text-sm text-slate-500", children: "Selecteer of maak een formulier." }) })
|
|
81
|
+
] });
|
|
82
|
+
}
|
|
83
|
+
function FormDetail({
|
|
84
|
+
siteId,
|
|
85
|
+
form,
|
|
86
|
+
onDeleted,
|
|
87
|
+
onToast
|
|
88
|
+
}) {
|
|
89
|
+
const [def, setDef] = useState(form);
|
|
90
|
+
const confirm = useConfirm();
|
|
91
|
+
async function save() {
|
|
92
|
+
const db = getDb();
|
|
93
|
+
if (!db) return;
|
|
94
|
+
const parsed = FormSchema.safeParse(def);
|
|
95
|
+
if (!parsed.success) {
|
|
96
|
+
onToast({ title: "Ongeldig formulier", description: parsed.error.issues[0]?.message, tone: "error" });
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
await setDoc(doc(db, "sites", siteId, "forms", form.id), parsed.data, { merge: true });
|
|
100
|
+
onToast({ title: "Opgeslagen", tone: "success" });
|
|
101
|
+
}
|
|
102
|
+
async function remove() {
|
|
103
|
+
const db = getDb();
|
|
104
|
+
if (!db) return;
|
|
105
|
+
const ok = await confirm({ title: "Formulier verwijderen?", description: "Het formulier wordt verwijderd. Inzendingen blijven bewaard.", confirmLabel: "Verwijderen", tone: "danger" });
|
|
106
|
+
if (!ok) return;
|
|
107
|
+
await deleteDoc(doc(db, "sites", siteId, "forms", form.id));
|
|
108
|
+
onDeleted();
|
|
109
|
+
}
|
|
110
|
+
function setField(i, part) {
|
|
111
|
+
const fields = [...def.fields];
|
|
112
|
+
fields[i] = { ...fields[i], ...part };
|
|
113
|
+
setDef({ ...def, fields });
|
|
114
|
+
}
|
|
115
|
+
return /* @__PURE__ */ jsxs("div", { className: "grid gap-4", children: [
|
|
116
|
+
/* @__PURE__ */ jsxs(Card, { children: [
|
|
117
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-3 flex items-center justify-between", children: [
|
|
118
|
+
/* @__PURE__ */ jsx("h3", { className: "text-base font-semibold", children: "Formulier bewerken" }),
|
|
119
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
120
|
+
/* @__PURE__ */ jsx(Button, { variant: "danger", size: "sm", onClick: remove, children: "Verwijderen" }),
|
|
121
|
+
/* @__PURE__ */ jsx(Button, { size: "sm", onClick: save, children: "Opslaan" })
|
|
122
|
+
] })
|
|
123
|
+
] }),
|
|
124
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-3 md:grid-cols-2", children: [
|
|
125
|
+
/* @__PURE__ */ jsx(Field, { label: "Naam", children: /* @__PURE__ */ jsx(Input, { value: def.name, onChange: (e) => setDef({ ...def, name: e.target.value }) }) }),
|
|
126
|
+
/* @__PURE__ */ jsx(Field, { label: "Verstuurknop", children: /* @__PURE__ */ jsx(Input, { value: def.submitLabel, onChange: (e) => setDef({ ...def, submitLabel: e.target.value }) }) }),
|
|
127
|
+
/* @__PURE__ */ jsx(Field, { label: "Bedankbericht", children: /* @__PURE__ */ jsx(Input, { value: def.successMessage, onChange: (e) => setDef({ ...def, successMessage: e.target.value }) }) }),
|
|
128
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 items-end gap-2", children: [
|
|
129
|
+
/* @__PURE__ */ jsx(Field, { label: "Bedankt-pagina (optioneel)", hint: "Na verzenden doorsturen naar dit pad, bv. /bedankt", children: /* @__PURE__ */ jsx(Input, { value: def.successRedirect ?? "", onChange: (e) => setDef({ ...def, successRedirect: e.target.value || void 0 }) }) }),
|
|
130
|
+
/* @__PURE__ */ jsxs("label", { className: "flex h-9 items-center gap-2 text-sm", children: [
|
|
131
|
+
/* @__PURE__ */ jsx(
|
|
132
|
+
"input",
|
|
133
|
+
{
|
|
134
|
+
type: "checkbox",
|
|
135
|
+
checked: Boolean(def.confirmSubmitter),
|
|
136
|
+
onChange: (e) => setDef({ ...def, confirmSubmitter: e.target.checked })
|
|
137
|
+
}
|
|
138
|
+
),
|
|
139
|
+
"Bevestigingsmail naar inzender (eerste e-mailveld)"
|
|
140
|
+
] })
|
|
141
|
+
] })
|
|
142
|
+
] }),
|
|
143
|
+
/* @__PURE__ */ jsxs("p", { className: "mt-3 text-xs text-slate-500", children: [
|
|
144
|
+
"Formulier-id (voor het Formulier-blok): ",
|
|
145
|
+
/* @__PURE__ */ jsx("code", { children: form.id })
|
|
146
|
+
] })
|
|
147
|
+
] }),
|
|
148
|
+
/* @__PURE__ */ jsxs(Card, { children: [
|
|
149
|
+
/* @__PURE__ */ jsx("h4", { className: "mb-3 text-sm font-semibold", children: "Velden" }),
|
|
150
|
+
/* @__PURE__ */ jsx("div", { className: "grid gap-3", children: def.fields.map((f, i) => /* @__PURE__ */ jsxs("div", { className: "grid items-end gap-2 rounded-lg border border-slate-200 p-3 md:grid-cols-[1fr_1fr_140px_auto_auto]", children: [
|
|
151
|
+
/* @__PURE__ */ jsx(Field, { label: "Naam (sleutel)", children: /* @__PURE__ */ jsx(Input, { value: f.name, onChange: (e) => setField(i, { name: e.target.value }) }) }),
|
|
152
|
+
/* @__PURE__ */ jsx(Field, { label: "Label", children: /* @__PURE__ */ jsx(Input, { value: f.label, onChange: (e) => setField(i, { label: e.target.value }) }) }),
|
|
153
|
+
/* @__PURE__ */ jsx(Field, { label: "Type", children: /* @__PURE__ */ jsx(Select, { value: f.type, onValueChange: (v) => setField(i, { type: v }), options: FIELD_TYPES }) }),
|
|
154
|
+
/* @__PURE__ */ jsx(Switch, { checked: f.required, onCheckedChange: (v) => setField(i, { required: v }), label: "Verplicht" }),
|
|
155
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: () => setDef({ ...def, fields: def.fields.filter((_, idx) => idx !== i) }), children: "\u{1F5D1}" }),
|
|
156
|
+
(f.type === "select" || f.type === "radio") && /* @__PURE__ */ jsx("div", { className: "md:col-span-5", children: /* @__PURE__ */ jsx(Field, { label: "Opties (komma-gescheiden)", children: /* @__PURE__ */ jsx(
|
|
157
|
+
Input,
|
|
158
|
+
{
|
|
159
|
+
value: (f.options ?? []).join(", "),
|
|
160
|
+
onChange: (e) => setField(i, { options: e.target.value.split(",").map((s) => s.trim()).filter(Boolean) })
|
|
161
|
+
}
|
|
162
|
+
) }) })
|
|
163
|
+
] }, i)) }),
|
|
164
|
+
/* @__PURE__ */ jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsx(
|
|
165
|
+
Button,
|
|
166
|
+
{
|
|
167
|
+
variant: "secondary",
|
|
168
|
+
size: "sm",
|
|
169
|
+
onClick: () => setDef({ ...def, fields: [...def.fields, { name: `veld${def.fields.length + 1}`, label: "Nieuw veld", type: "text", required: false }] }),
|
|
170
|
+
children: "+ Veld toevoegen"
|
|
171
|
+
}
|
|
172
|
+
) })
|
|
173
|
+
] }),
|
|
174
|
+
/* @__PURE__ */ jsx(Submissions, { siteId, formId: form.id })
|
|
175
|
+
] });
|
|
176
|
+
}
|
|
177
|
+
function Submissions({ siteId, formId }) {
|
|
178
|
+
const [subs, setSubs] = useState([]);
|
|
179
|
+
useEffect(() => {
|
|
180
|
+
const db = getDb();
|
|
181
|
+
if (!db) return;
|
|
182
|
+
const q = query(collection(db, "sites", siteId, "forms", formId, "submissions"), orderBy("createdAt", "desc"));
|
|
183
|
+
return onSnapshot(q, (snap) => setSubs(snap.docs.map((d) => ({ id: d.id, ...d.data() }))));
|
|
184
|
+
}, [siteId, formId]);
|
|
185
|
+
return /* @__PURE__ */ jsxs(Card, { children: [
|
|
186
|
+
/* @__PURE__ */ jsxs("h4", { className: "mb-3 text-sm font-semibold", children: [
|
|
187
|
+
"Inzendingen ",
|
|
188
|
+
/* @__PURE__ */ jsx(Badge, { children: subs.length })
|
|
189
|
+
] }),
|
|
190
|
+
subs.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-slate-500", children: "Nog geen inzendingen." }) : /* @__PURE__ */ jsx("div", { className: "grid gap-2", children: subs.map((s) => /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-slate-200 p-3 text-sm", children: Object.entries(s.values ?? {}).map(([k, v]) => /* @__PURE__ */ jsxs("div", { children: [
|
|
191
|
+
/* @__PURE__ */ jsxs("span", { className: "font-medium text-slate-600", children: [
|
|
192
|
+
k,
|
|
193
|
+
":"
|
|
194
|
+
] }),
|
|
195
|
+
" ",
|
|
196
|
+
String(v)
|
|
197
|
+
] }, k)) }, s.id)) })
|
|
198
|
+
] });
|
|
199
|
+
}
|
|
200
|
+
export {
|
|
201
|
+
FormsPanel
|
|
202
|
+
};
|
|
203
|
+
//# sourceMappingURL=FormsPanel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/forms/FormsPanel.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport {\n addDoc,\n collection,\n deleteDoc,\n doc,\n onSnapshot,\n orderBy,\n query,\n serverTimestamp,\n setDoc,\n} from \"firebase/firestore\";\nimport { getDb } from \"../../firebase/client\";\nimport { FormSchema, type FormDefinition, type FormField, type FieldType } from \"../../schema/forms\";\nimport { Button, Card, Field, Input, Badge } from \"../ui/primitives\";\nimport { Select } from \"../ui/select\";\nimport { Switch } from \"../ui/switch\";\nimport { useToast } from \"../ui/toast\";\nimport { useConfirm } from \"../ui/ConfirmDialog\";\n\ntype StoredForm = FormDefinition & { id: string };\n\nconst FIELD_TYPES: { value: FieldType; label: string }[] = [\n { value: \"text\", label: \"Tekst\" },\n { value: \"email\", label: \"E-mail\" },\n { value: \"tel\", label: \"Telefoon\" },\n { value: \"textarea\", label: \"Tekstvak\" },\n { value: \"select\", label: \"Keuzelijst\" },\n { value: \"radio\", label: \"Keuzerondjes\" },\n { value: \"checkbox\", label: \"Vinkje\" },\n { value: \"date\", label: \"Datum\" },\n { value: \"number\", label: \"Getal\" },\n];\n\nexport function FormsPanel({ siteId }: { siteId: string }) {\n const { toast } = useToast();\n const [forms, setForms] = useState<StoredForm[]>([]);\n const [selected, setSelected] = useState<string | null>(null);\n\n useEffect(() => {\n const db = getDb();\n if (!db) return;\n return onSnapshot(collection(db, \"sites\", siteId, \"forms\"), (snap) =>\n setForms(snap.docs.map((d) => ({ id: d.id, ...(d.data() as Omit<StoredForm, \"id\">) }))),\n );\n }, [siteId]);\n\n const current = forms.find((f) => f.id === selected) ?? null;\n\n async function createForm() {\n const db = getDb();\n if (!db) return;\n const ref = await addDoc(collection(db, \"sites\", siteId, \"forms\"), {\n name: \"Nieuw formulier\",\n fields: [{ name: \"naam\", label: \"Naam\", type: \"text\", required: true }],\n submitLabel: \"Versturen\",\n successMessage: \"Bedankt! We nemen snel contact op.\",\n } satisfies FormDefinition);\n setSelected(ref.id);\n }\n\n return (\n <div className=\"grid gap-4 md:grid-cols-[260px_1fr]\">\n <Card className=\"h-fit\">\n <div className=\"mb-3 flex items-center justify-between\">\n <h3 className=\"text-sm font-semibold\">Formulieren</h3>\n <Button size=\"sm\" onClick={createForm}>+ Nieuw</Button>\n </div>\n <ul className=\"grid gap-1\">\n {forms.map((f) => (\n <li key={f.id}>\n <button\n onClick={() => setSelected(f.id)}\n className={`w-full truncate rounded-lg px-3 py-2 text-left text-sm ${\n f.id === selected ? \"bg-brand-50 text-brand-700\" : \"hover:bg-slate-100\"\n }`}\n >\n {f.name}\n <span className=\"ml-1 text-xs text-slate-400\">#{f.id.slice(0, 5)}</span>\n </button>\n </li>\n ))}\n {forms.length === 0 && <li className=\"px-3 py-2 text-sm text-slate-500\">Nog geen formulieren.</li>}\n </ul>\n </Card>\n {current ? (\n <FormDetail key={current.id} siteId={siteId} form={current} onDeleted={() => setSelected(null)} onToast={toast} />\n ) : (\n <Card>\n <p className=\"text-sm text-slate-500\">Selecteer of maak een formulier.</p>\n </Card>\n )}\n </div>\n );\n}\n\nfunction FormDetail({\n siteId,\n form,\n onDeleted,\n onToast,\n}: {\n siteId: string;\n form: StoredForm;\n onDeleted: () => void;\n onToast: (t: { title: string; description?: string; tone?: \"default\" | \"success\" | \"error\" }) => void;\n}) {\n const [def, setDef] = useState<FormDefinition>(form);\n const confirm = useConfirm();\n\n async function save() {\n const db = getDb();\n if (!db) return;\n const parsed = FormSchema.safeParse(def);\n if (!parsed.success) {\n onToast({ title: \"Ongeldig formulier\", description: parsed.error.issues[0]?.message, tone: \"error\" });\n return;\n }\n await setDoc(doc(db, \"sites\", siteId, \"forms\", form.id), parsed.data, { merge: true });\n onToast({ title: \"Opgeslagen\", tone: \"success\" });\n }\n\n async function remove() {\n const db = getDb();\n if (!db) return;\n const ok = await confirm({ title: \"Formulier verwijderen?\", description: \"Het formulier wordt verwijderd. Inzendingen blijven bewaard.\", confirmLabel: \"Verwijderen\", tone: \"danger\" });\n if (!ok) return;\n await deleteDoc(doc(db, \"sites\", siteId, \"forms\", form.id));\n onDeleted();\n }\n\n function setField(i: number, part: Partial<FormField>) {\n const fields = [...def.fields];\n fields[i] = { ...fields[i], ...part };\n setDef({ ...def, fields });\n }\n\n return (\n <div className=\"grid gap-4\">\n <Card>\n <div className=\"mb-3 flex items-center justify-between\">\n <h3 className=\"text-base font-semibold\">Formulier bewerken</h3>\n <div className=\"flex gap-2\">\n <Button variant=\"danger\" size=\"sm\" onClick={remove}>Verwijderen</Button>\n <Button size=\"sm\" onClick={save}>Opslaan</Button>\n </div>\n </div>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Naam\"><Input value={def.name} onChange={(e) => setDef({ ...def, name: e.target.value })} /></Field>\n <Field label=\"Verstuurknop\"><Input value={def.submitLabel} onChange={(e) => setDef({ ...def, submitLabel: e.target.value })} /></Field>\n <Field label=\"Bedankbericht\"><Input value={def.successMessage} onChange={(e) => setDef({ ...def, successMessage: e.target.value })} /></Field>\n <div className=\"grid grid-cols-2 items-end gap-2\">\n <Field label=\"Bedankt-pagina (optioneel)\" hint=\"Na verzenden doorsturen naar dit pad, bv. /bedankt\">\n <Input value={def.successRedirect ?? \"\"} onChange={(e) => setDef({ ...def, successRedirect: e.target.value || undefined })} />\n </Field>\n <label className=\"flex h-9 items-center gap-2 text-sm\">\n <input\n type=\"checkbox\"\n checked={Boolean(def.confirmSubmitter)}\n onChange={(e) => setDef({ ...def, confirmSubmitter: e.target.checked })}\n />\n Bevestigingsmail naar inzender (eerste e-mailveld)\n </label>\n </div>\n </div>\n <p className=\"mt-3 text-xs text-slate-500\">Formulier-id (voor het Formulier-blok): <code>{form.id}</code></p>\n </Card>\n\n <Card>\n <h4 className=\"mb-3 text-sm font-semibold\">Velden</h4>\n <div className=\"grid gap-3\">\n {def.fields.map((f, i) => (\n <div key={i} className=\"grid items-end gap-2 rounded-lg border border-slate-200 p-3 md:grid-cols-[1fr_1fr_140px_auto_auto]\">\n <Field label=\"Naam (sleutel)\"><Input value={f.name} onChange={(e) => setField(i, { name: e.target.value })} /></Field>\n <Field label=\"Label\"><Input value={f.label} onChange={(e) => setField(i, { label: e.target.value })} /></Field>\n <Field label=\"Type\">\n <Select value={f.type} onValueChange={(v) => setField(i, { type: v as FieldType })} options={FIELD_TYPES} />\n </Field>\n <Switch checked={f.required} onCheckedChange={(v) => setField(i, { required: v })} label=\"Verplicht\" />\n <Button variant=\"ghost\" size=\"sm\" onClick={() => setDef({ ...def, fields: def.fields.filter((_, idx) => idx !== i) })}>🗑</Button>\n {(f.type === \"select\" || f.type === \"radio\") && (\n <div className=\"md:col-span-5\">\n <Field label=\"Opties (komma-gescheiden)\">\n <Input\n value={(f.options ?? []).join(\", \")}\n onChange={(e) => setField(i, { options: e.target.value.split(\",\").map((s) => s.trim()).filter(Boolean) })}\n />\n </Field>\n </div>\n )}\n </div>\n ))}\n </div>\n <div className=\"mt-3\">\n <Button\n variant=\"secondary\"\n size=\"sm\"\n onClick={() => setDef({ ...def, fields: [...def.fields, { name: `veld${def.fields.length + 1}`, label: \"Nieuw veld\", type: \"text\", required: false }] })}\n >\n + Veld toevoegen\n </Button>\n </div>\n </Card>\n\n <Submissions siteId={siteId} formId={form.id} />\n </div>\n );\n}\n\nfunction Submissions({ siteId, formId }: { siteId: string; formId: string }) {\n const [subs, setSubs] = useState<{ id: string; values: Record<string, unknown>; createdAt?: unknown }[]>([]);\n useEffect(() => {\n const db = getDb();\n if (!db) return;\n const q = query(collection(db, \"sites\", siteId, \"forms\", formId, \"submissions\"), orderBy(\"createdAt\", \"desc\"));\n return onSnapshot(q, (snap) => setSubs(snap.docs.map((d) => ({ id: d.id, ...(d.data() as { values: Record<string, unknown> }) }))));\n }, [siteId, formId]);\n\n return (\n <Card>\n <h4 className=\"mb-3 text-sm font-semibold\">Inzendingen <Badge>{subs.length}</Badge></h4>\n {subs.length === 0 ? (\n <p className=\"text-sm text-slate-500\">Nog geen inzendingen.</p>\n ) : (\n <div className=\"grid gap-2\">\n {subs.map((s) => (\n <div key={s.id} className=\"rounded-lg border border-slate-200 p-3 text-sm\">\n {Object.entries(s.values ?? {}).map(([k, v]) => (\n <div key={k}>\n <span className=\"font-medium text-slate-600\">{k}:</span> {String(v)}\n </div>\n ))}\n </div>\n ))}\n </div>\n )}\n </Card>\n );\n}\n"],"mappings":";AAkEQ,SACE,KADF;AAhER,SAAS,WAAW,gBAAgB;AACpC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,OACK;AACP,SAAS,aAAa;AACtB,SAAS,kBAAuE;AAChF,SAAS,QAAQ,MAAM,OAAO,OAAO,aAAa;AAClD,SAAS,cAAc;AACvB,SAAS,cAAc;AACvB,SAAS,gBAAgB;AACzB,SAAS,kBAAkB;AAI3B,MAAM,cAAqD;AAAA,EACzD,EAAE,OAAO,QAAQ,OAAO,QAAQ;AAAA,EAChC,EAAE,OAAO,SAAS,OAAO,SAAS;AAAA,EAClC,EAAE,OAAO,OAAO,OAAO,WAAW;AAAA,EAClC,EAAE,OAAO,YAAY,OAAO,WAAW;AAAA,EACvC,EAAE,OAAO,UAAU,OAAO,aAAa;AAAA,EACvC,EAAE,OAAO,SAAS,OAAO,eAAe;AAAA,EACxC,EAAE,OAAO,YAAY,OAAO,SAAS;AAAA,EACrC,EAAE,OAAO,QAAQ,OAAO,QAAQ;AAAA,EAChC,EAAE,OAAO,UAAU,OAAO,QAAQ;AACpC;AAEO,SAAS,WAAW,EAAE,OAAO,GAAuB;AACzD,QAAM,EAAE,MAAM,IAAI,SAAS;AAC3B,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAuB,CAAC,CAAC;AACnD,QAAM,CAAC,UAAU,WAAW,IAAI,SAAwB,IAAI;AAE5D,YAAU,MAAM;AACd,UAAM,KAAK,MAAM;AACjB,QAAI,CAAC,GAAI;AACT,WAAO;AAAA,MAAW,WAAW,IAAI,SAAS,QAAQ,OAAO;AAAA,MAAG,CAAC,SAC3D,SAAS,KAAK,KAAK,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,GAAI,EAAE,KAAK,EAA6B,EAAE,CAAC;AAAA,IACxF;AAAA,EACF,GAAG,CAAC,MAAM,CAAC;AAEX,QAAM,UAAU,MAAM,KAAK,CAAC,MAAM,EAAE,OAAO,QAAQ,KAAK;AAExD,iBAAe,aAAa;AAC1B,UAAM,KAAK,MAAM;AACjB,QAAI,CAAC,GAAI;AACT,UAAM,MAAM,MAAM,OAAO,WAAW,IAAI,SAAS,QAAQ,OAAO,GAAG;AAAA,MACjE,MAAM;AAAA,MACN,QAAQ,CAAC,EAAE,MAAM,QAAQ,OAAO,QAAQ,MAAM,QAAQ,UAAU,KAAK,CAAC;AAAA,MACtE,aAAa;AAAA,MACb,gBAAgB;AAAA,IAClB,CAA0B;AAC1B,gBAAY,IAAI,EAAE;AAAA,EACpB;AAEA,SACE,qBAAC,SAAI,WAAU,uCACb;AAAA,yBAAC,QAAK,WAAU,SACd;AAAA,2BAAC,SAAI,WAAU,0CACb;AAAA,4BAAC,QAAG,WAAU,yBAAwB,yBAAW;AAAA,QACjD,oBAAC,UAAO,MAAK,MAAK,SAAS,YAAY,qBAAO;AAAA,SAChD;AAAA,MACA,qBAAC,QAAG,WAAU,cACX;AAAA,cAAM,IAAI,CAAC,MACV,oBAAC,QACC;AAAA,UAAC;AAAA;AAAA,YACC,SAAS,MAAM,YAAY,EAAE,EAAE;AAAA,YAC/B,WAAW,0DACT,EAAE,OAAO,WAAW,+BAA+B,oBACrD;AAAA,YAEC;AAAA,gBAAE;AAAA,cACH,qBAAC,UAAK,WAAU,+BAA8B;AAAA;AAAA,gBAAE,EAAE,GAAG,MAAM,GAAG,CAAC;AAAA,iBAAE;AAAA;AAAA;AAAA,QACnE,KATO,EAAE,EAUX,CACD;AAAA,QACA,MAAM,WAAW,KAAK,oBAAC,QAAG,WAAU,oCAAmC,mCAAqB;AAAA,SAC/F;AAAA,OACF;AAAA,IACC,UACC,oBAAC,cAA4B,QAAgB,MAAM,SAAS,WAAW,MAAM,YAAY,IAAI,GAAG,SAAS,SAAxF,QAAQ,EAAuF,IAEhH,oBAAC,QACC,8BAAC,OAAE,WAAU,0BAAyB,8CAAgC,GACxE;AAAA,KAEJ;AAEJ;AAEA,SAAS,WAAW;AAAA,EAClB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKG;AACD,QAAM,CAAC,KAAK,MAAM,IAAI,SAAyB,IAAI;AACnD,QAAM,UAAU,WAAW;AAE3B,iBAAe,OAAO;AACpB,UAAM,KAAK,MAAM;AACjB,QAAI,CAAC,GAAI;AACT,UAAM,SAAS,WAAW,UAAU,GAAG;AACvC,QAAI,CAAC,OAAO,SAAS;AACnB,cAAQ,EAAE,OAAO,sBAAsB,aAAa,OAAO,MAAM,OAAO,CAAC,GAAG,SAAS,MAAM,QAAQ,CAAC;AACpG;AAAA,IACF;AACA,UAAM,OAAO,IAAI,IAAI,SAAS,QAAQ,SAAS,KAAK,EAAE,GAAG,OAAO,MAAM,EAAE,OAAO,KAAK,CAAC;AACrF,YAAQ,EAAE,OAAO,cAAc,MAAM,UAAU,CAAC;AAAA,EAClD;AAEA,iBAAe,SAAS;AACtB,UAAM,KAAK,MAAM;AACjB,QAAI,CAAC,GAAI;AACT,UAAM,KAAK,MAAM,QAAQ,EAAE,OAAO,0BAA0B,aAAa,gEAAgE,cAAc,eAAe,MAAM,SAAS,CAAC;AACtL,QAAI,CAAC,GAAI;AACT,UAAM,UAAU,IAAI,IAAI,SAAS,QAAQ,SAAS,KAAK,EAAE,CAAC;AAC1D,cAAU;AAAA,EACZ;AAEA,WAAS,SAAS,GAAW,MAA0B;AACrD,UAAM,SAAS,CAAC,GAAG,IAAI,MAAM;AAC7B,WAAO,CAAC,IAAI,EAAE,GAAG,OAAO,CAAC,GAAG,GAAG,KAAK;AACpC,WAAO,EAAE,GAAG,KAAK,OAAO,CAAC;AAAA,EAC3B;AAEA,SACE,qBAAC,SAAI,WAAU,cACb;AAAA,yBAAC,QACC;AAAA,2BAAC,SAAI,WAAU,0CACb;AAAA,4BAAC,QAAG,WAAU,2BAA0B,gCAAkB;AAAA,QAC1D,qBAAC,SAAI,WAAU,cACb;AAAA,8BAAC,UAAO,SAAQ,UAAS,MAAK,MAAK,SAAS,QAAQ,yBAAW;AAAA,UAC/D,oBAAC,UAAO,MAAK,MAAK,SAAS,MAAM,qBAAO;AAAA,WAC1C;AAAA,SACF;AAAA,MACA,qBAAC,SAAI,WAAU,6BACb;AAAA,4BAAC,SAAM,OAAM,QAAO,8BAAC,SAAM,OAAO,IAAI,MAAM,UAAU,CAAC,MAAM,OAAO,EAAE,GAAG,KAAK,MAAM,EAAE,OAAO,MAAM,CAAC,GAAG,GAAE;AAAA,QACzG,oBAAC,SAAM,OAAM,gBAAe,8BAAC,SAAM,OAAO,IAAI,aAAa,UAAU,CAAC,MAAM,OAAO,EAAE,GAAG,KAAK,aAAa,EAAE,OAAO,MAAM,CAAC,GAAG,GAAE;AAAA,QAC/H,oBAAC,SAAM,OAAM,iBAAgB,8BAAC,SAAM,OAAO,IAAI,gBAAgB,UAAU,CAAC,MAAM,OAAO,EAAE,GAAG,KAAK,gBAAgB,EAAE,OAAO,MAAM,CAAC,GAAG,GAAE;AAAA,QACtI,qBAAC,SAAI,WAAU,oCACb;AAAA,8BAAC,SAAM,OAAM,8BAA6B,MAAK,sDAC7C,8BAAC,SAAM,OAAO,IAAI,mBAAmB,IAAI,UAAU,CAAC,MAAM,OAAO,EAAE,GAAG,KAAK,iBAAiB,EAAE,OAAO,SAAS,OAAU,CAAC,GAAG,GAC9H;AAAA,UACA,qBAAC,WAAM,WAAU,uCACf;AAAA;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBACL,SAAS,QAAQ,IAAI,gBAAgB;AAAA,gBACrC,UAAU,CAAC,MAAM,OAAO,EAAE,GAAG,KAAK,kBAAkB,EAAE,OAAO,QAAQ,CAAC;AAAA;AAAA,YACxE;AAAA,YAAE;AAAA,aAEJ;AAAA,WACF;AAAA,SACF;AAAA,MACA,qBAAC,OAAE,WAAU,+BAA8B;AAAA;AAAA,QAAwC,oBAAC,UAAM,eAAK,IAAG;AAAA,SAAO;AAAA,OAC3G;AAAA,IAEA,qBAAC,QACC;AAAA,0BAAC,QAAG,WAAU,8BAA6B,oBAAM;AAAA,MACjD,oBAAC,SAAI,WAAU,cACZ,cAAI,OAAO,IAAI,CAAC,GAAG,MAClB,qBAAC,SAAY,WAAU,sGACrB;AAAA,4BAAC,SAAM,OAAM,kBAAiB,8BAAC,SAAM,OAAO,EAAE,MAAM,UAAU,CAAC,MAAM,SAAS,GAAG,EAAE,MAAM,EAAE,OAAO,MAAM,CAAC,GAAG,GAAE;AAAA,QAC9G,oBAAC,SAAM,OAAM,SAAQ,8BAAC,SAAM,OAAO,EAAE,OAAO,UAAU,CAAC,MAAM,SAAS,GAAG,EAAE,OAAO,EAAE,OAAO,MAAM,CAAC,GAAG,GAAE;AAAA,QACvG,oBAAC,SAAM,OAAM,QACX,8BAAC,UAAO,OAAO,EAAE,MAAM,eAAe,CAAC,MAAM,SAAS,GAAG,EAAE,MAAM,EAAe,CAAC,GAAG,SAAS,aAAa,GAC5G;AAAA,QACA,oBAAC,UAAO,SAAS,EAAE,UAAU,iBAAiB,CAAC,MAAM,SAAS,GAAG,EAAE,UAAU,EAAE,CAAC,GAAG,OAAM,aAAY;AAAA,QACrG,oBAAC,UAAO,SAAQ,SAAQ,MAAK,MAAK,SAAS,MAAM,OAAO,EAAE,GAAG,KAAK,QAAQ,IAAI,OAAO,OAAO,CAAC,GAAG,QAAQ,QAAQ,CAAC,EAAE,CAAC,GAAG,uBAAE;AAAA,SACvH,EAAE,SAAS,YAAY,EAAE,SAAS,YAClC,oBAAC,SAAI,WAAU,iBACb,8BAAC,SAAM,OAAM,6BACX;AAAA,UAAC;AAAA;AAAA,YACC,QAAQ,EAAE,WAAW,CAAC,GAAG,KAAK,IAAI;AAAA,YAClC,UAAU,CAAC,MAAM,SAAS,GAAG,EAAE,SAAS,EAAE,OAAO,MAAM,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO,EAAE,CAAC;AAAA;AAAA,QAC1G,GACF,GACF;AAAA,WAhBM,CAkBV,CACD,GACH;AAAA,MACA,oBAAC,SAAI,WAAU,QACb;AAAA,QAAC;AAAA;AAAA,UACC,SAAQ;AAAA,UACR,MAAK;AAAA,UACL,SAAS,MAAM,OAAO,EAAE,GAAG,KAAK,QAAQ,CAAC,GAAG,IAAI,QAAQ,EAAE,MAAM,OAAO,IAAI,OAAO,SAAS,CAAC,IAAI,OAAO,cAAc,MAAM,QAAQ,UAAU,MAAM,CAAC,EAAE,CAAC;AAAA,UACxJ;AAAA;AAAA,MAED,GACF;AAAA,OACF;AAAA,IAEA,oBAAC,eAAY,QAAgB,QAAQ,KAAK,IAAI;AAAA,KAChD;AAEJ;AAEA,SAAS,YAAY,EAAE,QAAQ,OAAO,GAAuC;AAC3E,QAAM,CAAC,MAAM,OAAO,IAAI,SAAiF,CAAC,CAAC;AAC3G,YAAU,MAAM;AACd,UAAM,KAAK,MAAM;AACjB,QAAI,CAAC,GAAI;AACT,UAAM,IAAI,MAAM,WAAW,IAAI,SAAS,QAAQ,SAAS,QAAQ,aAAa,GAAG,QAAQ,aAAa,MAAM,CAAC;AAC7G,WAAO,WAAW,GAAG,CAAC,SAAS,QAAQ,KAAK,KAAK,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,GAAI,EAAE,KAAK,EAA0C,EAAE,CAAC,CAAC;AAAA,EACpI,GAAG,CAAC,QAAQ,MAAM,CAAC;AAEnB,SACE,qBAAC,QACC;AAAA,yBAAC,QAAG,WAAU,8BAA6B;AAAA;AAAA,MAAY,oBAAC,SAAO,eAAK,QAAO;AAAA,OAAQ;AAAA,IAClF,KAAK,WAAW,IACf,oBAAC,OAAE,WAAU,0BAAyB,mCAAqB,IAE3D,oBAAC,SAAI,WAAU,cACZ,eAAK,IAAI,CAAC,MACT,oBAAC,SAAe,WAAU,kDACvB,iBAAO,QAAQ,EAAE,UAAU,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,MACxC,qBAAC,SACC;AAAA,2BAAC,UAAK,WAAU,8BAA8B;AAAA;AAAA,QAAE;AAAA,SAAC;AAAA,MAAO;AAAA,MAAE,OAAO,CAAC;AAAA,SAD1D,CAEV,CACD,KALO,EAAE,EAMZ,CACD,GACH;AAAA,KAEJ;AAEJ;","names":[]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @xiwebdesign/xicms-core/admin — het beheerdashboard (TipTap + Radix + Tailwind).
|
|
3
|
+
* Importeer óók de stijlen op je /beheer-route: import "@xiwebdesign/xicms-core/admin.css";
|
|
4
|
+
* Alleen op de admin-route laden — de stijlen zijn scoped onder `.xicms-admin`.
|
|
5
|
+
*/
|
|
6
|
+
export { AdminApp } from "./AdminApp";
|
|
7
|
+
export { PagesPanel } from "./pages/PagesPanel";
|
|
8
|
+
export { PageEditor } from "./pages/PageEditor";
|
|
9
|
+
export { MediaManager } from "./media/MediaManager";
|
|
10
|
+
export { FormsPanel } from "./forms/FormsPanel";
|
|
11
|
+
export { StatsPanel } from "./stats/StatsPanel";
|
|
12
|
+
export { SettingsPanel } from "./settings/SettingsPanel";
|
|
13
|
+
export { SuperadminPanel } from "./SuperadminPanel";
|
|
14
|
+
export { builtinPanels, type PanelDefinition, type PanelMinRole, } from "./panelRegistry";
|
|
15
|
+
export { NewsEditor } from "./NewsEditor";
|
|
16
|
+
export { RichTextEditor } from "./editors/RichTextEditor";
|
|
17
|
+
export { builtinBlockEditors } from "./blockEditors";
|
|
18
|
+
export { uploadImage } from "./lib/upload";
|
|
19
|
+
export { ConfirmHost, useConfirm, type ConfirmOptions } from "./ui/ConfirmDialog";
|
|
20
|
+
export { ArrayEditor } from "./ui/ArrayEditor";
|
|
21
|
+
export * as ui from "./ui";
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { AdminApp } from "./AdminApp";
|
|
2
|
+
import { PagesPanel } from "./pages/PagesPanel";
|
|
3
|
+
import { PageEditor } from "./pages/PageEditor";
|
|
4
|
+
import { MediaManager } from "./media/MediaManager";
|
|
5
|
+
import { FormsPanel } from "./forms/FormsPanel";
|
|
6
|
+
import { StatsPanel } from "./stats/StatsPanel";
|
|
7
|
+
import { SettingsPanel } from "./settings/SettingsPanel";
|
|
8
|
+
import { SuperadminPanel } from "./SuperadminPanel";
|
|
9
|
+
import {
|
|
10
|
+
builtinPanels
|
|
11
|
+
} from "./panelRegistry";
|
|
12
|
+
import { NewsEditor } from "./NewsEditor";
|
|
13
|
+
import { RichTextEditor } from "./editors/RichTextEditor";
|
|
14
|
+
import { builtinBlockEditors } from "./blockEditors";
|
|
15
|
+
import { uploadImage } from "./lib/upload";
|
|
16
|
+
import { ConfirmHost, useConfirm } from "./ui/ConfirmDialog";
|
|
17
|
+
import { ArrayEditor } from "./ui/ArrayEditor";
|
|
18
|
+
import * as ui from "./ui";
|
|
19
|
+
export {
|
|
20
|
+
AdminApp,
|
|
21
|
+
ArrayEditor,
|
|
22
|
+
ConfirmHost,
|
|
23
|
+
FormsPanel,
|
|
24
|
+
MediaManager,
|
|
25
|
+
NewsEditor,
|
|
26
|
+
PageEditor,
|
|
27
|
+
PagesPanel,
|
|
28
|
+
RichTextEditor,
|
|
29
|
+
SettingsPanel,
|
|
30
|
+
StatsPanel,
|
|
31
|
+
SuperadminPanel,
|
|
32
|
+
builtinBlockEditors,
|
|
33
|
+
builtinPanels,
|
|
34
|
+
ui,
|
|
35
|
+
uploadImage,
|
|
36
|
+
useConfirm
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/admin/index.ts"],"sourcesContent":["/**\n * @xiwebdesign/xicms-core/admin — het beheerdashboard (TipTap + Radix + Tailwind).\n * Importeer óók de stijlen op je /beheer-route: import \"@xiwebdesign/xicms-core/admin.css\";\n * Alleen op de admin-route laden — de stijlen zijn scoped onder `.xicms-admin`.\n */\nexport { AdminApp } from \"./AdminApp\";\nexport { PagesPanel } from \"./pages/PagesPanel\";\nexport { PageEditor } from \"./pages/PageEditor\";\nexport { MediaManager } from \"./media/MediaManager\";\nexport { FormsPanel } from \"./forms/FormsPanel\";\nexport { StatsPanel } from \"./stats/StatsPanel\";\nexport { SettingsPanel } from \"./settings/SettingsPanel\";\nexport { SuperadminPanel } from \"./SuperadminPanel\";\nexport {\n builtinPanels,\n type PanelDefinition,\n type PanelMinRole,\n} from \"./panelRegistry\";\nexport { NewsEditor } from \"./NewsEditor\";\nexport { RichTextEditor } from \"./editors/RichTextEditor\";\nexport { builtinBlockEditors } from \"./blockEditors\";\nexport { uploadImage } from \"./lib/upload\";\nexport { ConfirmHost, useConfirm, type ConfirmOptions } from \"./ui/ConfirmDialog\";\nexport { ArrayEditor } from \"./ui/ArrayEditor\";\nexport * as ui from \"./ui\";\n"],"mappings":"AAKA,SAAS,gBAAgB;AACzB,SAAS,kBAAkB;AAC3B,SAAS,kBAAkB;AAC3B,SAAS,oBAAoB;AAC7B,SAAS,kBAAkB;AAC3B,SAAS,kBAAkB;AAC3B,SAAS,qBAAqB;AAC9B,SAAS,uBAAuB;AAChC;AAAA,EACE;AAAA,OAGK;AACP,SAAS,kBAAkB;AAC3B,SAAS,sBAAsB;AAC/B,SAAS,2BAA2B;AACpC,SAAS,mBAAmB;AAC5B,SAAS,aAAa,kBAAuC;AAC7D,SAAS,mBAAmB;AAC5B,YAAY,QAAQ;","names":[]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comprimeert een afbeelding in de browser vóór upload: schaalt naar max `maxSize` px (langste
|
|
3
|
+
* zijde) en exporteert als webp. Dit bespaart fors op Storage-kosten (een 5MB-foto wordt ~200KB)
|
|
4
|
+
* en houdt de opgeslagen URL stabiel (we uploaden meteen het kleine bestand).
|
|
5
|
+
*
|
|
6
|
+
* Vector/animatie (SVG/GIF) en al kleine bestanden worden ongemoeid gelaten. Bij elke fout valt
|
|
7
|
+
* de functie terug op het originele bestand — uploaden mag nooit stuklopen op compressie.
|
|
8
|
+
*/
|
|
9
|
+
export declare function compressImage(file: File, opts?: {
|
|
10
|
+
maxSize?: number;
|
|
11
|
+
quality?: number;
|
|
12
|
+
}): Promise<File>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
async function compressImage(file, opts = {}) {
|
|
3
|
+
const maxSize = opts.maxSize ?? 2048;
|
|
4
|
+
const quality = opts.quality ?? 0.82;
|
|
5
|
+
if (typeof document === "undefined") return file;
|
|
6
|
+
if (!file.type.startsWith("image/")) return file;
|
|
7
|
+
if (file.type === "image/svg+xml" || file.type === "image/gif") return file;
|
|
8
|
+
try {
|
|
9
|
+
const bitmap = await loadBitmap(file);
|
|
10
|
+
const scale = Math.min(1, maxSize / Math.max(bitmap.width, bitmap.height));
|
|
11
|
+
const w = Math.max(1, Math.round(bitmap.width * scale));
|
|
12
|
+
const h = Math.max(1, Math.round(bitmap.height * scale));
|
|
13
|
+
const canvas = document.createElement("canvas");
|
|
14
|
+
canvas.width = w;
|
|
15
|
+
canvas.height = h;
|
|
16
|
+
const ctx = canvas.getContext("2d");
|
|
17
|
+
if (!ctx) return file;
|
|
18
|
+
ctx.drawImage(bitmap, 0, 0, w, h);
|
|
19
|
+
if ("close" in bitmap && typeof bitmap.close === "function") bitmap.close();
|
|
20
|
+
const blob = await new Promise(
|
|
21
|
+
(resolve) => canvas.toBlob(resolve, "image/webp", quality)
|
|
22
|
+
);
|
|
23
|
+
if (!blob || blob.size >= file.size) return file;
|
|
24
|
+
const name = file.name.replace(/\.[^.]+$/, "") + ".webp";
|
|
25
|
+
return new File([blob], name, { type: "image/webp", lastModified: Date.now() });
|
|
26
|
+
} catch {
|
|
27
|
+
return file;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async function loadBitmap(file) {
|
|
31
|
+
if (typeof createImageBitmap === "function") {
|
|
32
|
+
try {
|
|
33
|
+
return await createImageBitmap(file);
|
|
34
|
+
} catch {
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const url = URL.createObjectURL(file);
|
|
38
|
+
try {
|
|
39
|
+
const img = new Image();
|
|
40
|
+
await new Promise((resolve, reject) => {
|
|
41
|
+
img.onload = () => resolve();
|
|
42
|
+
img.onerror = () => reject(new Error("kon afbeelding niet laden"));
|
|
43
|
+
img.src = url;
|
|
44
|
+
});
|
|
45
|
+
return img;
|
|
46
|
+
} finally {
|
|
47
|
+
URL.revokeObjectURL(url);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export {
|
|
51
|
+
compressImage
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=compressImage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/lib/compressImage.ts"],"sourcesContent":["\"use client\";\n\n/**\n * Comprimeert een afbeelding in de browser vóór upload: schaalt naar max `maxSize` px (langste\n * zijde) en exporteert als webp. Dit bespaart fors op Storage-kosten (een 5MB-foto wordt ~200KB)\n * en houdt de opgeslagen URL stabiel (we uploaden meteen het kleine bestand).\n *\n * Vector/animatie (SVG/GIF) en al kleine bestanden worden ongemoeid gelaten. Bij elke fout valt\n * de functie terug op het originele bestand — uploaden mag nooit stuklopen op compressie.\n */\nexport async function compressImage(\n file: File,\n opts: { maxSize?: number; quality?: number } = {},\n): Promise<File> {\n const maxSize = opts.maxSize ?? 2048;\n const quality = opts.quality ?? 0.82;\n\n if (typeof document === \"undefined\") return file; // alleen client\n if (!file.type.startsWith(\"image/\")) return file;\n if (file.type === \"image/svg+xml\" || file.type === \"image/gif\") return file;\n\n try {\n const bitmap = await loadBitmap(file);\n const scale = Math.min(1, maxSize / Math.max(bitmap.width, bitmap.height));\n const w = Math.max(1, Math.round(bitmap.width * scale));\n const h = Math.max(1, Math.round(bitmap.height * scale));\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = w;\n canvas.height = h;\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return file;\n ctx.drawImage(bitmap as CanvasImageSource, 0, 0, w, h);\n if (\"close\" in bitmap && typeof bitmap.close === \"function\") bitmap.close();\n\n const blob = await new Promise<Blob | null>((resolve) =>\n canvas.toBlob(resolve, \"image/webp\", quality),\n );\n if (!blob || blob.size >= file.size) return file; // compressie hielp niet\n\n const name = file.name.replace(/\\.[^.]+$/, \"\") + \".webp\";\n return new File([blob], name, { type: \"image/webp\", lastModified: Date.now() });\n } catch {\n return file;\n }\n}\n\nasync function loadBitmap(file: File): Promise<ImageBitmap | HTMLImageElement> {\n if (typeof createImageBitmap === \"function\") {\n try {\n return await createImageBitmap(file);\n } catch {\n /* val terug op <img> */\n }\n }\n const url = URL.createObjectURL(file);\n try {\n const img = new Image();\n await new Promise<void>((resolve, reject) => {\n img.onload = () => resolve();\n img.onerror = () => reject(new Error(\"kon afbeelding niet laden\"));\n img.src = url;\n });\n return img;\n } finally {\n URL.revokeObjectURL(url);\n }\n}\n"],"mappings":";AAUA,eAAsB,cACpB,MACA,OAA+C,CAAC,GACjC;AACf,QAAM,UAAU,KAAK,WAAW;AAChC,QAAM,UAAU,KAAK,WAAW;AAEhC,MAAI,OAAO,aAAa,YAAa,QAAO;AAC5C,MAAI,CAAC,KAAK,KAAK,WAAW,QAAQ,EAAG,QAAO;AAC5C,MAAI,KAAK,SAAS,mBAAmB,KAAK,SAAS,YAAa,QAAO;AAEvE,MAAI;AACF,UAAM,SAAS,MAAM,WAAW,IAAI;AACpC,UAAM,QAAQ,KAAK,IAAI,GAAG,UAAU,KAAK,IAAI,OAAO,OAAO,OAAO,MAAM,CAAC;AACzE,UAAM,IAAI,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,QAAQ,KAAK,CAAC;AACtD,UAAM,IAAI,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,SAAS,KAAK,CAAC;AAEvD,UAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,WAAO,QAAQ;AACf,WAAO,SAAS;AAChB,UAAM,MAAM,OAAO,WAAW,IAAI;AAClC,QAAI,CAAC,IAAK,QAAO;AACjB,QAAI,UAAU,QAA6B,GAAG,GAAG,GAAG,CAAC;AACrD,QAAI,WAAW,UAAU,OAAO,OAAO,UAAU,WAAY,QAAO,MAAM;AAE1E,UAAM,OAAO,MAAM,IAAI;AAAA,MAAqB,CAAC,YAC3C,OAAO,OAAO,SAAS,cAAc,OAAO;AAAA,IAC9C;AACA,QAAI,CAAC,QAAQ,KAAK,QAAQ,KAAK,KAAM,QAAO;AAE5C,UAAM,OAAO,KAAK,KAAK,QAAQ,YAAY,EAAE,IAAI;AACjD,WAAO,IAAI,KAAK,CAAC,IAAI,GAAG,MAAM,EAAE,MAAM,cAAc,cAAc,KAAK,IAAI,EAAE,CAAC;AAAA,EAChF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAe,WAAW,MAAqD;AAC7E,MAAI,OAAO,sBAAsB,YAAY;AAC3C,QAAI;AACF,aAAO,MAAM,kBAAkB,IAAI;AAAA,IACrC,QAAQ;AAAA,IAER;AAAA,EACF;AACA,QAAM,MAAM,IAAI,gBAAgB,IAAI;AACpC,MAAI;AACF,UAAM,MAAM,IAAI,MAAM;AACtB,UAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,UAAI,SAAS,MAAM,QAAQ;AAC3B,UAAI,UAAU,MAAM,OAAO,IAAI,MAAM,2BAA2B,CAAC;AACjE,UAAI,MAAM;AAAA,IACZ,CAAC;AACD,WAAO;AAAA,EACT,UAAE;AACA,QAAI,gBAAgB,GAAG;AAAA,EACzB;AACF;","names":[]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zoekt (goedkoop) of een media-item in gepubliceerde pagina's wordt gebruikt, zodat we kunnen
|
|
3
|
+
* waarschuwen vóór verwijderen (anders breekt een live pagina met een dode afbeelding).
|
|
4
|
+
* Sites hebben weinig pagina's → een JSON-substring-scan is ruim voldoende.
|
|
5
|
+
*/
|
|
6
|
+
export declare function findMediaUsage(siteId: string, item: {
|
|
7
|
+
id: string;
|
|
8
|
+
url?: string;
|
|
9
|
+
}): Promise<string[]>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { collection, getDocs } from "firebase/firestore";
|
|
3
|
+
import { getDb } from "../../firebase/client";
|
|
4
|
+
async function findMediaUsage(siteId, item) {
|
|
5
|
+
const db = getDb();
|
|
6
|
+
if (!db) return [];
|
|
7
|
+
try {
|
|
8
|
+
const snap = await getDocs(collection(db, "sites", siteId, "pages"));
|
|
9
|
+
const hits = [];
|
|
10
|
+
for (const d of snap.docs) {
|
|
11
|
+
const data = d.data();
|
|
12
|
+
const blob = JSON.stringify(data.blocks ?? []);
|
|
13
|
+
if (item.url && blob.includes(item.url) || blob.includes(item.id)) {
|
|
14
|
+
hits.push(String(data.path ?? data.title ?? d.id));
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return hits;
|
|
18
|
+
} catch {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export {
|
|
23
|
+
findMediaUsage
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=mediaUsage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/lib/mediaUsage.ts"],"sourcesContent":["\"use client\";\n\nimport { collection, getDocs } from \"firebase/firestore\";\nimport { getDb } from \"../../firebase/client\";\n\n/**\n * Zoekt (goedkoop) of een media-item in gepubliceerde pagina's wordt gebruikt, zodat we kunnen\n * waarschuwen vóór verwijderen (anders breekt een live pagina met een dode afbeelding).\n * Sites hebben weinig pagina's → een JSON-substring-scan is ruim voldoende.\n */\nexport async function findMediaUsage(\n siteId: string,\n item: { id: string; url?: string },\n): Promise<string[]> {\n const db = getDb();\n if (!db) return [];\n try {\n const snap = await getDocs(collection(db, \"sites\", siteId, \"pages\"));\n const hits: string[] = [];\n for (const d of snap.docs) {\n const data = d.data() as Record<string, unknown>;\n const blob = JSON.stringify(data.blocks ?? []);\n if ((item.url && blob.includes(item.url)) || blob.includes(item.id)) {\n hits.push(String(data.path ?? data.title ?? d.id));\n }\n }\n return hits;\n } catch {\n return [];\n }\n}\n"],"mappings":";AAEA,SAAS,YAAY,eAAe;AACpC,SAAS,aAAa;AAOtB,eAAsB,eACpB,QACA,MACmB;AACnB,QAAM,KAAK,MAAM;AACjB,MAAI,CAAC,GAAI,QAAO,CAAC;AACjB,MAAI;AACF,UAAM,OAAO,MAAM,QAAQ,WAAW,IAAI,SAAS,QAAQ,OAAO,CAAC;AACnE,UAAM,OAAiB,CAAC;AACxB,eAAW,KAAK,KAAK,MAAM;AACzB,YAAM,OAAO,EAAE,KAAK;AACpB,YAAM,OAAO,KAAK,UAAU,KAAK,UAAU,CAAC,CAAC;AAC7C,UAAK,KAAK,OAAO,KAAK,SAAS,KAAK,GAAG,KAAM,KAAK,SAAS,KAAK,EAAE,GAAG;AACnE,aAAK,KAAK,OAAO,KAAK,QAAQ,KAAK,SAAS,EAAE,EAAE,CAAC;AAAA,MACnD;AAAA,IACF;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;","names":[]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Page } from "../../schema/pages";
|
|
2
|
+
export type DraftPage = Page & {
|
|
3
|
+
id: string;
|
|
4
|
+
published?: boolean;
|
|
5
|
+
};
|
|
6
|
+
/** Live-lijst van pagina's (werkversies in `drafts/`). */
|
|
7
|
+
export declare function subscribePages(siteId: string, cb: (pages: DraftPage[]) => void): () => void;
|
|
8
|
+
export declare function subscribeVersions(siteId: string, pageId: string, cb: (versions: {
|
|
9
|
+
id: string;
|
|
10
|
+
createdAt?: unknown;
|
|
11
|
+
kind?: string;
|
|
12
|
+
label?: string;
|
|
13
|
+
}[]) => void): () => void;
|
|
14
|
+
/** Laadt de werkversie (draft) voor een pad — voor het inline bewerken op de live pagina. */
|
|
15
|
+
export declare function getDraftByPath(siteId: string, path: string): Promise<DraftPage | null>;
|
|
16
|
+
/**
|
|
17
|
+
* Live-listener op één werkversie — voor conflictdetectie in de visuele editor (twee tabs of
|
|
18
|
+
* twee editors tegelijk). `fromServer` is false voor de lokale echo van een eigen write
|
|
19
|
+
* (hasPendingWrites), zodat de aanroeper eigen saves kan negeren.
|
|
20
|
+
*/
|
|
21
|
+
export declare function subscribeDraft(siteId: string, pageId: string, cb: (draft: DraftPage | null, meta: {
|
|
22
|
+
fromServer: boolean;
|
|
23
|
+
}) => void): () => void;
|
|
24
|
+
export declare function createPage(siteId: string, input: Partial<Page> & {
|
|
25
|
+
title: string;
|
|
26
|
+
slug: string;
|
|
27
|
+
path: string;
|
|
28
|
+
}): Promise<string>;
|
|
29
|
+
export declare function saveDraft(siteId: string, pageId: string, partial: Partial<Page>): Promise<void>;
|
|
30
|
+
export declare function deletePage(siteId: string, pageId: string): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Herstelt een eerdere versie naar de WERKVERSIE (draft). De gebruiker moet daarna zelf
|
|
33
|
+
* publiceren — zo is herstellen veilig en omkeerbaar (de live site verandert pas bij Publiceren).
|
|
34
|
+
* Geeft de herstelde velden terug zodat de editor zijn lokale state kan bijwerken.
|
|
35
|
+
*/
|
|
36
|
+
export declare function restoreVersion(siteId: string, pageId: string, versionId: string): Promise<Partial<Page>>;
|
|
37
|
+
/** Publiceren via de Cloud Function (server-side validatie + versie-snapshot + audit). */
|
|
38
|
+
export declare function publishPage(siteId: string, pageId: string): Promise<void>;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
addDoc,
|
|
4
|
+
collection,
|
|
5
|
+
deleteDoc,
|
|
6
|
+
doc,
|
|
7
|
+
getDoc,
|
|
8
|
+
getDocs,
|
|
9
|
+
limit,
|
|
10
|
+
onSnapshot,
|
|
11
|
+
orderBy,
|
|
12
|
+
query,
|
|
13
|
+
serverTimestamp,
|
|
14
|
+
setDoc,
|
|
15
|
+
where
|
|
16
|
+
} from "firebase/firestore";
|
|
17
|
+
import { getFunctions, httpsCallable } from "firebase/functions";
|
|
18
|
+
import { getDb, getFirebaseApp } from "../../firebase/client";
|
|
19
|
+
import { functionsRegion } from "../../firebase/region";
|
|
20
|
+
import { isDemoActive, demoIdForPath, resolveDemoPage, saveDemoDraft } from "../../demo/demoStore";
|
|
21
|
+
function subscribePages(siteId, cb) {
|
|
22
|
+
const db = getDb();
|
|
23
|
+
if (!db) {
|
|
24
|
+
cb([]);
|
|
25
|
+
return () => {
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
const q = query(collection(db, "sites", siteId, "drafts"), orderBy("order", "asc"));
|
|
29
|
+
return onSnapshot(q, (snap) => {
|
|
30
|
+
cb(snap.docs.map((d) => ({ id: d.id, ...d.data() })));
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
function subscribeVersions(siteId, pageId, cb) {
|
|
34
|
+
const db = getDb();
|
|
35
|
+
if (!db) {
|
|
36
|
+
cb([]);
|
|
37
|
+
return () => {
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const q = query(collection(db, "sites", siteId, "pages", pageId, "versions"), orderBy("createdAt", "desc"));
|
|
41
|
+
return onSnapshot(q, (snap) => cb(snap.docs.map((d) => ({ id: d.id, ...d.data() }))));
|
|
42
|
+
}
|
|
43
|
+
async function getDraftByPath(siteId, path) {
|
|
44
|
+
if (isDemoActive()) {
|
|
45
|
+
const page = resolveDemoPage(siteId, path);
|
|
46
|
+
return page ? { ...page, id: demoIdForPath(path) } : null;
|
|
47
|
+
}
|
|
48
|
+
const db = getDb();
|
|
49
|
+
if (!db) return null;
|
|
50
|
+
const snap = await getDocs(
|
|
51
|
+
query(collection(db, "sites", siteId, "drafts"), where("path", "==", path), limit(1))
|
|
52
|
+
);
|
|
53
|
+
const d = snap.docs[0];
|
|
54
|
+
return d ? { id: d.id, ...d.data() } : null;
|
|
55
|
+
}
|
|
56
|
+
function subscribeDraft(siteId, pageId, cb) {
|
|
57
|
+
if (isDemoActive()) return () => {
|
|
58
|
+
};
|
|
59
|
+
const db = getDb();
|
|
60
|
+
if (!db) return () => {
|
|
61
|
+
};
|
|
62
|
+
return onSnapshot(doc(db, "sites", siteId, "drafts", pageId), (snap) => {
|
|
63
|
+
cb(
|
|
64
|
+
snap.exists() ? { id: snap.id, ...snap.data() } : null,
|
|
65
|
+
{ fromServer: !snap.metadata.hasPendingWrites }
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
async function createPage(siteId, input) {
|
|
70
|
+
const db = getDb();
|
|
71
|
+
if (!db) throw new Error("Firebase niet geconfigureerd.");
|
|
72
|
+
const ref = await addDoc(collection(db, "sites", siteId, "drafts"), {
|
|
73
|
+
parentId: null,
|
|
74
|
+
order: Date.now(),
|
|
75
|
+
status: "draft",
|
|
76
|
+
blocks: [],
|
|
77
|
+
published: false,
|
|
78
|
+
...input,
|
|
79
|
+
updatedAt: serverTimestamp()
|
|
80
|
+
});
|
|
81
|
+
return ref.id;
|
|
82
|
+
}
|
|
83
|
+
async function saveDraft(siteId, pageId, partial) {
|
|
84
|
+
if (isDemoActive()) {
|
|
85
|
+
saveDemoDraft(siteId, pageId, partial);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const db = getDb();
|
|
89
|
+
if (!db) return;
|
|
90
|
+
await setDoc(
|
|
91
|
+
doc(db, "sites", siteId, "drafts", pageId),
|
|
92
|
+
{ ...partial, updatedAt: serverTimestamp() },
|
|
93
|
+
{ merge: true }
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
async function deletePage(siteId, pageId) {
|
|
97
|
+
const db = getDb();
|
|
98
|
+
if (!db) return;
|
|
99
|
+
await deleteDoc(doc(db, "sites", siteId, "drafts", pageId));
|
|
100
|
+
try {
|
|
101
|
+
await deleteDoc(doc(db, "sites", siteId, "pages", pageId));
|
|
102
|
+
} catch {
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async function restoreVersion(siteId, pageId, versionId) {
|
|
106
|
+
const db = getDb();
|
|
107
|
+
if (!db) throw new Error("Firebase niet geconfigureerd.");
|
|
108
|
+
const snap = await getDoc(doc(db, "sites", siteId, "pages", pageId, "versions", versionId));
|
|
109
|
+
if (!snap.exists()) throw new Error("Versie niet gevonden.");
|
|
110
|
+
const s = snap.data().snapshot ?? {};
|
|
111
|
+
const restored = {
|
|
112
|
+
title: s.title,
|
|
113
|
+
slug: s.slug,
|
|
114
|
+
path: s.path,
|
|
115
|
+
seo: s.seo,
|
|
116
|
+
blocks: s.blocks,
|
|
117
|
+
order: s.order
|
|
118
|
+
};
|
|
119
|
+
await saveDraft(siteId, pageId, restored);
|
|
120
|
+
return restored;
|
|
121
|
+
}
|
|
122
|
+
async function publishPage(siteId, pageId) {
|
|
123
|
+
if (isDemoActive()) {
|
|
124
|
+
saveDemoDraft(siteId, pageId, {});
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const app = getFirebaseApp();
|
|
128
|
+
if (!app) throw new Error("Firebase niet geconfigureerd.");
|
|
129
|
+
const fn = httpsCallable(getFunctions(app, functionsRegion()), "publishPage");
|
|
130
|
+
await fn({ siteId, pageId });
|
|
131
|
+
}
|
|
132
|
+
export {
|
|
133
|
+
createPage,
|
|
134
|
+
deletePage,
|
|
135
|
+
getDraftByPath,
|
|
136
|
+
publishPage,
|
|
137
|
+
restoreVersion,
|
|
138
|
+
saveDraft,
|
|
139
|
+
subscribeDraft,
|
|
140
|
+
subscribePages,
|
|
141
|
+
subscribeVersions
|
|
142
|
+
};
|
|
143
|
+
//# sourceMappingURL=pagesClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/lib/pagesClient.ts"],"sourcesContent":["\"use client\";\n\nimport {\n addDoc,\n collection,\n deleteDoc,\n doc,\n getDoc,\n getDocs,\n limit,\n onSnapshot,\n orderBy,\n query,\n serverTimestamp,\n setDoc,\n where,\n} from \"firebase/firestore\";\nimport { getFunctions, httpsCallable } from \"firebase/functions\";\nimport { getDb, getFirebaseApp } from \"../../firebase/client\";\nimport { functionsRegion } from \"../../firebase/region\";\nimport type { Page } from \"../../schema/pages\";\nimport { isDemoActive, demoIdForPath, resolveDemoPage, saveDemoDraft } from \"../../demo/demoStore\";\n\nexport type DraftPage = Page & { id: string; published?: boolean };\n\n/** Live-lijst van pagina's (werkversies in `drafts/`). */\nexport function subscribePages(siteId: string, cb: (pages: DraftPage[]) => void): () => void {\n const db = getDb();\n if (!db) {\n cb([]);\n return () => {};\n }\n const q = query(collection(db, \"sites\", siteId, \"drafts\"), orderBy(\"order\", \"asc\"));\n return onSnapshot(q, (snap) => {\n cb(snap.docs.map((d) => ({ id: d.id, ...(d.data() as Omit<DraftPage, \"id\">) })));\n });\n}\n\nexport function subscribeVersions(\n siteId: string,\n pageId: string,\n cb: (versions: { id: string; createdAt?: unknown; kind?: string; label?: string }[]) => void,\n): () => void {\n const db = getDb();\n if (!db) {\n cb([]);\n return () => {};\n }\n const q = query(collection(db, \"sites\", siteId, \"pages\", pageId, \"versions\"), orderBy(\"createdAt\", \"desc\"));\n return onSnapshot(q, (snap) => cb(snap.docs.map((d) => ({ id: d.id, ...(d.data() as Record<string, unknown>) }))));\n}\n\n/** Laadt de werkversie (draft) voor een pad — voor het inline bewerken op de live pagina. */\nexport async function getDraftByPath(siteId: string, path: string): Promise<DraftPage | null> {\n // Demo-modus: de werkversie komt uit de lokale browseropslag (seed + lokale bewerkingen).\n if (isDemoActive()) {\n const page = resolveDemoPage(siteId, path);\n return page ? ({ ...page, id: demoIdForPath(path) } as DraftPage) : null;\n }\n const db = getDb();\n if (!db) return null;\n const snap = await getDocs(\n query(collection(db, \"sites\", siteId, \"drafts\"), where(\"path\", \"==\", path), limit(1)),\n );\n const d = snap.docs[0];\n return d ? ({ id: d.id, ...(d.data() as Omit<DraftPage, \"id\">) }) : null;\n}\n\n/**\n * Live-listener op één werkversie — voor conflictdetectie in de visuele editor (twee tabs of\n * twee editors tegelijk). `fromServer` is false voor de lokale echo van een eigen write\n * (hasPendingWrites), zodat de aanroeper eigen saves kan negeren.\n */\nexport function subscribeDraft(\n siteId: string,\n pageId: string,\n cb: (draft: DraftPage | null, meta: { fromServer: boolean }) => void,\n): () => void {\n if (isDemoActive()) return () => {};\n const db = getDb();\n if (!db) return () => {};\n return onSnapshot(doc(db, \"sites\", siteId, \"drafts\", pageId), (snap) => {\n cb(\n snap.exists() ? ({ id: snap.id, ...(snap.data() as Omit<DraftPage, \"id\">) }) : null,\n { fromServer: !snap.metadata.hasPendingWrites },\n );\n });\n}\n\nexport async function createPage(siteId: string, input: Partial<Page> & { title: string; slug: string; path: string }) {\n const db = getDb();\n if (!db) throw new Error(\"Firebase niet geconfigureerd.\");\n const ref = await addDoc(collection(db, \"sites\", siteId, \"drafts\"), {\n parentId: null,\n order: Date.now(),\n status: \"draft\",\n blocks: [],\n published: false,\n ...input,\n updatedAt: serverTimestamp(),\n });\n return ref.id;\n}\n\nexport async function saveDraft(siteId: string, pageId: string, partial: Partial<Page>) {\n // Demo-modus: bewaar de bewerking alleen lokaal in de browser (niet in Firebase).\n if (isDemoActive()) {\n saveDemoDraft(siteId, pageId, partial);\n return;\n }\n const db = getDb();\n if (!db) return;\n await setDoc(\n doc(db, \"sites\", siteId, \"drafts\", pageId),\n { ...partial, updatedAt: serverTimestamp() },\n { merge: true },\n );\n}\n\nexport async function deletePage(siteId: string, pageId: string) {\n const db = getDb();\n if (!db) return;\n await deleteDoc(doc(db, \"sites\", siteId, \"drafts\", pageId));\n try {\n await deleteDoc(doc(db, \"sites\", siteId, \"pages\", pageId));\n } catch {\n /* gepubliceerde versie bestond niet of geen rechten — negeren */\n }\n}\n\n/**\n * Herstelt een eerdere versie naar de WERKVERSIE (draft). De gebruiker moet daarna zelf\n * publiceren — zo is herstellen veilig en omkeerbaar (de live site verandert pas bij Publiceren).\n * Geeft de herstelde velden terug zodat de editor zijn lokale state kan bijwerken.\n */\nexport async function restoreVersion(\n siteId: string,\n pageId: string,\n versionId: string,\n): Promise<Partial<Page>> {\n const db = getDb();\n if (!db) throw new Error(\"Firebase niet geconfigureerd.\");\n const snap = await getDoc(doc(db, \"sites\", siteId, \"pages\", pageId, \"versions\", versionId));\n if (!snap.exists()) throw new Error(\"Versie niet gevonden.\");\n const s = (snap.data().snapshot ?? {}) as Record<string, unknown>;\n const restored: Partial<Page> = {\n title: s.title as string,\n slug: s.slug as string,\n path: s.path as string,\n seo: s.seo as Page[\"seo\"],\n blocks: s.blocks as Page[\"blocks\"],\n order: s.order as number,\n };\n await saveDraft(siteId, pageId, restored);\n return restored;\n}\n\n/** Publiceren via de Cloud Function (server-side validatie + versie-snapshot + audit). */\nexport async function publishPage(siteId: string, pageId: string): Promise<void> {\n // Demo-modus: er wordt niets echt gepubliceerd; de lokale bewerking is al zichtbaar.\n if (isDemoActive()) {\n saveDemoDraft(siteId, pageId, {});\n return;\n }\n const app = getFirebaseApp();\n if (!app) throw new Error(\"Firebase niet geconfigureerd.\");\n const fn = httpsCallable(getFunctions(app, functionsRegion()), \"publishPage\");\n await fn({ siteId, pageId });\n}\n"],"mappings":";AAEA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,cAAc,qBAAqB;AAC5C,SAAS,OAAO,sBAAsB;AACtC,SAAS,uBAAuB;AAEhC,SAAS,cAAc,eAAe,iBAAiB,qBAAqB;AAKrE,SAAS,eAAe,QAAgB,IAA8C;AAC3F,QAAM,KAAK,MAAM;AACjB,MAAI,CAAC,IAAI;AACP,OAAG,CAAC,CAAC;AACL,WAAO,MAAM;AAAA,IAAC;AAAA,EAChB;AACA,QAAM,IAAI,MAAM,WAAW,IAAI,SAAS,QAAQ,QAAQ,GAAG,QAAQ,SAAS,KAAK,CAAC;AAClF,SAAO,WAAW,GAAG,CAAC,SAAS;AAC7B,OAAG,KAAK,KAAK,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,GAAI,EAAE,KAAK,EAA4B,EAAE,CAAC;AAAA,EACjF,CAAC;AACH;AAEO,SAAS,kBACd,QACA,QACA,IACY;AACZ,QAAM,KAAK,MAAM;AACjB,MAAI,CAAC,IAAI;AACP,OAAG,CAAC,CAAC;AACL,WAAO,MAAM;AAAA,IAAC;AAAA,EAChB;AACA,QAAM,IAAI,MAAM,WAAW,IAAI,SAAS,QAAQ,SAAS,QAAQ,UAAU,GAAG,QAAQ,aAAa,MAAM,CAAC;AAC1G,SAAO,WAAW,GAAG,CAAC,SAAS,GAAG,KAAK,KAAK,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,GAAI,EAAE,KAAK,EAA8B,EAAE,CAAC,CAAC;AACnH;AAGA,eAAsB,eAAe,QAAgB,MAAyC;AAE5F,MAAI,aAAa,GAAG;AAClB,UAAM,OAAO,gBAAgB,QAAQ,IAAI;AACzC,WAAO,OAAQ,EAAE,GAAG,MAAM,IAAI,cAAc,IAAI,EAAE,IAAkB;AAAA,EACtE;AACA,QAAM,KAAK,MAAM;AACjB,MAAI,CAAC,GAAI,QAAO;AAChB,QAAM,OAAO,MAAM;AAAA,IACjB,MAAM,WAAW,IAAI,SAAS,QAAQ,QAAQ,GAAG,MAAM,QAAQ,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC;AAAA,EACtF;AACA,QAAM,IAAI,KAAK,KAAK,CAAC;AACrB,SAAO,IAAK,EAAE,IAAI,EAAE,IAAI,GAAI,EAAE,KAAK,EAA4B,IAAK;AACtE;AAOO,SAAS,eACd,QACA,QACA,IACY;AACZ,MAAI,aAAa,EAAG,QAAO,MAAM;AAAA,EAAC;AAClC,QAAM,KAAK,MAAM;AACjB,MAAI,CAAC,GAAI,QAAO,MAAM;AAAA,EAAC;AACvB,SAAO,WAAW,IAAI,IAAI,SAAS,QAAQ,UAAU,MAAM,GAAG,CAAC,SAAS;AACtE;AAAA,MACE,KAAK,OAAO,IAAK,EAAE,IAAI,KAAK,IAAI,GAAI,KAAK,KAAK,EAA4B,IAAK;AAAA,MAC/E,EAAE,YAAY,CAAC,KAAK,SAAS,iBAAiB;AAAA,IAChD;AAAA,EACF,CAAC;AACH;AAEA,eAAsB,WAAW,QAAgB,OAAsE;AACrH,QAAM,KAAK,MAAM;AACjB,MAAI,CAAC,GAAI,OAAM,IAAI,MAAM,+BAA+B;AACxD,QAAM,MAAM,MAAM,OAAO,WAAW,IAAI,SAAS,QAAQ,QAAQ,GAAG;AAAA,IAClE,UAAU;AAAA,IACV,OAAO,KAAK,IAAI;AAAA,IAChB,QAAQ;AAAA,IACR,QAAQ,CAAC;AAAA,IACT,WAAW;AAAA,IACX,GAAG;AAAA,IACH,WAAW,gBAAgB;AAAA,EAC7B,CAAC;AACD,SAAO,IAAI;AACb;AAEA,eAAsB,UAAU,QAAgB,QAAgB,SAAwB;AAEtF,MAAI,aAAa,GAAG;AAClB,kBAAc,QAAQ,QAAQ,OAAO;AACrC;AAAA,EACF;AACA,QAAM,KAAK,MAAM;AACjB,MAAI,CAAC,GAAI;AACT,QAAM;AAAA,IACJ,IAAI,IAAI,SAAS,QAAQ,UAAU,MAAM;AAAA,IACzC,EAAE,GAAG,SAAS,WAAW,gBAAgB,EAAE;AAAA,IAC3C,EAAE,OAAO,KAAK;AAAA,EAChB;AACF;AAEA,eAAsB,WAAW,QAAgB,QAAgB;AAC/D,QAAM,KAAK,MAAM;AACjB,MAAI,CAAC,GAAI;AACT,QAAM,UAAU,IAAI,IAAI,SAAS,QAAQ,UAAU,MAAM,CAAC;AAC1D,MAAI;AACF,UAAM,UAAU,IAAI,IAAI,SAAS,QAAQ,SAAS,MAAM,CAAC;AAAA,EAC3D,QAAQ;AAAA,EAER;AACF;AAOA,eAAsB,eACpB,QACA,QACA,WACwB;AACxB,QAAM,KAAK,MAAM;AACjB,MAAI,CAAC,GAAI,OAAM,IAAI,MAAM,+BAA+B;AACxD,QAAM,OAAO,MAAM,OAAO,IAAI,IAAI,SAAS,QAAQ,SAAS,QAAQ,YAAY,SAAS,CAAC;AAC1F,MAAI,CAAC,KAAK,OAAO,EAAG,OAAM,IAAI,MAAM,uBAAuB;AAC3D,QAAM,IAAK,KAAK,KAAK,EAAE,YAAY,CAAC;AACpC,QAAM,WAA0B;AAAA,IAC9B,OAAO,EAAE;AAAA,IACT,MAAM,EAAE;AAAA,IACR,MAAM,EAAE;AAAA,IACR,KAAK,EAAE;AAAA,IACP,QAAQ,EAAE;AAAA,IACV,OAAO,EAAE;AAAA,EACX;AACA,QAAM,UAAU,QAAQ,QAAQ,QAAQ;AACxC,SAAO;AACT;AAGA,eAAsB,YAAY,QAAgB,QAA+B;AAE/E,MAAI,aAAa,GAAG;AAClB,kBAAc,QAAQ,QAAQ,CAAC,CAAC;AAChC;AAAA,EACF;AACA,QAAM,MAAM,eAAe;AAC3B,MAAI,CAAC,IAAK,OAAM,IAAI,MAAM,+BAA+B;AACzD,QAAM,KAAK,cAAc,aAAa,KAAK,gBAAgB,CAAC,GAAG,aAAa;AAC5E,QAAM,GAAG,EAAE,QAAQ,OAAO,CAAC;AAC7B;","names":[]}
|