@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.
Files changed (373) hide show
  1. package/dist/SiteChrome.d.ts +12 -0
  2. package/dist/SiteChrome.js +24 -0
  3. package/dist/SiteChrome.js.map +1 -0
  4. package/dist/XiCMSProvider.d.ts +16 -8
  5. package/dist/XiCMSProvider.js +83 -58
  6. package/dist/XiCMSProvider.js.map +1 -1
  7. package/dist/admin/AdminApp.d.ts +1 -1
  8. package/dist/admin/AdminApp.js +79 -35
  9. package/dist/admin/AdminApp.js.map +1 -1
  10. package/dist/admin/AdminHelp.d.ts +8 -1
  11. package/dist/admin/AdminHelp.js +2 -2
  12. package/dist/admin/AdminHelp.js.map +1 -1
  13. package/dist/admin/NewsEditor.js +9 -9
  14. package/dist/admin/SuperadminPanel.js +5 -5
  15. package/dist/admin/blockEditors/GenericEditor.d.ts +2 -2
  16. package/dist/admin/blockEditors/GenericEditor.js +12 -11
  17. package/dist/admin/blockEditors/GenericEditor.js.map +1 -1
  18. package/dist/admin/blockEditors/StyleEditor.d.ts +1 -1
  19. package/dist/admin/blockEditors/StyleEditor.js +2 -2
  20. package/dist/admin/blockEditors/SymbolRefEditor.d.ts +1 -1
  21. package/dist/admin/blockEditors/SymbolRefEditor.js +5 -5
  22. package/dist/admin/blockEditors/index.d.ts +1 -1
  23. package/dist/admin/blockEditors/index.js +10 -10
  24. package/dist/admin/customBlocks/CustomBlocksPanel.js +7 -7
  25. package/dist/admin/editors/RichTextEditor.js +8 -7
  26. package/dist/admin/editors/RichTextEditor.js.map +1 -1
  27. package/dist/admin/editors/fontSizeExtension.js +1 -1
  28. package/dist/admin/editors/fontSizeExtension.js.map +1 -1
  29. package/dist/admin/forms/FormsPanel.js +9 -9
  30. package/dist/admin/index.d.ts +16 -16
  31. package/dist/admin/index.js +16 -16
  32. package/dist/admin/lib/mediaUsage.js +1 -1
  33. package/dist/admin/lib/pagesClient.d.ts +1 -1
  34. package/dist/admin/lib/pagesClient.js +11 -7
  35. package/dist/admin/lib/pagesClient.js.map +1 -1
  36. package/dist/admin/lib/sanitizeDraft.d.ts +9 -0
  37. package/dist/admin/lib/sanitizeDraft.js +28 -0
  38. package/dist/admin/lib/sanitizeDraft.js.map +1 -0
  39. package/dist/admin/lib/symbolsClient.d.ts +1 -1
  40. package/dist/admin/lib/symbolsClient.js +3 -3
  41. package/dist/admin/lib/upload.js +3 -3
  42. package/dist/admin/lib/useNestedDialog.d.ts +15 -0
  43. package/dist/admin/lib/useNestedDialog.js +23 -0
  44. package/dist/admin/lib/useNestedDialog.js.map +1 -0
  45. package/dist/admin/lib/zodFields.d.ts +4 -18
  46. package/dist/admin/lib/zodFields.js +54 -35
  47. package/dist/admin/lib/zodFields.js.map +1 -1
  48. package/dist/admin/media/MediaManager.js +7 -7
  49. package/dist/admin/media/MediaPicker.js +7 -7
  50. package/dist/admin/media/MediaPicker.js.map +1 -1
  51. package/dist/admin/pages/PageEditor.d.ts +1 -1
  52. package/dist/admin/pages/PageEditor.js +13 -13
  53. package/dist/admin/pages/PagesPanel.js +30 -30
  54. package/dist/admin/pages/PagesPanel.js.map +1 -1
  55. package/dist/admin/panelRegistry.js +7 -7
  56. package/dist/admin/patterns/PatternInserter.d.ts +1 -1
  57. package/dist/admin/patterns/PatternInserter.js +5 -5
  58. package/dist/admin/patterns/PatternInserter.js.map +1 -1
  59. package/dist/admin/settings/MailSettings.js +5 -5
  60. package/dist/admin/settings/MailTemplates.d.ts +1 -1
  61. package/dist/admin/settings/MailTemplates.js +5 -5
  62. package/dist/admin/settings/SettingsPanel.js +20 -20
  63. package/dist/admin/settings/SettingsPanel.js.map +1 -1
  64. package/dist/admin/settings/SnapshotsCard.js +5 -5
  65. package/dist/admin/settings/moduleSettingsForm.d.ts +1 -1
  66. package/dist/admin/settings/moduleSettingsForm.js +4 -4
  67. package/dist/admin/settings/moduleSettingsForm.js.map +1 -1
  68. package/dist/admin/shop/CategoriesTab.js +8 -8
  69. package/dist/admin/shop/ConnectionsTab.js +3 -3
  70. package/dist/admin/shop/DiscountsTab.js +8 -8
  71. package/dist/admin/shop/OrdersTab.js +10 -10
  72. package/dist/admin/shop/OrdersTab.js.map +1 -1
  73. package/dist/admin/shop/ProductsTab.js +10 -10
  74. package/dist/admin/shop/ShopPanel.js +9 -9
  75. package/dist/admin/shop/ShopSettingsTab.js +4 -4
  76. package/dist/admin/shop/index.d.ts +2 -2
  77. package/dist/admin/shop/index.js +2 -2
  78. package/dist/admin/shop/lib.js +2 -2
  79. package/dist/admin/stats/StatsPanel.js +2 -2
  80. package/dist/admin/ui/ArrayEditor.js +1 -1
  81. package/dist/admin/ui/PanelIcon.d.ts +3 -0
  82. package/dist/admin/ui/PanelIcon.js +34 -0
  83. package/dist/admin/ui/PanelIcon.js.map +1 -0
  84. package/dist/admin/ui/dialog.d.ts +21 -6
  85. package/dist/admin/ui/dialog.js +55 -34
  86. package/dist/admin/ui/dialog.js.map +1 -1
  87. package/dist/admin/ui/index.d.ts +7 -7
  88. package/dist/admin/ui/index.js +7 -7
  89. package/dist/admin/ui/primitives.js +1 -1
  90. package/dist/admin/ui/tabs.js +1 -1
  91. package/dist/admin/visual/TranslationPanel.d.ts +1 -1
  92. package/dist/admin/visual/TranslationPanel.js +3 -3
  93. package/dist/admin/visual/VisualEditor.js +47 -25
  94. package/dist/admin/visual/VisualEditor.js.map +1 -1
  95. package/dist/admin.css +2 -1
  96. package/dist/agentFiles/catalog.d.ts +2 -2
  97. package/dist/agentFiles/catalog.js +6 -5
  98. package/dist/agentFiles/catalog.js.map +1 -1
  99. package/dist/agentFiles/files.d.ts +5 -5
  100. package/dist/agentFiles/files.js +17 -17
  101. package/dist/agentFiles/files.js.map +1 -1
  102. package/dist/all.d.ts +4 -4
  103. package/dist/all.js +6 -6
  104. package/dist/auth/AuthProvider.d.ts +2 -2
  105. package/dist/auth/AuthProvider.js +51 -28
  106. package/dist/auth/AuthProvider.js.map +1 -1
  107. package/dist/auth/LoginForm.js +1 -1
  108. package/dist/auth/roles.js +4 -1
  109. package/dist/auth/roles.js.map +1 -1
  110. package/dist/blocks/BlockRenderer.d.ts +1 -1
  111. package/dist/blocks/BlockRenderer.js +3 -3
  112. package/dist/blocks/blockStyle.d.ts +1 -1
  113. package/dist/blocks/builtins/Accordion.d.ts +1 -1
  114. package/dist/blocks/builtins/Accordion.js +13 -6
  115. package/dist/blocks/builtins/Accordion.js.map +1 -1
  116. package/dist/blocks/builtins/Agenda.d.ts +1 -1
  117. package/dist/blocks/builtins/Agenda.js +5 -5
  118. package/dist/blocks/builtins/Banner.d.ts +1 -1
  119. package/dist/blocks/builtins/Banner.js +2 -2
  120. package/dist/blocks/builtins/BeforeAfter.d.ts +1 -1
  121. package/dist/blocks/builtins/BeforeAfter.js +4 -4
  122. package/dist/blocks/builtins/BeforeAfter.js.map +1 -1
  123. package/dist/blocks/builtins/Booking.d.ts +1 -1
  124. package/dist/blocks/builtins/Booking.js +5 -5
  125. package/dist/blocks/builtins/Breadcrumbs.d.ts +1 -1
  126. package/dist/blocks/builtins/Breadcrumbs.js +3 -3
  127. package/dist/blocks/builtins/Cards.d.ts +1 -1
  128. package/dist/blocks/builtins/Cards.js +5 -5
  129. package/dist/blocks/builtins/Cards.js.map +1 -1
  130. package/dist/blocks/builtins/Columns.d.ts +1 -1
  131. package/dist/blocks/builtins/Columns.js +4 -4
  132. package/dist/blocks/builtins/Contact.d.ts +3 -3
  133. package/dist/blocks/builtins/Contact.js +4 -4
  134. package/dist/blocks/builtins/Countdown.d.ts +1 -1
  135. package/dist/blocks/builtins/Countdown.js +4 -4
  136. package/dist/blocks/builtins/Countdown.js.map +1 -1
  137. package/dist/blocks/builtins/Cta.d.ts +1 -1
  138. package/dist/blocks/builtins/Cta.js +3 -3
  139. package/dist/blocks/builtins/Downloads.d.ts +1 -1
  140. package/dist/blocks/builtins/Downloads.js +4 -4
  141. package/dist/blocks/builtins/Elements.d.ts +3 -3
  142. package/dist/blocks/builtins/Elements.js +3 -3
  143. package/dist/blocks/builtins/Embed.d.ts +1 -1
  144. package/dist/blocks/builtins/Embed.js +3 -3
  145. package/dist/blocks/builtins/Faq.d.ts +1 -1
  146. package/dist/blocks/builtins/Faq.js +14 -6
  147. package/dist/blocks/builtins/Faq.js.map +1 -1
  148. package/dist/blocks/builtins/FormBlock.d.ts +1 -1
  149. package/dist/blocks/builtins/FormBlock.js +28 -18
  150. package/dist/blocks/builtins/FormBlock.js.map +1 -1
  151. package/dist/blocks/builtins/Gallery.d.ts +2 -2
  152. package/dist/blocks/builtins/Gallery.js +5 -5
  153. package/dist/blocks/builtins/Hero.d.ts +1 -1
  154. package/dist/blocks/builtins/Hero.js +4 -4
  155. package/dist/blocks/builtins/ImageBlock.d.ts +1 -1
  156. package/dist/blocks/builtins/ImageBlock.js +3 -3
  157. package/dist/blocks/builtins/Jobs.d.ts +2 -2
  158. package/dist/blocks/builtins/Jobs.js +5 -5
  159. package/dist/blocks/builtins/Layout.d.ts +2 -2
  160. package/dist/blocks/builtins/Layout.js +4 -4
  161. package/dist/blocks/builtins/MapBlock.d.ts +1 -1
  162. package/dist/blocks/builtins/MapBlock.js +3 -3
  163. package/dist/blocks/builtins/NewsListBlock.d.ts +1 -1
  164. package/dist/blocks/builtins/NewsListBlock.js +4 -4
  165. package/dist/blocks/builtins/Newsletter.d.ts +1 -1
  166. package/dist/blocks/builtins/Newsletter.js +7 -7
  167. package/dist/blocks/builtins/Newsletter.js.map +1 -1
  168. package/dist/blocks/builtins/OpenNowBadge.d.ts +1 -1
  169. package/dist/blocks/builtins/OpenNowBadge.js +8 -3
  170. package/dist/blocks/builtins/OpenNowBadge.js.map +1 -1
  171. package/dist/blocks/builtins/OpeningHours.d.ts +1 -1
  172. package/dist/blocks/builtins/OpeningHours.js +6 -6
  173. package/dist/blocks/builtins/PdfBlock.d.ts +1 -1
  174. package/dist/blocks/builtins/PdfBlock.js +3 -3
  175. package/dist/blocks/builtins/Portfolio.d.ts +1 -1
  176. package/dist/blocks/builtins/Portfolio.js +5 -5
  177. package/dist/blocks/builtins/Pricing.d.ts +2 -2
  178. package/dist/blocks/builtins/Pricing.js +5 -5
  179. package/dist/blocks/builtins/Pricing.js.map +1 -1
  180. package/dist/blocks/builtins/RichTextBlock.d.ts +1 -1
  181. package/dist/blocks/builtins/RichTextBlock.js +2 -2
  182. package/dist/blocks/builtins/RichTextBlock.js.map +1 -1
  183. package/dist/blocks/builtins/Search.d.ts +1 -1
  184. package/dist/blocks/builtins/Search.js +9 -7
  185. package/dist/blocks/builtins/Search.js.map +1 -1
  186. package/dist/blocks/builtins/Sections.d.ts +4 -4
  187. package/dist/blocks/builtins/Sections.js +4 -4
  188. package/dist/blocks/builtins/Slider.d.ts +1 -1
  189. package/dist/blocks/builtins/Slider.js +3 -3
  190. package/dist/blocks/builtins/Spacer.d.ts +1 -1
  191. package/dist/blocks/builtins/Spacer.js +2 -2
  192. package/dist/blocks/builtins/SymbolRef.d.ts +1 -1
  193. package/dist/blocks/builtins/SymbolRef.js +31 -17
  194. package/dist/blocks/builtins/SymbolRef.js.map +1 -1
  195. package/dist/blocks/builtins/Team.d.ts +1 -1
  196. package/dist/blocks/builtins/Team.js +4 -4
  197. package/dist/blocks/builtins/Testimonials.d.ts +2 -2
  198. package/dist/blocks/builtins/Testimonials.js +4 -4
  199. package/dist/blocks/builtins/Video.d.ts +2 -2
  200. package/dist/blocks/builtins/Video.js +4 -4
  201. package/dist/blocks/builtins/Video.js.map +1 -1
  202. package/dist/blocks/builtins/WhatsappChat.d.ts +1 -1
  203. package/dist/blocks/builtins/WhatsappChat.js +2 -2
  204. package/dist/blocks/builtins/Widgets.d.ts +12 -12
  205. package/dist/blocks/builtins/Widgets.js +19 -19
  206. package/dist/blocks/builtins/Widgets.js.map +1 -1
  207. package/dist/blocks/builtins/consentEmbed.js +2 -2
  208. package/dist/blocks/builtins/index.d.ts +40 -40
  209. package/dist/blocks/builtins/index.js +40 -40
  210. package/dist/blocks/builtins/lib/submitForm.js +5 -3
  211. package/dist/blocks/builtins/lib/submitForm.js.map +1 -1
  212. package/dist/blocks/context.d.ts +1 -1
  213. package/dist/blocks/customTemplate/definition.d.ts +3 -3
  214. package/dist/blocks/customTemplate/definition.js +6 -6
  215. package/dist/blocks/customTemplate/definition.js.map +1 -1
  216. package/dist/blocks/customTemplate/interpret.js +1 -1
  217. package/dist/blocks/index.d.ts +5 -5
  218. package/dist/blocks/index.js +5 -5
  219. package/dist/blocks/registry.d.ts +17 -7
  220. package/dist/blocks/registry.js +2 -1
  221. package/dist/blocks/registry.js.map +1 -1
  222. package/dist/components/EditBar.js +3 -3
  223. package/dist/components/FeedbackWidget.js +4 -4
  224. package/dist/components/News.d.ts +1 -1
  225. package/dist/components/News.js +50 -37
  226. package/dist/components/News.js.map +1 -1
  227. package/dist/components/PageView.js +11 -8
  228. package/dist/components/PageView.js.map +1 -1
  229. package/dist/components/ResponsiveImage.d.ts +1 -1
  230. package/dist/components/site/SiteFooter.d.ts +1 -1
  231. package/dist/components/site/SiteFooter.js +2 -2
  232. package/dist/config.d.ts +1 -1
  233. package/dist/demo/DemoStudio.js +3 -3
  234. package/dist/demo/demoStore.d.ts +3 -3
  235. package/dist/demo/index.d.ts +2 -2
  236. package/dist/demo/index.js +2 -2
  237. package/dist/editor/EditorProvider.js +1 -1
  238. package/dist/firebase/admin.d.ts +0 -2
  239. package/dist/firebase/admin.js +0 -6
  240. package/dist/firebase/admin.js.map +1 -1
  241. package/dist/firebase/appcheck.d.ts +1 -1
  242. package/dist/firebase/appcheck.js +5 -3
  243. package/dist/firebase/appcheck.js.map +1 -1
  244. package/dist/firebase/client.d.ts +2 -1
  245. package/dist/firebase/client.js +2 -14
  246. package/dist/firebase/client.js.map +1 -1
  247. package/dist/firebase/config.d.ts +31 -0
  248. package/dist/firebase/config.js +39 -0
  249. package/dist/firebase/config.js.map +1 -0
  250. package/dist/firebase/feedback.js +5 -3
  251. package/dist/firebase/feedback.js.map +1 -1
  252. package/dist/i18n/overlay.d.ts +2 -2
  253. package/dist/i18n/overlay.js +6 -4
  254. package/dist/i18n/overlay.js.map +1 -1
  255. package/dist/index.d.ts +45 -44
  256. package/dist/index.js +35 -33
  257. package/dist/index.js.map +1 -1
  258. package/dist/lock/DemoLock.js +1 -1
  259. package/dist/modules/builtins.d.ts +1 -1
  260. package/dist/modules/builtins.js +53 -55
  261. package/dist/modules/builtins.js.map +1 -1
  262. package/dist/modules/consent/ConsentBanner.js +1 -1
  263. package/dist/modules/consent/useConsent.js +1 -1
  264. package/dist/modules/context.d.ts +1 -1
  265. package/dist/modules/contract.d.ts +1 -1
  266. package/dist/modules/defineModule.d.ts +1 -1
  267. package/dist/modules/index.d.ts +9 -9
  268. package/dist/modules/index.js +8 -8
  269. package/dist/modules/overrides.d.ts +1 -1
  270. package/dist/modules/registry.d.ts +2 -7
  271. package/dist/modules/registry.js +2 -1
  272. package/dist/modules/registry.js.map +1 -1
  273. package/dist/modules/shop/blocks/Cart.d.ts +1 -1
  274. package/dist/modules/shop/blocks/Cart.js +6 -6
  275. package/dist/modules/shop/blocks/Checkout.d.ts +1 -1
  276. package/dist/modules/shop/blocks/Checkout.js +6 -6
  277. package/dist/modules/shop/blocks/Donation.d.ts +1 -1
  278. package/dist/modules/shop/blocks/Donation.js +5 -5
  279. package/dist/modules/shop/blocks/OrderStatus.d.ts +1 -1
  280. package/dist/modules/shop/blocks/OrderStatus.js +5 -5
  281. package/dist/modules/shop/blocks/ProductDetail.d.ts +1 -1
  282. package/dist/modules/shop/blocks/ProductDetail.js +8 -8
  283. package/dist/modules/shop/blocks/ProductGrid.d.ts +3 -3
  284. package/dist/modules/shop/blocks/ProductGrid.js +7 -7
  285. package/dist/modules/shop/blocks/ShopCategories.d.ts +1 -1
  286. package/dist/modules/shop/blocks/ShopCategories.js +6 -6
  287. package/dist/modules/shop/index.d.ts +5 -5
  288. package/dist/modules/shop/index.js +5 -5
  289. package/dist/modules/shop/lib.d.ts +2 -2
  290. package/dist/modules/shop/lib.js +18 -13
  291. package/dist/modules/shop/lib.js.map +1 -1
  292. package/dist/modules/shop/module.d.ts +1 -1
  293. package/dist/modules/shop/module.js +9 -9
  294. package/dist/modules/shop/settings.d.ts +18 -50
  295. package/dist/modules/shop/settings.js +16 -16
  296. package/dist/modules/shop/settings.js.map +1 -1
  297. package/dist/modules/shop/strings.js +1 -1
  298. package/dist/modules/shop/types.d.ts +1 -1
  299. package/dist/modules/types.d.ts +2 -2
  300. package/dist/patterns/builtins.d.ts +1 -1
  301. package/dist/patterns/builtins.js +1 -1
  302. package/dist/patterns/index.d.ts +2 -2
  303. package/dist/patterns/index.js +2 -2
  304. package/dist/render/PageRenderer.d.ts +2 -2
  305. package/dist/render/PageRenderer.js +1 -1
  306. package/dist/render/PublishedPage.d.ts +1 -1
  307. package/dist/render/PublishedPage.js +2 -2
  308. package/dist/schema/blocks.d.ts +6793 -15095
  309. package/dist/schema/blocks.js +335 -355
  310. package/dist/schema/blocks.js.map +1 -1
  311. package/dist/schema/customBlocks.d.ts +10 -29
  312. package/dist/schema/customBlocks.js +14 -13
  313. package/dist/schema/customBlocks.js.map +1 -1
  314. package/dist/schema/fields.d.ts +10 -16
  315. package/dist/schema/fields.js +37 -37
  316. package/dist/schema/fields.js.map +1 -1
  317. package/dist/schema/forms.d.ts +62 -111
  318. package/dist/schema/forms.js +17 -17
  319. package/dist/schema/forms.js.map +1 -1
  320. package/dist/schema/index.d.ts +7 -7
  321. package/dist/schema/index.js +7 -7
  322. package/dist/schema/mail.d.ts +39 -87
  323. package/dist/schema/mail.js +4 -4
  324. package/dist/schema/mail.js.map +1 -1
  325. package/dist/schema/pages.d.ts +152 -674
  326. package/dist/schema/pages.js +26 -15
  327. package/dist/schema/pages.js.map +1 -1
  328. package/dist/schema/seo.d.ts +9 -27
  329. package/dist/schema/seo.js +7 -7
  330. package/dist/schema/seo.js.map +1 -1
  331. package/dist/schema/settings.d.ts +218 -1074
  332. package/dist/schema/settings.js +55 -55
  333. package/dist/schema/settings.js.map +1 -1
  334. package/dist/schema/shop.d.ts +470 -1214
  335. package/dist/schema/shop.js +124 -124
  336. package/dist/schema/shop.js.map +1 -1
  337. package/dist/schema/siteConfig.d.ts +73 -597
  338. package/dist/schema/siteConfig.js +17 -17
  339. package/dist/schema/siteConfig.js.map +1 -1
  340. package/dist/schema/symbols.d.ts +34 -191
  341. package/dist/schema/symbols.js +8 -8
  342. package/dist/schema/symbols.js.map +1 -1
  343. package/dist/server/auth.d.ts +17 -0
  344. package/dist/server/auth.js +11 -0
  345. package/dist/server/auth.js.map +1 -0
  346. package/dist/server/bundle.d.ts +15 -6
  347. package/dist/server/bundle.js +5 -1
  348. package/dist/server/bundle.js.map +1 -1
  349. package/dist/server/draft.d.ts +16 -0
  350. package/dist/server/draft.js +12 -0
  351. package/dist/server/draft.js.map +1 -0
  352. package/dist/server/env.js +8 -8
  353. package/dist/server/env.js.map +1 -1
  354. package/dist/server/index.d.ts +10 -8
  355. package/dist/server/index.js +12 -9
  356. package/dist/server/index.js.map +1 -1
  357. package/dist/server/pages.d.ts +14 -7
  358. package/dist/server/pages.js +183 -17
  359. package/dist/server/pages.js.map +1 -1
  360. package/dist/server/revalidate.d.ts +6 -0
  361. package/dist/server/revalidate.js +40 -0
  362. package/dist/server/revalidate.js.map +1 -0
  363. package/dist/server/seo.d.ts +3 -3
  364. package/dist/server/seo.js +1 -1
  365. package/dist/server/shop.d.ts +3 -3
  366. package/dist/server/shop.js +6 -3
  367. package/dist/server/shop.js.map +1 -1
  368. package/dist/settings/useSiteSettings.d.ts +2 -2
  369. package/dist/settings/useSiteSettings.js +48 -28
  370. package/dist/settings/useSiteSettings.js.map +1 -1
  371. package/dist/styles.css +4 -1
  372. package/dist/theme/presets.d.ts +1 -1
  373. package/package.json +35 -34
@@ -1,21 +1,21 @@
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";
1
+ import { AdminApp } from "./AdminApp.js";
2
+ import { PagesPanel } from "./pages/PagesPanel.js";
3
+ import { PageEditor } from "./pages/PageEditor.js";
4
+ import { MediaManager } from "./media/MediaManager.js";
5
+ import { FormsPanel } from "./forms/FormsPanel.js";
6
+ import { StatsPanel } from "./stats/StatsPanel.js";
7
+ import { SettingsPanel } from "./settings/SettingsPanel.js";
8
+ import { SuperadminPanel } from "./SuperadminPanel.js";
9
9
  import {
10
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";
11
+ } from "./panelRegistry.js";
12
+ import { NewsEditor } from "./NewsEditor.js";
13
+ import { RichTextEditor } from "./editors/RichTextEditor.js";
14
+ import { builtinBlockEditors } from "./blockEditors/index.js";
15
+ import { uploadImage } from "./lib/upload.js";
16
+ import { ConfirmHost, useConfirm } from "./ui/ConfirmDialog.js";
17
+ import { ArrayEditor } from "./ui/ArrayEditor.js";
18
+ import * as ui from "./ui/index.js";
19
19
  export {
20
20
  AdminApp,
21
21
  ArrayEditor,
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { collection, doc, getDoc, getDocs } from "firebase/firestore";
3
- import { getDb } from "../../firebase/client";
3
+ import { getDb } from "../../firebase/client.js";
4
4
  async function findMediaUsage(siteId, item) {
5
5
  const db = getDb();
6
6
  if (!db) return [];
@@ -1,4 +1,4 @@
1
- import type { Page } from "../../schema/pages";
1
+ import type { Page } from "../../schema/pages.js";
2
2
  export type DraftPage = Page & {
3
3
  id: string;
4
4
  published?: boolean;
@@ -15,9 +15,10 @@ import {
15
15
  where
16
16
  } from "firebase/firestore";
17
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";
18
+ import { getDb, getFirebaseApp } from "../../firebase/client.js";
19
+ import { functionsRegion } from "../../firebase/region.js";
20
+ import { isDemoActive, demoIdForPath, resolveDemoPage, saveDemoDraft } from "../../demo/demoStore.js";
21
+ import { sanitizeDraftBlocks } from "./sanitizeDraft.js";
21
22
  function subscribePages(siteId, cb) {
22
23
  const db = getDb();
23
24
  if (!db) {
@@ -51,7 +52,7 @@ async function getDraftByPath(siteId, path) {
51
52
  query(collection(db, "sites", siteId, "drafts"), where("path", "==", path), limit(1))
52
53
  );
53
54
  const d = snap.docs[0];
54
- return d ? { id: d.id, ...d.data() } : null;
55
+ return d ? sanitizeDraftBlocks({ id: d.id, ...d.data() }) : null;
55
56
  }
56
57
  function subscribeDraft(siteId, pageId, cb) {
57
58
  if (isDemoActive()) return () => {
@@ -61,11 +62,14 @@ function subscribeDraft(siteId, pageId, cb) {
61
62
  };
62
63
  return onSnapshot(doc(db, "sites", siteId, "drafts", pageId), (snap) => {
63
64
  cb(
64
- snap.exists() ? { id: snap.id, ...snap.data() } : null,
65
+ snap.exists() ? sanitizeDraftBlocks({ id: snap.id, ...snap.data() }) : null,
65
66
  { fromServer: !snap.metadata.hasPendingWrites }
66
67
  );
67
68
  });
68
69
  }
70
+ function zonderUndefined(o) {
71
+ return Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0));
72
+ }
69
73
  async function createPage(siteId, input) {
70
74
  const db = getDb();
71
75
  if (!db) throw new Error("Firebase niet geconfigureerd.");
@@ -75,7 +79,7 @@ async function createPage(siteId, input) {
75
79
  status: "draft",
76
80
  blocks: [],
77
81
  published: false,
78
- ...input,
82
+ ...zonderUndefined(input),
79
83
  updatedAt: serverTimestamp()
80
84
  });
81
85
  return ref.id;
@@ -89,7 +93,7 @@ async function saveDraft(siteId, pageId, partial) {
89
93
  if (!db) return;
90
94
  await setDoc(
91
95
  doc(db, "sites", siteId, "drafts", pageId),
92
- { ...partial, updatedAt: serverTimestamp() },
96
+ { ...zonderUndefined(partial), updatedAt: serverTimestamp() },
93
97
  { merge: true }
94
98
  );
95
99
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/lib/pagesClient.ts"],"sourcesContent":["\"use client\";\n\nimport {\n addDoc,\n collection,\n deleteDoc,\n doc,\n getDoc,\n getDocs,\n limit,\n onSnapshot,\n orderBy,\n query,\n serverTimestamp,\n setDoc,\n where,\n} from \"firebase/firestore\";\nimport { 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 // Draft direct (client mag drafts beheren); de gepubliceerde pagina via de Cloud Function —\n // die verwijdert óók de versions-subcollectie (Firestore cascadeert niet; directe client-deletes\n // lieten wees-versiegeschiedenis achter en zijn nu in de rules dichtgezet).\n await deleteDoc(doc(db, \"sites\", siteId, \"drafts\", pageId));\n const app = getFirebaseApp();\n if (!app) return;\n try {\n const fn = httpsCallable(getFunctions(app, functionsRegion()), \"deletePage\");\n await fn({ siteId, pageId });\n } catch {\n /* gepubliceerde versie bestond niet — negeren */\n }\n}\n\n/**\n * Herstelt een eerdere versie naar de WERKVERSIE (draft). De gebruiker moet daarna zelf\n * publiceren — zo is herstellen veilig en omkeerbaar (de live site verandert pas bij Publiceren).\n * Geeft de herstelde velden terug zodat de editor zijn lokale state kan bijwerken.\n */\nexport async function restoreVersion(\n siteId: string,\n pageId: string,\n versionId: string,\n): Promise<Partial<Page>> {\n const db = getDb();\n if (!db) throw new Error(\"Firebase niet geconfigureerd.\");\n const snap = await getDoc(doc(db, \"sites\", siteId, \"pages\", pageId, \"versions\", versionId));\n if (!snap.exists()) throw new Error(\"Versie niet gevonden.\");\n const s = (snap.data().snapshot ?? {}) as Record<string, unknown>;\n const restored: Partial<Page> = {\n title: s.title as string,\n slug: s.slug as string,\n path: s.path as string,\n seo: s.seo as Page[\"seo\"],\n blocks: s.blocks as Page[\"blocks\"],\n order: s.order as number,\n };\n await saveDraft(siteId, pageId, restored);\n return restored;\n}\n\n/** Publiceren via de Cloud Function (server-side validatie + versie-snapshot + audit). */\nexport async function publishPage(siteId: string, pageId: string): Promise<void> {\n // Demo-modus: er wordt niets echt gepubliceerd; de lokale bewerking is al zichtbaar.\n if (isDemoActive()) {\n saveDemoDraft(siteId, pageId, {});\n return;\n }\n const app = getFirebaseApp();\n if (!app) throw new Error(\"Firebase niet geconfigureerd.\");\n const fn = 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;AAIT,QAAM,UAAU,IAAI,IAAI,SAAS,QAAQ,UAAU,MAAM,CAAC;AAC1D,QAAM,MAAM,eAAe;AAC3B,MAAI,CAAC,IAAK;AACV,MAAI;AACF,UAAM,KAAK,cAAc,aAAa,KAAK,gBAAgB,CAAC,GAAG,YAAY;AAC3E,UAAM,GAAG,EAAE,QAAQ,OAAO,CAAC;AAAA,EAC7B,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":[]}
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\";\nimport { sanitizeDraftBlocks } from \"./sanitizeDraft\";\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 // Rich text uit een werkversie is nooit server-side gesaneerd (zie sanitizeDraft.ts).\n return d ? sanitizeDraftBlocks({ id: d.id, ...(d.data() as Omit<DraftPage, \"id\">) }) : null;\n}\n\n/**\n * Live-listener op één werkversie — voor conflictdetectie in de visuele editor (twee tabs of\n * twee editors tegelijk). `fromServer` is false voor de lokale echo van een eigen write\n * (hasPendingWrites), zodat de aanroeper eigen saves kan negeren.\n */\nexport function subscribeDraft(\n siteId: string,\n pageId: string,\n cb: (draft: DraftPage | null, meta: { fromServer: boolean }) => void,\n): () => void {\n if (isDemoActive()) return () => {};\n const db = getDb();\n if (!db) return () => {};\n return onSnapshot(doc(db, \"sites\", siteId, \"drafts\", pageId), (snap) => {\n cb(\n snap.exists() ? sanitizeDraftBlocks({ id: snap.id, ...(snap.data() as Omit<DraftPage, \"id\">) }) : null,\n { fromServer: !snap.metadata.hasPendingWrites },\n );\n });\n}\n\n/**\n * Firestore WEIGERT `undefined` als veldwaarde: `setDoc({ seo: undefined })` gooit\n * \"Unsupported field value: undefined\" en de hele schrijfactie mislukt. De editors geven hun\n * pagina-state als geheel door (`{ title, slug, path, seo, blocks }`), en bij een nieuwe pagina is\n * `seo` gewoon nog niet ingevuld — dus liep élke eerste publicatie van een nieuwe pagina stuk op\n * de autosave die eraan voorafgaat. Gevonden op de staging-deploy van groenhof: \"Publiceren\n * mislukt — Unsupported field value: undefined (found in field seo)\".\n *\n * Weglaten is hier ook de JUISTE semantiek: deze writes zijn `merge: true`, en \"veld niet\n * meegestuurd\" betekent daar \"laat staan wat er stond\". Een veld écht wissen doe je met\n * `deleteField()`, niet met `undefined` — dat onderscheid blijft zo intact.\n */\nfunction zonderUndefined<T extends Record<string, unknown>>(o: T): Partial<T> {\n return Object.fromEntries(Object.entries(o).filter(([, v]) => v !== undefined)) as Partial<T>;\n}\n\nexport async function createPage(siteId: string, input: Partial<Page> & { title: string; slug: string; path: string }) {\n const db = getDb();\n if (!db) throw new Error(\"Firebase niet geconfigureerd.\");\n const ref = await addDoc(collection(db, \"sites\", siteId, \"drafts\"), {\n parentId: null,\n order: Date.now(),\n status: \"draft\",\n blocks: [],\n published: false,\n ...zonderUndefined(input),\n updatedAt: serverTimestamp(),\n });\n return ref.id;\n}\n\nexport async function saveDraft(siteId: string, pageId: string, partial: Partial<Page>) {\n // Demo-modus: bewaar de bewerking alleen lokaal in de browser (niet in Firebase).\n if (isDemoActive()) {\n saveDemoDraft(siteId, pageId, partial);\n return;\n }\n const db = getDb();\n if (!db) return;\n await setDoc(\n doc(db, \"sites\", siteId, \"drafts\", pageId),\n { ...zonderUndefined(partial), updatedAt: serverTimestamp() },\n { merge: true },\n );\n}\n\nexport async function deletePage(siteId: string, pageId: string) {\n const db = getDb();\n if (!db) return;\n // Draft direct (client mag drafts beheren); de gepubliceerde pagina via de Cloud Function —\n // die verwijdert óók de versions-subcollectie (Firestore cascadeert niet; directe client-deletes\n // lieten wees-versiegeschiedenis achter en zijn nu in de rules dichtgezet).\n await deleteDoc(doc(db, \"sites\", siteId, \"drafts\", pageId));\n const app = getFirebaseApp();\n if (!app) return;\n try {\n const fn = httpsCallable(getFunctions(app, functionsRegion()), \"deletePage\");\n await fn({ siteId, pageId });\n } catch {\n /* gepubliceerde versie bestond niet — negeren */\n }\n}\n\n/**\n * Herstelt een eerdere versie naar de WERKVERSIE (draft). De gebruiker moet daarna zelf\n * publiceren — zo is herstellen veilig en omkeerbaar (de live site verandert pas bij Publiceren).\n * Geeft de herstelde velden terug zodat de editor zijn lokale state kan bijwerken.\n */\nexport async function restoreVersion(\n siteId: string,\n pageId: string,\n versionId: string,\n): Promise<Partial<Page>> {\n const db = getDb();\n if (!db) throw new Error(\"Firebase niet geconfigureerd.\");\n const snap = await getDoc(doc(db, \"sites\", siteId, \"pages\", pageId, \"versions\", versionId));\n if (!snap.exists()) throw new Error(\"Versie niet gevonden.\");\n const s = (snap.data().snapshot ?? {}) as Record<string, unknown>;\n const restored: Partial<Page> = {\n title: s.title as string,\n slug: s.slug as string,\n path: s.path as string,\n seo: s.seo as Page[\"seo\"],\n blocks: s.blocks as Page[\"blocks\"],\n order: s.order as number,\n };\n await saveDraft(siteId, pageId, restored);\n return restored;\n}\n\n/** Publiceren via de Cloud Function (server-side validatie + versie-snapshot + audit). */\nexport async function publishPage(siteId: string, pageId: string): Promise<void> {\n // Demo-modus: er wordt niets echt gepubliceerd; de lokale bewerking is al zichtbaar.\n if (isDemoActive()) {\n saveDemoDraft(siteId, pageId, {});\n return;\n }\n const app = getFirebaseApp();\n if (!app) throw new Error(\"Firebase niet geconfigureerd.\");\n const fn = httpsCallable(getFunctions(app, functionsRegion()), \"publishPage\");\n await fn({ siteId, pageId });\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Saneert de blokken van een werkversie. Buiten de browser (geen DOM) is DOMPurify niet
3
+ * ondersteund en zou `sanitize` de invoer ONGEWIJZIGD teruggeven — dan liever niets doen dan
4
+ * stilzwijgend suggereren dat er gesaneerd is. Dit pad draait alleen client-side (de editor), dus
5
+ * in de praktijk is DOMPurify er altijd.
6
+ */
7
+ export declare function sanitizeDraftBlocks<T extends {
8
+ blocks?: unknown;
9
+ } | null>(draft: T): T;
@@ -0,0 +1,28 @@
1
+ "use client";
2
+ import DOMPurify from "dompurify";
3
+ function isRichTextKey(key) {
4
+ return key === "html" || key.endsWith("Html");
5
+ }
6
+ function isPlainObject(v) {
7
+ const proto = Object.getPrototypeOf(v);
8
+ return proto === Object.prototype || proto === null;
9
+ }
10
+ function deepSanitize(value) {
11
+ if (Array.isArray(value)) return value.map(deepSanitize);
12
+ if (value && typeof value === "object" && isPlainObject(value)) {
13
+ const out = {};
14
+ for (const [key, v] of Object.entries(value)) {
15
+ out[key] = isRichTextKey(key) && typeof v === "string" ? DOMPurify.sanitize(v) : deepSanitize(v);
16
+ }
17
+ return out;
18
+ }
19
+ return value;
20
+ }
21
+ function sanitizeDraftBlocks(draft) {
22
+ if (!draft || !DOMPurify.isSupported || !Array.isArray(draft.blocks)) return draft;
23
+ return { ...draft, blocks: draft.blocks.map(deepSanitize) };
24
+ }
25
+ export {
26
+ sanitizeDraftBlocks
27
+ };
28
+ //# sourceMappingURL=sanitizeDraft.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/admin/lib/sanitizeDraft.ts"],"sourcesContent":["\"use client\";\n\nimport DOMPurify from \"dompurify\";\n\n/**\n * Saneert de rich text in een WERKVERSIE bij het inlezen.\n *\n * Waarom dit nodig is: gepubliceerde pagina's zijn veilig — `publishPage` haalt ze door\n * `sanitizeBlocks` en de Firestore-rules maken `pages` Functions-only. Werkversies niet: de rules\n * staan editors toe rechtstreeks naar `drafts/{pageId}` te schrijven, en `validPage()` kan geen\n * HTML saneren (rules kennen alleen types en lengtes). De enige verdediging op dat pad was\n * DOMPurify bij het COMMITTEN in de editor, en die omzeilt een kwaadwillende editor door direct\n * met de Firebase-SDK te schrijven. Een andere editor of de siteadmin voerde die HTML vervolgens\n * uit in zijn eigen sessie zodra hij de werkversie opende.\n *\n * De render is dus de veiligheidsgrens, niet de herkomst — dezelfde redenering die `News.tsx` al\n * volgt. Sanering hier dekt alles wat de editor met de werkversie doet: het canvas, het zijpaneel\n * en het vertaalpaneel.\n *\n * Conventie gelijk aan de server (`functions/src/lib/sanitize.ts`): velden die `html` heten of op\n * `Html` eindigen zijn rich text. Loopt op willekeurige diepte, zodat ook geneste layout-blokken\n * (`withSidebar` → main/aside, `tabs` → tabs[].blocks) meegaan.\n */\nfunction isRichTextKey(key: string): boolean {\n return key === \"html\" || key.endsWith(\"Html\");\n}\n\n/** Alleen plain objects doorlopen; class-instances (Firestore Timestamp e.d.) ongemoeid laten. */\nfunction isPlainObject(v: object): boolean {\n const proto = Object.getPrototypeOf(v);\n return proto === Object.prototype || proto === null;\n}\n\nfunction deepSanitize(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(deepSanitize);\n if (value && typeof value === \"object\" && isPlainObject(value)) {\n const out: Record<string, unknown> = {};\n for (const [key, v] of Object.entries(value as Record<string, unknown>)) {\n out[key] = isRichTextKey(key) && typeof v === \"string\" ? DOMPurify.sanitize(v) : deepSanitize(v);\n }\n return out;\n }\n return value;\n}\n\n/**\n * Saneert de blokken van een werkversie. Buiten de browser (geen DOM) is DOMPurify niet\n * ondersteund en zou `sanitize` de invoer ONGEWIJZIGD teruggeven — dan liever niets doen dan\n * stilzwijgend suggereren dat er gesaneerd is. Dit pad draait alleen client-side (de editor), dus\n * in de praktijk is DOMPurify er altijd.\n */\nexport function sanitizeDraftBlocks<T extends { blocks?: unknown } | null>(draft: T): T {\n if (!draft || !DOMPurify.isSupported || !Array.isArray(draft.blocks)) return draft;\n return { ...draft, blocks: draft.blocks.map(deepSanitize) };\n}\n"],"mappings":";AAEA,OAAO,eAAe;AAqBtB,SAAS,cAAc,KAAsB;AAC3C,SAAO,QAAQ,UAAU,IAAI,SAAS,MAAM;AAC9C;AAGA,SAAS,cAAc,GAAoB;AACzC,QAAM,QAAQ,OAAO,eAAe,CAAC;AACrC,SAAO,UAAU,OAAO,aAAa,UAAU;AACjD;AAEA,SAAS,aAAa,OAAyB;AAC7C,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO,MAAM,IAAI,YAAY;AACvD,MAAI,SAAS,OAAO,UAAU,YAAY,cAAc,KAAK,GAAG;AAC9D,UAAM,MAA+B,CAAC;AACtC,eAAW,CAAC,KAAK,CAAC,KAAK,OAAO,QAAQ,KAAgC,GAAG;AACvE,UAAI,GAAG,IAAI,cAAc,GAAG,KAAK,OAAO,MAAM,WAAW,UAAU,SAAS,CAAC,IAAI,aAAa,CAAC;AAAA,IACjG;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAQO,SAAS,oBAA2D,OAAa;AACtF,MAAI,CAAC,SAAS,CAAC,UAAU,eAAe,CAAC,MAAM,QAAQ,MAAM,MAAM,EAAG,QAAO;AAC7E,SAAO,EAAE,GAAG,OAAO,QAAQ,MAAM,OAAO,IAAI,YAAY,EAAE;AAC5D;","names":[]}
@@ -1,4 +1,4 @@
1
- import { type StoredSymbol } from "../../schema/symbols";
1
+ import { type StoredSymbol } from "../../schema/symbols.js";
2
2
  export declare function subscribeSymbols(siteId: string, cb: (symbols: StoredSymbol[]) => void): () => void;
3
3
  export declare function getSymbolOnce(siteId: string, symbolId: string): Promise<StoredSymbol | null>;
4
4
  export declare function saveSymbol(input: {
@@ -1,9 +1,9 @@
1
1
  "use client";
2
2
  import { collection, doc, getDoc, onSnapshot } from "firebase/firestore";
3
3
  import { getFunctions, httpsCallable } from "firebase/functions";
4
- import { getDb, getFirebaseApp } from "../../firebase/client";
5
- import { functionsRegion } from "../../firebase/region";
6
- import { SymbolSchema } from "../../schema/symbols";
4
+ import { getDb, getFirebaseApp } from "../../firebase/client.js";
5
+ import { functionsRegion } from "../../firebase/region.js";
6
+ import { SymbolSchema } from "../../schema/symbols.js";
7
7
  function subscribeSymbols(siteId, cb) {
8
8
  const db = getDb();
9
9
  if (!db) {
@@ -1,9 +1,9 @@
1
1
  "use client";
2
2
  import { ref as storageRef, uploadBytes, getDownloadURL } from "firebase/storage";
3
3
  import { addDoc, collection, deleteDoc, doc, serverTimestamp, updateDoc } from "firebase/firestore";
4
- import { getBucket, getDb } from "../../firebase/client";
5
- import { addDemoMedia, isDemoActive } from "../../demo/demoStore";
6
- import { compressImage } from "./compressImage";
4
+ import { getBucket, getDb } from "../../firebase/client.js";
5
+ import { addDemoMedia, isDemoActive } from "../../demo/demoStore.js";
6
+ import { compressImage } from "./compressImage.js";
7
7
  const MAX_UPLOAD_BYTES = 15 * 1024 * 1024;
8
8
  const MAX_STORED_BYTES = 10 * 1024 * 1024;
9
9
  async function uploadImage(siteId, file) {
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Meldt of er ergens in het document een Radix-dialoog openstaat (mediakiezer, bevestiging,
3
+ * patroonkiezer). Die renderen via een portal — dus BUITEN de editor-shell.
4
+ *
5
+ * Waarom dit bestaat: de visuele editor vangt focus (`useFocusTrap`), en Radix' eigen
6
+ * `FocusScope` doet dat óók binnen zijn dialoog. Zouden beide tegelijk actief zijn, dan trekken
7
+ * ze bij elke Tab aan dezelfde focus — de gebruiker ziet de focus heen en weer springen tussen
8
+ * dialoog en editor. De editor-trap staat daarom uit zolang een dialoog "boven" hem actief is;
9
+ * dan is Radix de enige die focus beheert, precies zoals bij geneste dialogen hoort.
10
+ *
11
+ * Bewust op DOM-niveau: `ConfirmHost` houdt zijn open-state intern en de patroonkiezer zit weer
12
+ * elders, dus er is geen gedeelde React-state om op te leunen. Het `data-state`-attribuut is de
13
+ * enige bron die alle drie de dialogen gemeen hebben.
14
+ */
15
+ export declare function useNestedDialogOpen(): boolean;
@@ -0,0 +1,23 @@
1
+ "use client";
2
+ import { useEffect, useState } from "react";
3
+ const OPEN_DIALOG = 'dialog[open], [role="dialog"][data-state="open"], [role="alertdialog"][data-state="open"]';
4
+ function useNestedDialogOpen() {
5
+ const [open, setOpen] = useState(false);
6
+ useEffect(() => {
7
+ const check = () => setOpen(!!document.querySelector(OPEN_DIALOG));
8
+ check();
9
+ const observer = new MutationObserver(check);
10
+ observer.observe(document.body, {
11
+ subtree: true,
12
+ childList: true,
13
+ attributes: true,
14
+ attributeFilter: ["data-state", "role", "open"]
15
+ });
16
+ return () => observer.disconnect();
17
+ }, []);
18
+ return open;
19
+ }
20
+ export {
21
+ useNestedDialogOpen
22
+ };
23
+ //# sourceMappingURL=useNestedDialog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/admin/lib/useNestedDialog.ts"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\n\n/**\n * Een open dialoog herkennen. Astryx rendert een NATIEVE `<dialog>` en opent hem met\n * `showModal()`, waarna het element het `open`-attribuut draagt — dat is de bron van waarheid.\n * De `[data-state=\"open\"]`-vormen blijven meelopen zodat een eventuele Radix-dialoog (of een\n * klantrepo die er nog een gebruikt) net zo goed herkend wordt.\n */\nconst OPEN_DIALOG = 'dialog[open], [role=\"dialog\"][data-state=\"open\"], [role=\"alertdialog\"][data-state=\"open\"]';\n\n/**\n * Meldt of er ergens in het document een Radix-dialoog openstaat (mediakiezer, bevestiging,\n * patroonkiezer). Die renderen via een portal — dus BUITEN de editor-shell.\n *\n * Waarom dit bestaat: de visuele editor vangt focus (`useFocusTrap`), en Radix' eigen\n * `FocusScope` doet dat óók binnen zijn dialoog. Zouden beide tegelijk actief zijn, dan trekken\n * ze bij elke Tab aan dezelfde focus — de gebruiker ziet de focus heen en weer springen tussen\n * dialoog en editor. De editor-trap staat daarom uit zolang een dialoog \"boven\" hem actief is;\n * dan is Radix de enige die focus beheert, precies zoals bij geneste dialogen hoort.\n *\n * Bewust op DOM-niveau: `ConfirmHost` houdt zijn open-state intern en de patroonkiezer zit weer\n * elders, dus er is geen gedeelde React-state om op te leunen. Het `data-state`-attribuut is de\n * enige bron die alle drie de dialogen gemeen hebben.\n */\nexport function useNestedDialogOpen(): boolean {\n const [open, setOpen] = useState(false);\n\n useEffect(() => {\n const check = () => setOpen(!!document.querySelector(OPEN_DIALOG));\n check();\n // Alleen op attribuut- en kindwijzigingen letten: Radix wisselt `data-state` op hetzelfde\n // element en voegt/verwijdert het portal. Geen character-data, dat is puur ruis.\n const observer = new MutationObserver(check);\n observer.observe(document.body, {\n subtree: true,\n childList: true,\n attributes: true,\n attributeFilter: [\"data-state\", \"role\", \"open\"],\n });\n return () => observer.disconnect();\n }, []);\n\n return open;\n}\n"],"mappings":";AAEA,SAAS,WAAW,gBAAgB;AAQpC,MAAM,cAAc;AAgBb,SAAS,sBAA+B;AAC7C,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,KAAK;AAEtC,YAAU,MAAM;AACd,UAAM,QAAQ,MAAM,QAAQ,CAAC,CAAC,SAAS,cAAc,WAAW,CAAC;AACjE,UAAM;AAGN,UAAM,WAAW,IAAI,iBAAiB,KAAK;AAC3C,aAAS,QAAQ,SAAS,MAAM;AAAA,MAC9B,SAAS;AAAA,MACT,WAAW;AAAA,MACX,YAAY;AAAA,MACZ,iBAAiB,CAAC,cAAc,QAAQ,MAAM;AAAA,IAChD,CAAC;AACD,WAAO,MAAM,SAAS,WAAW;AAAA,EACnC,GAAG,CAAC,CAAC;AAEL,SAAO;AACT;","names":[]}
@@ -1,19 +1,5 @@
1
- /**
2
- * Zod-introspectie voor automatisch gegenereerde formulieren. Gedeeld door:
3
- * - de `GenericEditor` (fallback-editor voor blokken zonder eigen `Editor`), en
4
- * - het `ModuleSettingsForm` (instellingen van een module in /beheer).
5
- *
6
- * Twee lagen:
7
- * - `introspectObjectSchema` (legacy, plat): simpele kinds — blijft bestaan voor module-settings.
8
- * - `introspectToFields` (5.0): levert de rijke **Field-IR** (schema/fields.ts, primo-port) met
9
- * échte repeaters/groepen (recursief), richtext-/image-/link-detectie en optionele per-block
10
- * hints (`defineBlock({ fields })`). De GenericEditor 2.0 rendert deze IR.
11
- *
12
- * Een veld krijgt zijn label uit `.describe("…")` op het schema; zonder beschrijving valt het
13
- * terug op de veldsleutel. Puur (geen React) — apart testbaar.
14
- */
15
- import type { ZodTypeAny } from "zod";
16
- import type { Field } from "../../schema/fields";
1
+ import type { $ZodType as ZodTypeAny } from "zod/v4/core";
2
+ import type { Field } from "../../schema/fields.js";
17
3
  export interface SchemaField {
18
4
  key: string;
19
5
  kind: "number" | "boolean" | "string" | "enum" | "json";
@@ -32,8 +18,8 @@ export interface SchemaField {
32
18
  * aanbieden; een aanroeper die alleen simpele velden wil, filtert op `kind !== "json"`.
33
19
  */
34
20
  export declare function introspectObjectSchema(schema: ZodTypeAny): SchemaField[];
35
- export type { FieldHints } from "../../schema/fields";
36
- import type { FieldHints } from "../../schema/fields";
21
+ export type { FieldHints } from "../../schema/fields.js";
22
+ import type { FieldHints } from "../../schema/fields.js";
37
23
  /**
38
24
  * Leidt de rijke Field-IR af uit een Zod-objectschema (5.0). Verschillen met de platte laag:
39
25
  * - `z.array(z.object)` → **repeater** met recursief geïntrospecteerde subvelden;
@@ -1,39 +1,59 @@
1
+ import { globalRegistry } from "zod/mini";
2
+ const defOf = (schema) => schema?._zod?.def ?? schema?._def;
3
+ function maxLengthOf(def) {
4
+ for (const c of def?.checks ?? []) {
5
+ const rule = c?._zod?.def;
6
+ if (rule?.check === "max_length") return rule.maximum;
7
+ }
8
+ return void 0;
9
+ }
10
+ const valuesOf = (def) => Object.values(def?.entries ?? {});
11
+ function shapeOf(schema) {
12
+ const def = defOf(schema);
13
+ if (def?.type !== "object" || !def.shape || typeof def.shape !== "object") return null;
14
+ return def.shape;
15
+ }
16
+ function describedAs(s) {
17
+ const uitRegister = globalRegistry.get(s)?.description;
18
+ return uitRegister ?? s?.description;
19
+ }
1
20
  function unwrap(schema) {
2
21
  let s = schema;
3
- let description = s?._def?.description;
4
- while (s?._def?.typeName === "ZodDefault" || s?._def?.typeName === "ZodOptional" || s?._def?.typeName === "ZodNullable") {
5
- s = s._def.innerType;
6
- description = description ?? s?._def?.description;
22
+ let description = describedAs(s);
23
+ for (; ; ) {
24
+ const def = defOf(s);
25
+ if (def?.type !== "default" && def?.type !== "optional" && def?.type !== "nullable") break;
26
+ s = def.innerType;
27
+ description = description ?? describedAs(s);
7
28
  }
8
- return { def: s?._def, description, inner: s };
29
+ return { def: defOf(s), description, inner: s };
9
30
  }
10
31
  function introspectObjectSchema(schema) {
11
- const def = schema._def;
12
- if (def?.typeName !== "ZodObject" || typeof def.shape !== "function") return [];
32
+ const shape = shapeOf(schema);
33
+ if (!shape) return [];
13
34
  const out = [];
14
- for (const [key, field] of Object.entries(def.shape())) {
35
+ for (const [key, field] of Object.entries(shape)) {
15
36
  if (key.startsWith("_")) continue;
16
37
  const { def: inner, description } = unwrap(field);
17
38
  const label = description ?? key;
18
39
  const fieldSchema = field;
19
- switch (inner?.typeName) {
20
- case "ZodNumber":
40
+ switch (inner?.type) {
41
+ case "number":
21
42
  out.push({ key, kind: "number", label, schema: fieldSchema });
22
43
  break;
23
- case "ZodBoolean":
44
+ case "boolean":
24
45
  out.push({ key, kind: "boolean", label, schema: fieldSchema });
25
46
  break;
26
- case "ZodEnum":
27
- out.push({ key, kind: "enum", label, options: inner.values, schema: fieldSchema });
47
+ case "enum":
48
+ out.push({ key, kind: "enum", label, options: valuesOf(inner), schema: fieldSchema });
28
49
  break;
29
- case "ZodString": {
30
- const max = inner.checks?.find((c) => c.kind === "max")?.value;
31
- out.push({ key, kind: "string", label, maxLength: max, schema: fieldSchema });
50
+ case "string": {
51
+ out.push({ key, kind: "string", label, maxLength: maxLengthOf(inner), schema: fieldSchema });
32
52
  break;
33
53
  }
34
- case "ZodArray":
35
- case "ZodObject":
36
- case "ZodRecord":
54
+ case "array":
55
+ case "object":
56
+ case "record":
37
57
  out.push({ key, kind: "json", label, schema: fieldSchema });
38
58
  break;
39
59
  default:
@@ -45,28 +65,28 @@ function introspectObjectSchema(schema) {
45
65
  const IMAGE_KEY = /image|photo|foto|logo|cover|avatar|poster/i;
46
66
  const URL_KEY = /^(href|url|link)$|Url$|Href$/;
47
67
  function introspectToFields(schema, hints, prefix = "") {
48
- const def = schema._def;
49
- if (def?.typeName !== "ZodObject" || typeof def.shape !== "function") return [];
68
+ const shape = shapeOf(schema);
69
+ if (!shape) return [];
50
70
  const out = [];
51
- for (const [key, raw] of Object.entries(def.shape())) {
71
+ for (const [key, raw] of Object.entries(shape)) {
52
72
  if (key.startsWith("_")) continue;
53
73
  const { def: inner, description, inner: innerSchema } = unwrap(raw);
54
74
  const label = description ?? key;
55
75
  const path = prefix ? `${prefix}.${key}` : key;
56
76
  const hint = hints?.[path];
57
77
  const push = (f) => out.push({ ...f, ...hint, key, label: hint?.label ?? label });
58
- switch (inner?.typeName) {
59
- case "ZodNumber":
78
+ switch (inner?.type) {
79
+ case "number":
60
80
  push({ key, label, type: "number" });
61
81
  break;
62
- case "ZodBoolean":
82
+ case "boolean":
63
83
  push({ key, label, type: "switch" });
64
84
  break;
65
- case "ZodEnum":
66
- push({ key, label, type: "select", options: (inner.values ?? []).map((v) => ({ value: v, label: v })) });
85
+ case "enum":
86
+ push({ key, label, type: "select", options: valuesOf(inner).map((v) => ({ value: v, label: v })) });
67
87
  break;
68
- case "ZodString": {
69
- const max = inner.checks?.find((c) => c.kind === "max")?.value;
88
+ case "string": {
89
+ const max = maxLengthOf(inner);
70
90
  if (key === "html" || key.endsWith("Html")) push({ key, label, type: "richtext" });
71
91
  else if (IMAGE_KEY.test(key)) push({ key, label, type: "image" });
72
92
  else if (URL_KEY.test(key)) push({ key, label, type: "url" });
@@ -74,7 +94,7 @@ function introspectToFields(schema, hints, prefix = "") {
74
94
  else push({ key, label, type: "text", maxLength: max });
75
95
  break;
76
96
  }
77
- case "ZodObject": {
97
+ case "object": {
78
98
  const sub = introspectToFields(innerSchema, hints, path);
79
99
  const isLink = sub.length === 2 && sub.some((f) => f.key === "label") && sub.some((f) => f.key === "href");
80
100
  if (isLink) push({ key, label, type: "link" });
@@ -82,10 +102,9 @@ function introspectToFields(schema, hints, prefix = "") {
82
102
  else push({ key, label, type: "json" });
83
103
  break;
84
104
  }
85
- case "ZodArray": {
86
- const el = inner.type ?? inner.element;
87
- const { def: elDef, inner: elSchema } = unwrap(el);
88
- if (elDef?.typeName === "ZodObject") {
105
+ case "array": {
106
+ const { def: elDef, inner: elSchema } = unwrap(inner?.element);
107
+ if (elDef?.type === "object") {
89
108
  const sub = introspectToFields(elSchema, hints, path);
90
109
  if (sub.length > 0) {
91
110
  push({ key, label, type: "repeater", fields: sub });
@@ -95,7 +114,7 @@ function introspectToFields(schema, hints, prefix = "") {
95
114
  push({ key, label, type: "json" });
96
115
  break;
97
116
  }
98
- case "ZodRecord":
117
+ case "record":
99
118
  push({ key, label, type: "json" });
100
119
  break;
101
120
  default:
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/lib/zodFields.ts"],"sourcesContent":["/**\n * Zod-introspectie voor automatisch gegenereerde formulieren. Gedeeld door:\n * - de `GenericEditor` (fallback-editor voor blokken zonder eigen `Editor`), en\n * - het `ModuleSettingsForm` (instellingen van een module in /beheer).\n *\n * Twee lagen:\n * - `introspectObjectSchema` (legacy, plat): simpele kinds — blijft bestaan voor module-settings.\n * - `introspectToFields` (5.0): levert de rijke **Field-IR** (schema/fields.ts, primo-port) met\n * échte repeaters/groepen (recursief), richtext-/image-/link-detectie en optionele per-block\n * hints (`defineBlock({ fields })`). De GenericEditor 2.0 rendert deze IR.\n *\n * Een veld krijgt zijn label uit `.describe(\"…\")` op het schema; zonder beschrijving valt het\n * terug op de veldsleutel. Puur (geen React) — apart testbaar.\n */\nimport type { ZodTypeAny } from \"zod\";\nimport type { Field } from \"../../schema/fields\";\n\nexport interface SchemaField {\n key: string;\n kind: \"number\" | \"boolean\" | \"string\" | \"enum\" | \"json\";\n /** Leesbaar label: `.describe()` van het schema, anders de veldsleutel. */\n label: string;\n options?: string[];\n /** Max-lengte van een string (bepaalt Input vs Textarea in de UI). */\n maxLength?: number;\n /** Het volledige veld-schema — voor validatie van json-velden vóór commit. */\n schema: ZodTypeAny;\n}\n\ninterface AnyDef {\n typeName?: string;\n innerType?: unknown;\n values?: string[];\n description?: string;\n checks?: { kind: string; value?: number }[];\n shape?: () => Record<string, unknown>;\n}\n\n/** Pakt het binnenste type uit ZodDefault/ZodOptional/ZodNullable; bewaart de buitenste beschrijving. */\nfunction unwrap(schema: unknown): { def?: AnyDef; description?: string; inner?: unknown } {\n let s = schema as { _def?: AnyDef };\n let description = s?._def?.description;\n while (\n s?._def?.typeName === \"ZodDefault\" ||\n s?._def?.typeName === \"ZodOptional\" ||\n s?._def?.typeName === \"ZodNullable\"\n ) {\n s = s._def.innerType as typeof s;\n description = description ?? s?._def?.description;\n }\n return { def: s?._def, description, inner: s };\n}\n\n/**\n * Leidt een formulier-beschrijving af uit een Zod-objectschema. Interne velden (`_key`/`_type`/\n * `_style`) worden overgeslagen — die beheren de editor-chrome en de StyleEditor. Complexe velden\n * (array/object/record) worden als `json` teruggegeven zodat een fallback-editor ze tóch kan\n * aanbieden; een aanroeper die alleen simpele velden wil, filtert op `kind !== \"json\"`.\n */\nexport function introspectObjectSchema(schema: ZodTypeAny): SchemaField[] {\n const def = (schema as { _def?: AnyDef })._def;\n if (def?.typeName !== \"ZodObject\" || typeof def.shape !== \"function\") return [];\n const out: SchemaField[] = [];\n for (const [key, field] of Object.entries(def.shape())) {\n if (key.startsWith(\"_\")) continue;\n const { def: inner, description } = unwrap(field);\n const label = description ?? key;\n const fieldSchema = field as ZodTypeAny;\n switch (inner?.typeName) {\n case \"ZodNumber\":\n out.push({ key, kind: \"number\", label, schema: fieldSchema });\n break;\n case \"ZodBoolean\":\n out.push({ key, kind: \"boolean\", label, schema: fieldSchema });\n break;\n case \"ZodEnum\":\n out.push({ key, kind: \"enum\", label, options: inner.values, schema: fieldSchema });\n break;\n case \"ZodString\": {\n const max = inner.checks?.find((c) => c.kind === \"max\")?.value;\n out.push({ key, kind: \"string\", label, maxLength: max, schema: fieldSchema });\n break;\n }\n case \"ZodArray\":\n case \"ZodObject\":\n case \"ZodRecord\":\n out.push({ key, kind: \"json\", label, schema: fieldSchema });\n break;\n default:\n // ZodLiteral (bv. _type), unions e.d.: geen zinvol generiek veld.\n break;\n }\n }\n return out;\n}\n\nexport type { FieldHints } from \"../../schema/fields\";\nimport type { FieldHints } from \"../../schema/fields\";\n\n/** Herkent een beeld-URL-veld aan de sleutelnaam (zelfde heuristiek als de oude GenericEditor). */\nconst IMAGE_KEY = /image|photo|foto|logo|cover|avatar|poster/i;\n/** Herkent een URL-veld aan de sleutelnaam. */\nconst URL_KEY = /^(href|url|link)$|Url$|Href$/;\n\n/**\n * Leidt de rijke Field-IR af uit een Zod-objectschema (5.0). Verschillen met de platte laag:\n * - `z.array(z.object)` → **repeater** met recursief geïntrospecteerde subvelden;\n * - genest `z.object` → **group** (of **link** als de vorm exact `{label, href}` is);\n * - stringvelden `html`/`*Html` → **richtext**, beeld-sleutels → **image**, URL-sleutels → **url**;\n * - al het overige (records, scalar-arrays, unions) → **json** (laatste redmiddel).\n * `hints` verfijnt per (dotted) sleutel: type-overrides (date/slider/…) en `showIf`.\n */\nexport function introspectToFields(schema: ZodTypeAny, hints?: FieldHints, prefix = \"\"): Field[] {\n const def = (schema as { _def?: AnyDef })._def;\n if (def?.typeName !== \"ZodObject\" || typeof def.shape !== \"function\") return [];\n const out: Field[] = [];\n for (const [key, raw] of Object.entries(def.shape())) {\n if (key.startsWith(\"_\")) continue;\n const { def: inner, description, inner: innerSchema } = unwrap(raw);\n const label = description ?? key;\n const path = prefix ? `${prefix}.${key}` : key;\n const hint = hints?.[path];\n const push = (f: Field) => out.push({ ...f, ...(hint as Partial<Field>), key, label: (hint?.label as string) ?? label } as Field);\n switch (inner?.typeName) {\n case \"ZodNumber\":\n push({ key, label, type: \"number\" });\n break;\n case \"ZodBoolean\":\n push({ key, label, type: \"switch\" });\n break;\n case \"ZodEnum\":\n push({ key, label, type: \"select\", options: (inner.values ?? []).map((v) => ({ value: v, label: v })) });\n break;\n case \"ZodString\": {\n const max = inner.checks?.find((c) => c.kind === \"max\")?.value;\n if (key === \"html\" || key.endsWith(\"Html\")) push({ key, label, type: \"richtext\" });\n else if (IMAGE_KEY.test(key)) push({ key, label, type: \"image\" });\n else if (URL_KEY.test(key)) push({ key, label, type: \"url\" });\n else if ((max ?? 0) > 500) push({ key, label, type: \"textarea\", maxLength: max });\n else push({ key, label, type: \"text\", maxLength: max });\n break;\n }\n case \"ZodObject\": {\n const sub = introspectToFields(innerSchema as ZodTypeAny, hints, path);\n const isLink = sub.length === 2 && sub.some((f) => f.key === \"label\") && sub.some((f) => f.key === \"href\");\n if (isLink) push({ key, label, type: \"link\" });\n else if (sub.length > 0) push({ key, label, type: \"group\", fields: sub });\n else push({ key, label, type: \"json\" });\n break;\n }\n case \"ZodArray\": {\n const el = (inner as { type?: unknown }).type ?? (inner as AnyDef & { element?: unknown }).element;\n const { def: elDef, inner: elSchema } = unwrap(el);\n if (elDef?.typeName === \"ZodObject\") {\n const sub = introspectToFields(elSchema as ZodTypeAny, hints, path);\n if (sub.length > 0) {\n push({ key, label, type: \"repeater\", fields: sub });\n break;\n }\n }\n push({ key, label, type: \"json\" });\n break;\n }\n case \"ZodRecord\":\n push({ key, label, type: \"json\" });\n break;\n default:\n break;\n }\n }\n return out;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/lib/zodFields.ts"],"sourcesContent":["/**\n * Zod-introspectie voor automatisch gegenereerde formulieren. Gedeeld door:\n * - de `GenericEditor` (fallback-editor voor blokken zonder eigen `Editor`), en\n * - het `ModuleSettingsForm` (instellingen van een module in /beheer).\n *\n * Twee lagen:\n * - `introspectObjectSchema` (legacy, plat): simpele kinds — blijft bestaan voor module-settings.\n * - `introspectToFields` (5.0): levert de rijke **Field-IR** (schema/fields.ts, primo-port) met\n * échte repeaters/groepen (recursief), richtext-/image-/link-detectie en optionele per-block\n * hints (`defineBlock({ fields })`). De GenericEditor 2.0 rendert deze IR.\n *\n * Een veld krijgt zijn label uit `.describe(\"…\")` op het schema; zonder beschrijving valt het\n * terug op de veldsleutel. Puur (geen React) — apart testbaar.\n */\nimport { globalRegistry } from \"zod/mini\";\nimport type { $ZodType as ZodTypeAny } from \"zod/v4/core\";\nimport type { Field } from \"../../schema/fields\";\n\nexport interface SchemaField {\n key: string;\n kind: \"number\" | \"boolean\" | \"string\" | \"enum\" | \"json\";\n /** Leesbaar label: `.describe()` van het schema, anders de veldsleutel. */\n label: string;\n options?: string[];\n /** Max-lengte van een string (bepaalt Input vs Textarea in de UI). */\n maxLength?: number;\n /** Het volledige veld-schema — voor validatie van json-velden vóór commit. */\n schema: ZodTypeAny;\n}\n\n/**\n * Zod 4-interne vorm. In Zod 3 heette dit `_def.typeName` (\"ZodObject\") met `shape` als FUNCTIE en\n * de beschrijving op `_def.description`; sinds Zod 4 is het `_def.type` (\"object\") met `shape` als\n * gewoon object, staat de beschrijving op de schema-instantie, dragen enums hun waarden in\n * `entries`, en zit een string-check achter `checks[i]._zod.def` als\n * `{ check: \"max_length\", maximum: n }`.\n *\n * Dit blijft leunen op interne velden — er is geen publieke introspectie-API in Zod. De\n * Field-IR-tests (fields.test.ts) zijn daarom het vangnet: die vallen om zodra Zod zijn interne\n * vorm opnieuw wijzigt, in plaats van dat de GenericEditor stilletjes lege formulieren toont.\n */\ntype ZodKind =\n | \"object\" | \"string\" | \"number\" | \"boolean\" | \"enum\" | \"array\" | \"record\"\n | \"default\" | \"optional\" | \"nullable\" | \"literal\" | (string & {});\n\ninterface AnyDef {\n type?: ZodKind;\n innerType?: unknown;\n entries?: Record<string, string>;\n element?: unknown;\n checks?: { _zod?: { def?: { check?: string; maximum?: number } } }[];\n shape?: Record<string, unknown>;\n}\n\n/**\n * De interne definitie van een schema.\n *\n * LET OP: `_def` is een alias die alléén de classic-façade aanbiedt — `zod/mini` (waar core op\n * draait) heeft hem niet en zet alles op `_zod.def`. Lees dus `_zod.def` eerst, met `_def` als\n * terugval voor classic-schema's uit een klantrepo. Zonder die volgorde levert introspectie\n * stilletjes niets op en toont de GenericEditor lege formulieren.\n */\nconst defOf = (schema: unknown): AnyDef | undefined =>\n (schema as { _zod?: { def?: AnyDef } })?._zod?.def ?? (schema as { _def?: AnyDef })?._def;\n\n/** Max-lengte van een string-schema (Zod 4: checks dragen hun regel onder `_zod.def`). */\nfunction maxLengthOf(def: AnyDef | undefined): number | undefined {\n for (const c of def?.checks ?? []) {\n const rule = c?._zod?.def;\n if (rule?.check === \"max_length\") return rule.maximum;\n }\n return undefined;\n}\n\n/** Waarden van een enum (Zod 4 levert een entries-map i.p.v. een values-array). */\nconst valuesOf = (def: AnyDef | undefined): string[] => Object.values(def?.entries ?? {});\n\n/** De velden van een objectschema; leeg als het geen object is. */\nfunction shapeOf(schema: unknown): Record<string, unknown> | null {\n const def = defOf(schema);\n if (def?.type !== \"object\" || !def.shape || typeof def.shape !== \"object\") return null;\n return def.shape;\n}\n\n/**\n * De beschrijving van een schema — de bron van de veldlabels in /beheer.\n *\n * `zod/mini` heeft géén `.description`-getter (die hoort bij de classic-façade), maar `.describe()`\n * en `.register()` schrijven allebei naar dezelfde `globalRegistry`. Die lezen we hier dus direct,\n * met de getter als terugval zodat een classic-schema uit een klantrepo blijft werken.\n */\nfunction describedAs(s: unknown): string | undefined {\n const uitRegister = globalRegistry.get(s as never)?.description;\n return uitRegister ?? (s as { description?: string } | null)?.description;\n}\n\n/** Pakt het binnenste type uit default/optional/nullable; bewaart de buitenste beschrijving. */\nfunction unwrap(schema: unknown): { def?: AnyDef; description?: string; inner?: unknown } {\n let s: unknown = schema;\n let description = describedAs(s);\n for (;;) {\n const def = defOf(s);\n if (def?.type !== \"default\" && def?.type !== \"optional\" && def?.type !== \"nullable\") break;\n s = def.innerType;\n description = description ?? describedAs(s);\n }\n return { def: defOf(s), description, inner: s };\n}\n\n/**\n * Leidt een formulier-beschrijving af uit een Zod-objectschema. Interne velden (`_key`/`_type`/\n * `_style`) worden overgeslagen — die beheren de editor-chrome en de StyleEditor. Complexe velden\n * (array/object/record) worden als `json` teruggegeven zodat een fallback-editor ze tóch kan\n * aanbieden; een aanroeper die alleen simpele velden wil, filtert op `kind !== \"json\"`.\n */\nexport function introspectObjectSchema(schema: ZodTypeAny): SchemaField[] {\n const shape = shapeOf(schema);\n if (!shape) return [];\n const out: SchemaField[] = [];\n for (const [key, field] of Object.entries(shape)) {\n if (key.startsWith(\"_\")) continue;\n const { def: inner, description } = unwrap(field);\n const label = description ?? key;\n const fieldSchema = field as ZodTypeAny;\n switch (inner?.type) {\n case \"number\":\n out.push({ key, kind: \"number\", label, schema: fieldSchema });\n break;\n case \"boolean\":\n out.push({ key, kind: \"boolean\", label, schema: fieldSchema });\n break;\n case \"enum\":\n out.push({ key, kind: \"enum\", label, options: valuesOf(inner), schema: fieldSchema });\n break;\n case \"string\": {\n out.push({ key, kind: \"string\", label, maxLength: maxLengthOf(inner), schema: fieldSchema });\n break;\n }\n case \"array\":\n case \"object\":\n case \"record\":\n out.push({ key, kind: \"json\", label, schema: fieldSchema });\n break;\n default:\n // ZodLiteral (bv. _type), unions e.d.: geen zinvol generiek veld.\n break;\n }\n }\n return out;\n}\n\nexport type { FieldHints } from \"../../schema/fields\";\nimport type { FieldHints } from \"../../schema/fields\";\n\n/** Herkent een beeld-URL-veld aan de sleutelnaam (zelfde heuristiek als de oude GenericEditor). */\nconst IMAGE_KEY = /image|photo|foto|logo|cover|avatar|poster/i;\n/** Herkent een URL-veld aan de sleutelnaam. */\nconst URL_KEY = /^(href|url|link)$|Url$|Href$/;\n\n/**\n * Leidt de rijke Field-IR af uit een Zod-objectschema (5.0). Verschillen met de platte laag:\n * - `z.array(z.object)` → **repeater** met recursief geïntrospecteerde subvelden;\n * - genest `z.object` → **group** (of **link** als de vorm exact `{label, href}` is);\n * - stringvelden `html`/`*Html` → **richtext**, beeld-sleutels → **image**, URL-sleutels → **url**;\n * - al het overige (records, scalar-arrays, unions) → **json** (laatste redmiddel).\n * `hints` verfijnt per (dotted) sleutel: type-overrides (date/slider/…) en `showIf`.\n */\nexport function introspectToFields(schema: ZodTypeAny, hints?: FieldHints, prefix = \"\"): Field[] {\n const shape = shapeOf(schema);\n if (!shape) return [];\n const out: Field[] = [];\n for (const [key, raw] of Object.entries(shape)) {\n if (key.startsWith(\"_\")) continue;\n const { def: inner, description, inner: innerSchema } = unwrap(raw);\n const label = description ?? key;\n const path = prefix ? `${prefix}.${key}` : key;\n const hint = hints?.[path];\n const push = (f: Field) => out.push({ ...f, ...(hint as Partial<Field>), key, label: (hint?.label as string) ?? label } as Field);\n switch (inner?.type) {\n case \"number\":\n push({ key, label, type: \"number\" });\n break;\n case \"boolean\":\n push({ key, label, type: \"switch\" });\n break;\n case \"enum\":\n push({ key, label, type: \"select\", options: valuesOf(inner).map((v) => ({ value: v, label: v })) });\n break;\n case \"string\": {\n const max = maxLengthOf(inner);\n if (key === \"html\" || key.endsWith(\"Html\")) push({ key, label, type: \"richtext\" });\n else if (IMAGE_KEY.test(key)) push({ key, label, type: \"image\" });\n else if (URL_KEY.test(key)) push({ key, label, type: \"url\" });\n else if ((max ?? 0) > 500) push({ key, label, type: \"textarea\", maxLength: max });\n else push({ key, label, type: \"text\", maxLength: max });\n break;\n }\n case \"object\": {\n const sub = introspectToFields(innerSchema as ZodTypeAny, hints, path);\n const isLink = sub.length === 2 && sub.some((f) => f.key === \"label\") && sub.some((f) => f.key === \"href\");\n if (isLink) push({ key, label, type: \"link\" });\n else if (sub.length > 0) push({ key, label, type: \"group\", fields: sub });\n else push({ key, label, type: \"json\" });\n break;\n }\n case \"array\": {\n const { def: elDef, inner: elSchema } = unwrap(inner?.element);\n if (elDef?.type === \"object\") {\n const sub = introspectToFields(elSchema as ZodTypeAny, hints, path);\n if (sub.length > 0) {\n push({ key, label, type: \"repeater\", fields: sub });\n break;\n }\n }\n push({ key, label, type: \"json\" });\n break;\n }\n case \"record\":\n push({ key, label, type: \"json\" });\n break;\n default:\n break;\n }\n }\n return out;\n}\n"],"mappings":"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","names":[]}
@@ -12,13 +12,13 @@ import {
12
12
  updateDoc
13
13
  } from "firebase/firestore";
14
14
  import { deleteObject, ref as storageRef } from "firebase/storage";
15
- import { getBucket, getDb } from "../../firebase/client";
16
- import { DEMO_EVENT, isDemoActive, listDemoMediaAll } from "../../demo/demoStore";
17
- import { Button, Card, Badge, Input } from "../ui/primitives";
18
- import { useToast } from "../ui/toast";
19
- import { useConfirm } from "../ui/ConfirmDialog";
20
- import { uploadImage } from "../lib/upload";
21
- import { findMediaUsage } from "../lib/mediaUsage";
15
+ import { getBucket, getDb } from "../../firebase/client.js";
16
+ import { DEMO_EVENT, isDemoActive, listDemoMediaAll } from "../../demo/demoStore.js";
17
+ import { Button, Card, Badge, Input } from "../ui/primitives.js";
18
+ import { useToast } from "../ui/toast.js";
19
+ import { useConfirm } from "../ui/ConfirmDialog.js";
20
+ import { uploadImage } from "../lib/upload.js";
21
+ import { findMediaUsage } from "../lib/mediaUsage.js";
22
22
  const PAGE_SIZE = 48;
23
23
  function MediaManager({ siteId }) {
24
24
  const { toast } = useToast();
@@ -2,12 +2,12 @@
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useMemo, useRef, useState } from "react";
4
4
  import { collection, limit, onSnapshot, orderBy, query } from "firebase/firestore";
5
- import { getDb } from "../../firebase/client";
6
- import { DEMO_EVENT, isDemoActive, listDemoMediaAll } from "../../demo/demoStore";
7
- import { Button, Input } from "../ui/primitives";
8
- import { Dialog, DialogContent } from "../ui/dialog";
9
- import { useToast } from "../ui/toast";
10
- import { uploadImage } from "../lib/upload";
5
+ import { getDb } from "../../firebase/client.js";
6
+ import { DEMO_EVENT, isDemoActive, listDemoMediaAll } from "../../demo/demoStore.js";
7
+ import { Button, Input } from "../ui/primitives.js";
8
+ import { Dialog, DialogContent } from "../ui/dialog.js";
9
+ import { useToast } from "../ui/toast.js";
10
+ import { uploadImage } from "../lib/upload.js";
11
11
  const PICKER_PAGE_SIZE = 48;
12
12
  function MediaPickerDialog({
13
13
  siteId,
@@ -68,7 +68,7 @@ function MediaPickerDialog({
68
68
  setBusy(false);
69
69
  }
70
70
  }
71
- return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(DialogContent, { title: "Afbeelding kiezen", className: "max-w-2xl", children: [
71
+ return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(DialogContent, { title: "Afbeelding kiezen", width: "min(92vw, 42rem)", children: [
72
72
  /* @__PURE__ */ jsxs("div", { className: "mb-3 flex items-center gap-2", children: [
73
73
  /* @__PURE__ */ jsx(Input, { value: q, onChange: (e) => setQ(e.target.value), placeholder: "Zoek in bibliotheek\u2026", className: "h-9" }),
74
74
  /* @__PURE__ */ jsx(Button, { type: "button", disabled: busy, onClick: () => inputRef.current?.click(), children: busy ? "Uploaden\u2026" : "Upload nieuwe" }),