@xiwebdesign/xicms-core 6.5.0 → 7.1.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 (381) 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 +90 -61
  6. package/dist/XiCMSProvider.js.map +1 -1
  7. package/dist/admin/AdminApp.d.ts +1 -1
  8. package/dist/admin/AdminApp.js +81 -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/CoreVersion.d.ts +13 -0
  14. package/dist/admin/CoreVersion.js +38 -0
  15. package/dist/admin/CoreVersion.js.map +1 -0
  16. package/dist/admin/NewsEditor.js +9 -9
  17. package/dist/admin/SuperadminPanel.js +5 -5
  18. package/dist/admin/blockEditors/GenericEditor.d.ts +2 -2
  19. package/dist/admin/blockEditors/GenericEditor.js +12 -11
  20. package/dist/admin/blockEditors/GenericEditor.js.map +1 -1
  21. package/dist/admin/blockEditors/StyleEditor.d.ts +1 -1
  22. package/dist/admin/blockEditors/StyleEditor.js +2 -2
  23. package/dist/admin/blockEditors/SymbolRefEditor.d.ts +1 -1
  24. package/dist/admin/blockEditors/SymbolRefEditor.js +5 -5
  25. package/dist/admin/blockEditors/index.d.ts +1 -1
  26. package/dist/admin/blockEditors/index.js +10 -10
  27. package/dist/admin/customBlocks/CustomBlocksPanel.js +7 -7
  28. package/dist/admin/editors/RichTextEditor.js +8 -7
  29. package/dist/admin/editors/RichTextEditor.js.map +1 -1
  30. package/dist/admin/editors/fontSizeExtension.js +1 -1
  31. package/dist/admin/editors/fontSizeExtension.js.map +1 -1
  32. package/dist/admin/forms/FormsPanel.js +9 -9
  33. package/dist/admin/index.d.ts +16 -16
  34. package/dist/admin/index.js +16 -16
  35. package/dist/admin/lib/mediaUsage.js +1 -1
  36. package/dist/admin/lib/pagesClient.d.ts +1 -1
  37. package/dist/admin/lib/pagesClient.js +11 -7
  38. package/dist/admin/lib/pagesClient.js.map +1 -1
  39. package/dist/admin/lib/sanitizeDraft.d.ts +9 -0
  40. package/dist/admin/lib/sanitizeDraft.js +28 -0
  41. package/dist/admin/lib/sanitizeDraft.js.map +1 -0
  42. package/dist/admin/lib/symbolsClient.d.ts +1 -1
  43. package/dist/admin/lib/symbolsClient.js +3 -3
  44. package/dist/admin/lib/upload.js +3 -3
  45. package/dist/admin/lib/useNestedDialog.d.ts +15 -0
  46. package/dist/admin/lib/useNestedDialog.js +23 -0
  47. package/dist/admin/lib/useNestedDialog.js.map +1 -0
  48. package/dist/admin/lib/zodFields.d.ts +4 -18
  49. package/dist/admin/lib/zodFields.js +54 -35
  50. package/dist/admin/lib/zodFields.js.map +1 -1
  51. package/dist/admin/media/MediaManager.js +7 -7
  52. package/dist/admin/media/MediaPicker.js +7 -7
  53. package/dist/admin/media/MediaPicker.js.map +1 -1
  54. package/dist/admin/pages/PageEditor.d.ts +1 -1
  55. package/dist/admin/pages/PageEditor.js +13 -13
  56. package/dist/admin/pages/PagesPanel.js +30 -30
  57. package/dist/admin/pages/PagesPanel.js.map +1 -1
  58. package/dist/admin/panelRegistry.js +7 -7
  59. package/dist/admin/patterns/PatternInserter.d.ts +1 -1
  60. package/dist/admin/patterns/PatternInserter.js +5 -5
  61. package/dist/admin/patterns/PatternInserter.js.map +1 -1
  62. package/dist/admin/settings/MailSettings.js +5 -5
  63. package/dist/admin/settings/MailTemplates.d.ts +1 -1
  64. package/dist/admin/settings/MailTemplates.js +5 -5
  65. package/dist/admin/settings/SettingsPanel.js +66 -20
  66. package/dist/admin/settings/SettingsPanel.js.map +1 -1
  67. package/dist/admin/settings/SnapshotsCard.js +5 -5
  68. package/dist/admin/settings/moduleSettingsForm.d.ts +1 -1
  69. package/dist/admin/settings/moduleSettingsForm.js +4 -4
  70. package/dist/admin/settings/moduleSettingsForm.js.map +1 -1
  71. package/dist/admin/shop/CategoriesTab.js +8 -8
  72. package/dist/admin/shop/ConnectionsTab.js +3 -3
  73. package/dist/admin/shop/DiscountsTab.js +8 -8
  74. package/dist/admin/shop/OrdersTab.js +10 -10
  75. package/dist/admin/shop/OrdersTab.js.map +1 -1
  76. package/dist/admin/shop/ProductsTab.js +10 -10
  77. package/dist/admin/shop/ShopPanel.js +9 -9
  78. package/dist/admin/shop/ShopSettingsTab.js +4 -4
  79. package/dist/admin/shop/index.d.ts +2 -2
  80. package/dist/admin/shop/index.js +2 -2
  81. package/dist/admin/shop/lib.js +2 -2
  82. package/dist/admin/stats/StatsPanel.js +2 -2
  83. package/dist/admin/ui/ArrayEditor.js +1 -1
  84. package/dist/admin/ui/PanelIcon.d.ts +3 -0
  85. package/dist/admin/ui/PanelIcon.js +34 -0
  86. package/dist/admin/ui/PanelIcon.js.map +1 -0
  87. package/dist/admin/ui/dialog.d.ts +21 -6
  88. package/dist/admin/ui/dialog.js +55 -34
  89. package/dist/admin/ui/dialog.js.map +1 -1
  90. package/dist/admin/ui/index.d.ts +7 -7
  91. package/dist/admin/ui/index.js +7 -7
  92. package/dist/admin/ui/primitives.js +1 -1
  93. package/dist/admin/ui/tabs.js +1 -1
  94. package/dist/admin/visual/TranslationPanel.d.ts +1 -1
  95. package/dist/admin/visual/TranslationPanel.js +3 -3
  96. package/dist/admin/visual/VisualEditor.js +47 -25
  97. package/dist/admin/visual/VisualEditor.js.map +1 -1
  98. package/dist/admin.css +2 -1
  99. package/dist/agentFiles/catalog.d.ts +2 -2
  100. package/dist/agentFiles/catalog.js +6 -5
  101. package/dist/agentFiles/catalog.js.map +1 -1
  102. package/dist/agentFiles/files.d.ts +5 -5
  103. package/dist/agentFiles/files.js +17 -17
  104. package/dist/agentFiles/files.js.map +1 -1
  105. package/dist/all.d.ts +4 -4
  106. package/dist/all.js +6 -6
  107. package/dist/auth/AuthProvider.d.ts +2 -2
  108. package/dist/auth/AuthProvider.js +51 -28
  109. package/dist/auth/AuthProvider.js.map +1 -1
  110. package/dist/auth/LoginForm.js +1 -1
  111. package/dist/auth/roles.js +4 -1
  112. package/dist/auth/roles.js.map +1 -1
  113. package/dist/blocks/BlockRenderer.d.ts +1 -1
  114. package/dist/blocks/BlockRenderer.js +3 -3
  115. package/dist/blocks/blockStyle.d.ts +1 -1
  116. package/dist/blocks/builtins/Accordion.d.ts +1 -1
  117. package/dist/blocks/builtins/Accordion.js +13 -6
  118. package/dist/blocks/builtins/Accordion.js.map +1 -1
  119. package/dist/blocks/builtins/Agenda.d.ts +1 -1
  120. package/dist/blocks/builtins/Agenda.js +5 -5
  121. package/dist/blocks/builtins/Banner.d.ts +1 -1
  122. package/dist/blocks/builtins/Banner.js +2 -2
  123. package/dist/blocks/builtins/BeforeAfter.d.ts +1 -1
  124. package/dist/blocks/builtins/BeforeAfter.js +4 -4
  125. package/dist/blocks/builtins/BeforeAfter.js.map +1 -1
  126. package/dist/blocks/builtins/Booking.d.ts +1 -1
  127. package/dist/blocks/builtins/Booking.js +5 -5
  128. package/dist/blocks/builtins/Breadcrumbs.d.ts +1 -1
  129. package/dist/blocks/builtins/Breadcrumbs.js +3 -3
  130. package/dist/blocks/builtins/Cards.d.ts +1 -1
  131. package/dist/blocks/builtins/Cards.js +5 -5
  132. package/dist/blocks/builtins/Cards.js.map +1 -1
  133. package/dist/blocks/builtins/Columns.d.ts +1 -1
  134. package/dist/blocks/builtins/Columns.js +4 -4
  135. package/dist/blocks/builtins/Contact.d.ts +3 -3
  136. package/dist/blocks/builtins/Contact.js +4 -4
  137. package/dist/blocks/builtins/Countdown.d.ts +1 -1
  138. package/dist/blocks/builtins/Countdown.js +4 -4
  139. package/dist/blocks/builtins/Countdown.js.map +1 -1
  140. package/dist/blocks/builtins/Cta.d.ts +1 -1
  141. package/dist/blocks/builtins/Cta.js +3 -3
  142. package/dist/blocks/builtins/Downloads.d.ts +1 -1
  143. package/dist/blocks/builtins/Downloads.js +4 -4
  144. package/dist/blocks/builtins/Elements.d.ts +3 -3
  145. package/dist/blocks/builtins/Elements.js +3 -3
  146. package/dist/blocks/builtins/Embed.d.ts +1 -1
  147. package/dist/blocks/builtins/Embed.js +3 -3
  148. package/dist/blocks/builtins/Faq.d.ts +1 -1
  149. package/dist/blocks/builtins/Faq.js +14 -6
  150. package/dist/blocks/builtins/Faq.js.map +1 -1
  151. package/dist/blocks/builtins/FormBlock.d.ts +1 -1
  152. package/dist/blocks/builtins/FormBlock.js +28 -18
  153. package/dist/blocks/builtins/FormBlock.js.map +1 -1
  154. package/dist/blocks/builtins/Gallery.d.ts +2 -2
  155. package/dist/blocks/builtins/Gallery.js +5 -5
  156. package/dist/blocks/builtins/Hero.d.ts +1 -1
  157. package/dist/blocks/builtins/Hero.js +4 -4
  158. package/dist/blocks/builtins/ImageBlock.d.ts +1 -1
  159. package/dist/blocks/builtins/ImageBlock.js +3 -3
  160. package/dist/blocks/builtins/Jobs.d.ts +2 -2
  161. package/dist/blocks/builtins/Jobs.js +5 -5
  162. package/dist/blocks/builtins/Layout.d.ts +2 -2
  163. package/dist/blocks/builtins/Layout.js +4 -4
  164. package/dist/blocks/builtins/MapBlock.d.ts +1 -1
  165. package/dist/blocks/builtins/MapBlock.js +3 -3
  166. package/dist/blocks/builtins/NewsListBlock.d.ts +1 -1
  167. package/dist/blocks/builtins/NewsListBlock.js +4 -4
  168. package/dist/blocks/builtins/Newsletter.d.ts +1 -1
  169. package/dist/blocks/builtins/Newsletter.js +7 -7
  170. package/dist/blocks/builtins/Newsletter.js.map +1 -1
  171. package/dist/blocks/builtins/OpenNowBadge.d.ts +1 -1
  172. package/dist/blocks/builtins/OpenNowBadge.js +8 -3
  173. package/dist/blocks/builtins/OpenNowBadge.js.map +1 -1
  174. package/dist/blocks/builtins/OpeningHours.d.ts +1 -1
  175. package/dist/blocks/builtins/OpeningHours.js +6 -6
  176. package/dist/blocks/builtins/PdfBlock.d.ts +1 -1
  177. package/dist/blocks/builtins/PdfBlock.js +3 -3
  178. package/dist/blocks/builtins/Portfolio.d.ts +1 -1
  179. package/dist/blocks/builtins/Portfolio.js +5 -5
  180. package/dist/blocks/builtins/Pricing.d.ts +2 -2
  181. package/dist/blocks/builtins/Pricing.js +5 -5
  182. package/dist/blocks/builtins/Pricing.js.map +1 -1
  183. package/dist/blocks/builtins/RichTextBlock.d.ts +1 -1
  184. package/dist/blocks/builtins/RichTextBlock.js +2 -2
  185. package/dist/blocks/builtins/RichTextBlock.js.map +1 -1
  186. package/dist/blocks/builtins/Search.d.ts +1 -1
  187. package/dist/blocks/builtins/Search.js +9 -7
  188. package/dist/blocks/builtins/Search.js.map +1 -1
  189. package/dist/blocks/builtins/Sections.d.ts +4 -4
  190. package/dist/blocks/builtins/Sections.js +4 -4
  191. package/dist/blocks/builtins/Slider.d.ts +1 -1
  192. package/dist/blocks/builtins/Slider.js +3 -3
  193. package/dist/blocks/builtins/Spacer.d.ts +1 -1
  194. package/dist/blocks/builtins/Spacer.js +2 -2
  195. package/dist/blocks/builtins/SymbolRef.d.ts +1 -1
  196. package/dist/blocks/builtins/SymbolRef.js +31 -17
  197. package/dist/blocks/builtins/SymbolRef.js.map +1 -1
  198. package/dist/blocks/builtins/Team.d.ts +1 -1
  199. package/dist/blocks/builtins/Team.js +4 -4
  200. package/dist/blocks/builtins/Testimonials.d.ts +2 -2
  201. package/dist/blocks/builtins/Testimonials.js +4 -4
  202. package/dist/blocks/builtins/Video.d.ts +2 -2
  203. package/dist/blocks/builtins/Video.js +4 -4
  204. package/dist/blocks/builtins/Video.js.map +1 -1
  205. package/dist/blocks/builtins/WhatsappChat.d.ts +1 -1
  206. package/dist/blocks/builtins/WhatsappChat.js +2 -2
  207. package/dist/blocks/builtins/Widgets.d.ts +12 -12
  208. package/dist/blocks/builtins/Widgets.js +19 -19
  209. package/dist/blocks/builtins/Widgets.js.map +1 -1
  210. package/dist/blocks/builtins/consentEmbed.js +2 -2
  211. package/dist/blocks/builtins/index.d.ts +40 -40
  212. package/dist/blocks/builtins/index.js +40 -40
  213. package/dist/blocks/builtins/lib/submitForm.js +5 -3
  214. package/dist/blocks/builtins/lib/submitForm.js.map +1 -1
  215. package/dist/blocks/context.d.ts +1 -1
  216. package/dist/blocks/customTemplate/definition.d.ts +3 -3
  217. package/dist/blocks/customTemplate/definition.js +6 -6
  218. package/dist/blocks/customTemplate/definition.js.map +1 -1
  219. package/dist/blocks/customTemplate/interpret.js +1 -1
  220. package/dist/blocks/index.d.ts +5 -5
  221. package/dist/blocks/index.js +5 -5
  222. package/dist/blocks/registry.d.ts +17 -7
  223. package/dist/blocks/registry.js +2 -1
  224. package/dist/blocks/registry.js.map +1 -1
  225. package/dist/components/EditBar.js +3 -3
  226. package/dist/components/FeedbackWidget.js +4 -4
  227. package/dist/components/News.d.ts +1 -1
  228. package/dist/components/News.js +50 -37
  229. package/dist/components/News.js.map +1 -1
  230. package/dist/components/PageView.js +11 -8
  231. package/dist/components/PageView.js.map +1 -1
  232. package/dist/components/ResponsiveImage.d.ts +1 -1
  233. package/dist/components/site/SiteFooter.d.ts +1 -1
  234. package/dist/components/site/SiteFooter.js +2 -2
  235. package/dist/config.d.ts +1 -1
  236. package/dist/demo/DemoStudio.js +3 -3
  237. package/dist/demo/demoStore.d.ts +3 -3
  238. package/dist/demo/index.d.ts +2 -2
  239. package/dist/demo/index.js +2 -2
  240. package/dist/editor/EditorProvider.js +1 -1
  241. package/dist/firebase/admin.d.ts +0 -2
  242. package/dist/firebase/admin.js +0 -6
  243. package/dist/firebase/admin.js.map +1 -1
  244. package/dist/firebase/appcheck.d.ts +1 -1
  245. package/dist/firebase/appcheck.js +5 -3
  246. package/dist/firebase/appcheck.js.map +1 -1
  247. package/dist/firebase/client.d.ts +2 -1
  248. package/dist/firebase/client.js +2 -14
  249. package/dist/firebase/client.js.map +1 -1
  250. package/dist/firebase/config.d.ts +31 -0
  251. package/dist/firebase/config.js +39 -0
  252. package/dist/firebase/config.js.map +1 -0
  253. package/dist/firebase/feedback.js +5 -3
  254. package/dist/firebase/feedback.js.map +1 -1
  255. package/dist/i18n/overlay.d.ts +2 -2
  256. package/dist/i18n/overlay.js +6 -4
  257. package/dist/i18n/overlay.js.map +1 -1
  258. package/dist/index.d.ts +46 -44
  259. package/dist/index.js +39 -33
  260. package/dist/index.js.map +1 -1
  261. package/dist/lock/DemoLock.js +1 -1
  262. package/dist/modules/builtins.d.ts +1 -1
  263. package/dist/modules/builtins.js +53 -55
  264. package/dist/modules/builtins.js.map +1 -1
  265. package/dist/modules/consent/ConsentBanner.js +1 -1
  266. package/dist/modules/consent/useConsent.js +1 -1
  267. package/dist/modules/context.d.ts +1 -1
  268. package/dist/modules/contract.d.ts +1 -1
  269. package/dist/modules/defineModule.d.ts +1 -1
  270. package/dist/modules/index.d.ts +9 -9
  271. package/dist/modules/index.js +8 -8
  272. package/dist/modules/overrides.d.ts +1 -1
  273. package/dist/modules/registry.d.ts +2 -7
  274. package/dist/modules/registry.js +2 -1
  275. package/dist/modules/registry.js.map +1 -1
  276. package/dist/modules/shop/blocks/Cart.d.ts +1 -1
  277. package/dist/modules/shop/blocks/Cart.js +6 -6
  278. package/dist/modules/shop/blocks/Checkout.d.ts +1 -1
  279. package/dist/modules/shop/blocks/Checkout.js +6 -6
  280. package/dist/modules/shop/blocks/Donation.d.ts +1 -1
  281. package/dist/modules/shop/blocks/Donation.js +5 -5
  282. package/dist/modules/shop/blocks/OrderStatus.d.ts +1 -1
  283. package/dist/modules/shop/blocks/OrderStatus.js +5 -5
  284. package/dist/modules/shop/blocks/ProductDetail.d.ts +1 -1
  285. package/dist/modules/shop/blocks/ProductDetail.js +8 -8
  286. package/dist/modules/shop/blocks/ProductGrid.d.ts +3 -3
  287. package/dist/modules/shop/blocks/ProductGrid.js +7 -7
  288. package/dist/modules/shop/blocks/ShopCategories.d.ts +1 -1
  289. package/dist/modules/shop/blocks/ShopCategories.js +6 -6
  290. package/dist/modules/shop/index.d.ts +5 -5
  291. package/dist/modules/shop/index.js +5 -5
  292. package/dist/modules/shop/lib.d.ts +2 -2
  293. package/dist/modules/shop/lib.js +18 -13
  294. package/dist/modules/shop/lib.js.map +1 -1
  295. package/dist/modules/shop/module.d.ts +1 -1
  296. package/dist/modules/shop/module.js +9 -9
  297. package/dist/modules/shop/settings.d.ts +18 -50
  298. package/dist/modules/shop/settings.js +16 -16
  299. package/dist/modules/shop/settings.js.map +1 -1
  300. package/dist/modules/shop/strings.js +1 -1
  301. package/dist/modules/shop/types.d.ts +1 -1
  302. package/dist/modules/types.d.ts +2 -2
  303. package/dist/patterns/builtins.d.ts +1 -1
  304. package/dist/patterns/builtins.js +1 -1
  305. package/dist/patterns/index.d.ts +2 -2
  306. package/dist/patterns/index.js +2 -2
  307. package/dist/render/PageRenderer.d.ts +2 -2
  308. package/dist/render/PageRenderer.js +1 -1
  309. package/dist/render/PublishedPage.d.ts +1 -1
  310. package/dist/render/PublishedPage.js +2 -2
  311. package/dist/schema/blocks.d.ts +6793 -15095
  312. package/dist/schema/blocks.js +335 -355
  313. package/dist/schema/blocks.js.map +1 -1
  314. package/dist/schema/customBlocks.d.ts +10 -29
  315. package/dist/schema/customBlocks.js +14 -13
  316. package/dist/schema/customBlocks.js.map +1 -1
  317. package/dist/schema/fields.d.ts +10 -16
  318. package/dist/schema/fields.js +37 -37
  319. package/dist/schema/fields.js.map +1 -1
  320. package/dist/schema/forms.d.ts +62 -111
  321. package/dist/schema/forms.js +17 -17
  322. package/dist/schema/forms.js.map +1 -1
  323. package/dist/schema/index.d.ts +7 -7
  324. package/dist/schema/index.js +7 -7
  325. package/dist/schema/mail.d.ts +39 -87
  326. package/dist/schema/mail.js +4 -4
  327. package/dist/schema/mail.js.map +1 -1
  328. package/dist/schema/pages.d.ts +152 -674
  329. package/dist/schema/pages.js +26 -15
  330. package/dist/schema/pages.js.map +1 -1
  331. package/dist/schema/seo.d.ts +9 -27
  332. package/dist/schema/seo.js +7 -7
  333. package/dist/schema/seo.js.map +1 -1
  334. package/dist/schema/settings.d.ts +233 -1074
  335. package/dist/schema/settings.js +71 -56
  336. package/dist/schema/settings.js.map +1 -1
  337. package/dist/schema/shop.d.ts +470 -1214
  338. package/dist/schema/shop.js +124 -124
  339. package/dist/schema/shop.js.map +1 -1
  340. package/dist/schema/siteConfig.d.ts +78 -597
  341. package/dist/schema/siteConfig.js +17 -17
  342. package/dist/schema/siteConfig.js.map +1 -1
  343. package/dist/schema/symbols.d.ts +34 -191
  344. package/dist/schema/symbols.js +8 -8
  345. package/dist/schema/symbols.js.map +1 -1
  346. package/dist/server/auth.d.ts +17 -0
  347. package/dist/server/auth.js +11 -0
  348. package/dist/server/auth.js.map +1 -0
  349. package/dist/server/bundle.d.ts +15 -6
  350. package/dist/server/bundle.js +5 -1
  351. package/dist/server/bundle.js.map +1 -1
  352. package/dist/server/draft.d.ts +16 -0
  353. package/dist/server/draft.js +12 -0
  354. package/dist/server/draft.js.map +1 -0
  355. package/dist/server/env.js +8 -8
  356. package/dist/server/env.js.map +1 -1
  357. package/dist/server/index.d.ts +11 -9
  358. package/dist/server/index.js +13 -10
  359. package/dist/server/index.js.map +1 -1
  360. package/dist/server/pages.d.ts +11 -4
  361. package/dist/server/pages.js +43 -15
  362. package/dist/server/pages.js.map +1 -1
  363. package/dist/server/revalidate.js +11 -2
  364. package/dist/server/revalidate.js.map +1 -1
  365. package/dist/server/seo.d.ts +3 -3
  366. package/dist/server/seo.js +1 -1
  367. package/dist/server/shop.d.ts +3 -3
  368. package/dist/server/shop.js +6 -3
  369. package/dist/server/shop.js.map +1 -1
  370. package/dist/server/version.d.ts +16 -0
  371. package/dist/server/version.js +17 -0
  372. package/dist/server/version.js.map +1 -0
  373. package/dist/settings/useSiteSettings.d.ts +2 -2
  374. package/dist/settings/useSiteSettings.js +48 -28
  375. package/dist/settings/useSiteSettings.js.map +1 -1
  376. package/dist/styles.css +30 -4
  377. package/dist/theme/presets.d.ts +1 -1
  378. package/dist/version.d.ts +8 -0
  379. package/dist/version.js +16 -0
  380. package/dist/version.js.map +1 -0
  381. package/package.json +35 -34
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/admin/AdminApp.tsx"],"sourcesContent":["\"use client\";\n\nimport { useAuth } from \"../auth/AuthProvider\";\nimport { LoginForm } from \"../auth/LoginForm\";\nimport { useSiteConfig } from \"../config\";\nimport { useModules } from \"../modules/context\";\nimport { canEdit, canManageSite, type Role } from \"../auth/roles\";\nimport { Tabs, TabsList, TabsTrigger, TabsContent } from \"./ui/tabs\";\nimport { ToastProvider } from \"./ui/toast\";\nimport { ConfirmHost } from \"./ui/ConfirmDialog\";\nimport { Button } from \"./ui/primitives\";\nimport { builtinPanels, type PanelDefinition } from \"./panelRegistry\";\nimport { AdminHelp } from \"./AdminHelp\";\n\n/**\n * Rol-bewaakt beheerdashboard (XiCMS 3.0). De tabs komen uit de paneel-registry, gefilterd op de\n * actieve modules (`SiteConfig.modules`/`features`) en de rol van de gebruiker. Een module met een\n * eigen paneel (ook klant-modules via `extraPanels`) verschijnt automatisch.\n *\n * Wikkel in `.xicms-admin` zodat de Tailwind-stijlen uit `@xiwebdesign/xicms-core/admin.css` scoped\n * blijven en niet naar de site lekken. Importeer die CSS alleen op de /beheer-route.\n */\nfunction roleAllows(role: Role | null, min: PanelDefinition[\"minRole\"]): boolean {\n return min === \"siteadmin\" ? canManageSite(role) : canEdit(role);\n}\n\nexport function AdminApp({ extraPanels = [] }: { extraPanels?: PanelDefinition[] }) {\n const { user, role, loading, configured, signOut } = useAuth();\n const { name, siteId } = useSiteConfig();\n const { activeIds } = useModules();\n\n // Panelen van actieve modules waar de rol toegang toe heeft, gesorteerd op volgorde.\n const panels = [...builtinPanels, ...extraPanels]\n .filter((p) => activeIds.has(p.moduleId) && roleAllows(role, p.minRole))\n .sort((a, b) => a.order - b.order);\n const firstTab = panels[0]?.moduleId;\n\n return (\n <div className=\"xicms-admin\">\n <ToastProvider>\n <ConfirmHost>\n {!configured ? (\n <div className=\"rounded-xl border border-slate-200 bg-white p-6 text-sm\">\n Firebase is nog niet geconfigureerd. Vul de <code>NEXT_PUBLIC_FIREBASE_*</code>-variabelen in\n om het beheer aan te zetten.\n </div>\n ) : loading ? (\n <p className=\"text-sm text-slate-500\">Laden…</p>\n ) : !user ? (\n <LoginForm />\n ) : !canEdit(role) ? (\n <div className=\"rounded-xl border border-slate-200 bg-white p-6 text-sm\">\n Je bent ingelogd als <strong>{user.email}</strong>, maar hebt geen beheerrechten voor deze site.\n <div className=\"mt-3\">\n <Button variant=\"secondary\" size=\"sm\" onClick={() => void signOut()}>Uitloggen</Button>\n </div>\n </div>\n ) : (\n <div className=\"grid gap-6\">\n <header className=\"flex flex-wrap items-center justify-between gap-3\">\n <div>\n <h1 className=\"text-xl font-bold\">Beheer · {name}</h1>\n <p className=\"text-sm text-slate-500\">Ingelogd als {user.email} ({role})</p>\n </div>\n <div className=\"flex gap-2\">\n <a className=\"inline-flex h-9 items-center rounded-lg border border-slate-200 px-3 text-sm hover:bg-slate-50\" href=\"/\">Bekijk site</a>\n <Button variant=\"secondary\" size=\"sm\" onClick={() => void signOut()}>Uitloggen</Button>\n </div>\n </header>\n\n <AdminHelp />\n\n {panels.length === 0 ? (\n <p className=\"text-sm text-slate-500\">Geen actieve beheer-modules voor deze site.</p>\n ) : (\n <Tabs defaultValue={firstTab}>\n <TabsList>\n {panels.map((p) => (\n <TabsTrigger key={p.moduleId} value={p.moduleId}>\n {p.label}\n </TabsTrigger>\n ))}\n </TabsList>\n\n {panels.map((p) => (\n <TabsContent key={p.moduleId} value={p.moduleId}>\n <p.Panel siteId={siteId} />\n </TabsContent>\n ))}\n </Tabs>\n )}\n </div>\n )}\n </ConfirmHost>\n </ToastProvider>\n </div>\n );\n}\n"],"mappings":";AA0CU,SAC8C,KAD9C;AAxCV,SAAS,eAAe;AACxB,SAAS,iBAAiB;AAC1B,SAAS,qBAAqB;AAC9B,SAAS,kBAAkB;AAC3B,SAAS,SAAS,qBAAgC;AAClD,SAAS,MAAM,UAAU,aAAa,mBAAmB;AACzD,SAAS,qBAAqB;AAC9B,SAAS,mBAAmB;AAC5B,SAAS,cAAc;AACvB,SAAS,qBAA2C;AACpD,SAAS,iBAAiB;AAU1B,SAAS,WAAW,MAAmB,KAA0C;AAC/E,SAAO,QAAQ,cAAc,cAAc,IAAI,IAAI,QAAQ,IAAI;AACjE;AAEO,SAAS,SAAS,EAAE,cAAc,CAAC,EAAE,GAAwC;AAClF,QAAM,EAAE,MAAM,MAAM,SAAS,YAAY,QAAQ,IAAI,QAAQ;AAC7D,QAAM,EAAE,MAAM,OAAO,IAAI,cAAc;AACvC,QAAM,EAAE,UAAU,IAAI,WAAW;AAGjC,QAAM,SAAS,CAAC,GAAG,eAAe,GAAG,WAAW,EAC7C,OAAO,CAAC,MAAM,UAAU,IAAI,EAAE,QAAQ,KAAK,WAAW,MAAM,EAAE,OAAO,CAAC,EACtE,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AACnC,QAAM,WAAW,OAAO,CAAC,GAAG;AAE5B,SACE,oBAAC,SAAI,WAAU,eACb,8BAAC,iBACA,8BAAC,eACC,WAAC,aACA,qBAAC,SAAI,WAAU,2DAA0D;AAAA;AAAA,IAC3B,oBAAC,UAAK,oCAAsB;AAAA,IAAO;AAAA,KAEjF,IACE,UACF,oBAAC,OAAE,WAAU,0BAAyB,yBAAM,IAC1C,CAAC,OACH,oBAAC,aAAU,IACT,CAAC,QAAQ,IAAI,IACf,qBAAC,SAAI,WAAU,2DAA0D;AAAA;AAAA,IAClD,oBAAC,YAAQ,eAAK,OAAM;AAAA,IAAS;AAAA,IAClD,oBAAC,SAAI,WAAU,QACb,8BAAC,UAAO,SAAQ,aAAY,MAAK,MAAK,SAAS,MAAM,KAAK,QAAQ,GAAG,uBAAS,GAChF;AAAA,KACF,IAEA,qBAAC,SAAI,WAAU,cACb;AAAA,yBAAC,YAAO,WAAU,qDAChB;AAAA,2BAAC,SACC;AAAA,6BAAC,QAAG,WAAU,qBAAoB;AAAA;AAAA,UAAU;AAAA,WAAK;AAAA,QACjD,qBAAC,OAAE,WAAU,0BAAyB;AAAA;AAAA,UAAc,KAAK;AAAA,UAAM;AAAA,UAAG;AAAA,UAAK;AAAA,WAAC;AAAA,SAC1E;AAAA,MACA,qBAAC,SAAI,WAAU,cACb;AAAA,4BAAC,OAAE,WAAU,kGAAiG,MAAK,KAAI,yBAAW;AAAA,QAClI,oBAAC,UAAO,SAAQ,aAAY,MAAK,MAAK,SAAS,MAAM,KAAK,QAAQ,GAAG,uBAAS;AAAA,SAChF;AAAA,OACF;AAAA,IAEA,oBAAC,aAAU;AAAA,IAEV,OAAO,WAAW,IACjB,oBAAC,OAAE,WAAU,0BAAyB,yDAA2C,IAEjF,qBAAC,QAAK,cAAc,UAClB;AAAA,0BAAC,YACE,iBAAO,IAAI,CAAC,MACX,oBAAC,eAA6B,OAAO,EAAE,UACpC,YAAE,SADa,EAAE,QAEpB,CACD,GACH;AAAA,MAEC,OAAO,IAAI,CAAC,MACX,oBAAC,eAA6B,OAAO,EAAE,UACrC,8BAAC,EAAE,OAAF,EAAQ,QAAgB,KADT,EAAE,QAEpB,CACD;AAAA,OACH;AAAA,KAEJ,GAEH,GACD,GACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/admin/AdminApp.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState } from \"react\";\nimport { useAuth } from \"../auth/AuthProvider\";\nimport { LoginForm } from \"../auth/LoginForm\";\nimport { useSiteConfig } from \"../config\";\nimport { useModules } from \"../modules/context\";\nimport { canEdit, canManageSite, type Role } from \"../auth/roles\";\nimport { Tabs, TabsContent } from \"./ui/tabs\";\nimport * as TabsPrimitive from \"@radix-ui/react-tabs\";\nimport { ToastProvider } from \"./ui/toast\";\nimport { ConfirmHost } from \"./ui/ConfirmDialog\";\nimport { Button } from \"./ui/primitives\";\nimport { PanelIcon } from \"./ui/PanelIcon\";\nimport { builtinPanels, type PanelDefinition } from \"./panelRegistry\";\nimport { AdminHelp } from \"./AdminHelp\";\nimport { CoreVersion } from \"./CoreVersion\";\n\n/**\n * Rol-bewaakt beheer (XiCMS 3.0). De secties komen uit de paneel-registry, gefilterd op de actieve\n * modules (`SiteConfig.modules`/`features`) en de rol van de gebruiker. Een module met een eigen\n * paneel (ook klant-modules via `extraPanels`) verschijnt automatisch.\n *\n * VORM: een schermvullend werkblad met een vaste zijbalk, geen kaart in de pagina. Het beheer viel\n * hiervoor binnen de site-chrome — nav erboven, footer eronder, cookiebanner eroverheen — en las\n * daardoor als een blok ergens op de website in plaats van als gereedschap. De site-chrome leeft nu\n * in `app/(site)/layout.tsx`; /beheer valt daarbuiten.\n *\n * De zijbalk verving de horizontale tabstrip omdat die bij zeven of meer panelen al omsloeg naar een\n * tweede regel. Verticaal is er bovendien ruimte voor een icoon naast het woord, en `orientation`\n * houdt de pijltjestoetsen correct (Radix).\n *\n * Wikkel in `.xicms-admin` zodat de Tailwind-stijlen uit `@xiwebdesign/xicms-core/admin.css` scoped\n * blijven en niet naar de site lekken. Importeer die CSS alleen op de /beheer-route.\n */\nfunction roleAllows(role: Role | null, min: PanelDefinition[\"minRole\"]): boolean {\n return min === \"siteadmin\" ? canManageSite(role) : canEdit(role);\n}\n\n/** Gecentreerde boodschap voor de toestanden waarin er (nog) geen beheer te tonen is. */\nfunction Melding({ children }: { children: React.ReactNode }) {\n return (\n <div className=\"grid min-h-screen place-items-center p-6\">\n <div className=\"w-full max-w-md rounded-xl border border-slate-200 bg-white p-6 text-sm shadow-sm\">{children}</div>\n </div>\n );\n}\n\nexport function AdminApp({ extraPanels = [] }: { extraPanels?: PanelDefinition[] }) {\n const { user, role, loading, configured, signOut } = useAuth();\n const { name, siteId } = useSiteConfig();\n const { activeIds } = useModules();\n\n // Panelen van actieve modules waar de rol toegang toe heeft, gesorteerd op volgorde.\n const panels = [...builtinPanels, ...extraPanels]\n .filter((p) => activeIds.has(p.moduleId) && roleAllows(role, p.minRole))\n .sort((a, b) => a.order - b.order);\n const firstTab = panels[0]?.moduleId;\n // Gecontroleerd i.p.v. `defaultValue`: het werkblad toont de naam van de sectie waar je in zit.\n // Zonder die kop lees je alleen aan de zijbalk-markering af waar je bent, en dat is te weinig\n // houvast op een breed scherm waar de zijbalk ver van je blik af staat.\n const [actief, setActief] = useState<string | undefined>(firstTab);\n const actievePanel = panels.find((p) => p.moduleId === (actief ?? firstTab));\n\n return (\n <div className=\"xicms-admin\">\n <ToastProvider>\n <ConfirmHost>\n {!configured ? (\n <Melding>\n Firebase is nog niet geconfigureerd. Vul de <code>NEXT_PUBLIC_FIREBASE_*</code>-variabelen in\n om het beheer aan te zetten.\n </Melding>\n ) : loading ? (\n <Melding>\n <span className=\"text-slate-500\">Bezig met laden…</span>\n </Melding>\n ) : !user ? (\n <div className=\"grid min-h-screen place-items-center p-6\">\n <div className=\"w-full max-w-sm\">\n <p className=\"mb-4 text-center text-sm text-slate-500\">Beheer · {name}</p>\n <LoginForm />\n </div>\n </div>\n ) : !canEdit(role) ? (\n <Melding>\n Je bent ingelogd als <strong>{user.email}</strong>, maar hebt geen beheerrechten voor deze site.\n <div className=\"mt-3\">\n <Button variant=\"secondary\" size=\"sm\" onClick={() => void signOut()}>Uitloggen</Button>\n </div>\n </Melding>\n ) : panels.length === 0 ? (\n <Melding>Geen actieve beheer-modules voor deze site.</Melding>\n ) : (\n <Tabs value={actief ?? firstTab} onValueChange={setActief} orientation=\"vertical\" className=\"min-h-screen md:grid md:grid-cols-[248px_1fr]\">\n {/* ── Zijbalk ─────────────────────────────────────────────────────────── */}\n <div\n className=\"flex flex-col gap-4 border-b border-slate-200 p-4 md:sticky md:top-0 md:h-screen md:border-r md:border-b-0\"\n style={{ background: \"var(--admin-sidebar)\" }}\n >\n <div className=\"min-w-0 px-2 pt-1\">\n <p className=\"truncate text-sm font-semibold\">{name}</p>\n <p className=\"text-xs text-slate-500\">Beheer</p>\n </div>\n\n <TabsPrimitive.List aria-label=\"Beheer-secties\" className=\"-mx-1 flex gap-1 overflow-x-auto md:flex-col md:overflow-visible\">\n {panels.map((p) => (\n <TabsPrimitive.Trigger\n key={p.moduleId}\n value={p.moduleId}\n className=\"flex shrink-0 items-center gap-2.5 rounded-lg px-3 py-2 text-sm font-medium text-slate-600 hover:bg-slate-200 hover:text-slate-900 focus-visible:ring-2 focus-visible:ring-brand-500 focus-visible:outline-none data-[state=active]:bg-slate-100 data-[state=active]:text-slate-900 data-[state=active]:shadow-[inset_2px_0_0_0_var(--color-brand-500)]\"\n >\n <PanelIcon id={p.moduleId} />\n {p.label}\n </TabsPrimitive.Trigger>\n ))}\n </TabsPrimitive.List>\n\n <div className=\"mt-auto hidden min-w-0 border-t border-slate-200 pt-3 md:block\">\n <p className=\"truncate text-xs text-slate-500\">{user.email}</p>\n <p className=\"text-xs text-slate-400\">{role}</p>\n <div className=\"mt-2\"><CoreVersion /></div>\n <button\n className=\"mt-2 rounded text-xs text-slate-500 underline hover:text-slate-900 focus-visible:ring-2 focus-visible:ring-brand-500 focus-visible:outline-none\"\n onClick={() => void signOut()}\n >\n Uitloggen\n </button>\n </div>\n </div>\n\n {/* ── Werkblad ────────────────────────────────────────────────────────── */}\n <div className=\"min-w-0\">\n <header className=\"sticky top-0 z-10 flex items-center justify-between gap-4 border-b border-slate-200 px-4 py-3 md:px-8\" style={{ background: \"var(--admin-canvas)\" }}>\n <h1 className=\"truncate text-base font-semibold\">{actievePanel?.label ?? \"Beheer\"}</h1>\n <a\n className=\"inline-flex h-8 shrink-0 items-center rounded-lg border border-slate-200 px-3 text-sm hover:bg-slate-100 focus-visible:ring-2 focus-visible:ring-brand-500 focus-visible:outline-none\"\n href=\"/\"\n >\n Bekijk site ↗\n </a>\n </header>\n <div className=\"grid gap-6 p-4 md:p-8\">\n {panels.map((p) => (\n <TabsContent key={p.moduleId} value={p.moduleId} className=\"pt-0\">\n <p.Panel siteId={siteId} />\n </TabsContent>\n ))}\n <AdminHelp />\n </div>\n </div>\n </Tabs>\n )}\n </ConfirmHost>\n </ToastProvider>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -1,2 +1,9 @@
1
- /** Korte, vriendelijke uitleg voor (a-technische) gebruikers die voor het eerst inloggen. */
1
+ /**
2
+ * Korte, vriendelijke uitleg voor (a-technische) gebruikers die voor het eerst inloggen.
3
+ *
4
+ * BEWUST INGETOGEN en onder het werk geplaatst. In merkkleur bovenaan was dit het grootste en
5
+ * felste element op het scherm — uitleg die je één keer leest, die visueel won van de pagina's
6
+ * waarvoor je kwam. Nu een rustige notitie in de neutrale schaal: vindbaar zolang je hem nodig
7
+ * hebt, wegklikbaar zodra dat niet meer zo is.
8
+ */
2
9
  export declare function AdminHelp(): import("react").JSX.Element | null;
@@ -12,9 +12,9 @@ function AdminHelp() {
12
12
  }
13
13
  }, []);
14
14
  if (hidden) return null;
15
- return /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-brand-200 bg-brand-50 p-4 text-sm text-slate-700", children: [
15
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-slate-200 p-4 text-sm text-slate-600", style: { background: "var(--color-background-card)" }, children: [
16
16
  /* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center justify-between", children: [
17
- /* @__PURE__ */ jsx("strong", { children: "Welkom in het beheer \u{1F44B}" }),
17
+ /* @__PURE__ */ jsx("strong", { className: "text-slate-700", children: "Zo werkt het beheer" }),
18
18
  /* @__PURE__ */ jsx(
19
19
  "button",
20
20
  {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/admin/AdminHelp.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\n\nconst KEY = \"xicms-help-dismissed\";\n\n/** Korte, vriendelijke uitleg voor (a-technische) gebruikers die voor het eerst inloggen. */\nexport function AdminHelp() {\n const [hidden, setHidden] = useState(true);\n useEffect(() => {\n try {\n setHidden(localStorage.getItem(KEY) === \"1\");\n } catch {\n setHidden(false);\n }\n }, []);\n if (hidden) return null;\n return (\n <div className=\"rounded-xl border border-brand-200 bg-brand-50 p-4 text-sm text-slate-700\">\n <div className=\"mb-2 flex items-center justify-between\">\n <strong>Welkom in het beheer 👋</strong>\n <button\n type=\"button\"\n className=\"text-xs text-slate-500 hover:underline\"\n onClick={() => {\n try {\n localStorage.setItem(KEY, \"1\");\n } catch {\n /* opslag geweigerd */\n }\n setHidden(true);\n }}\n >\n Verbergen\n </button>\n </div>\n <ul className=\"grid list-disc gap-1 pl-5\">\n <li>\n <strong>Pagina&apos;s</strong>: pas teksten en secties aan. Je wijzigingen gaan pas live als je op\n <strong> Publiceren</strong> klikt — experimenteren kan dus veilig.\n </li>\n <li>\n Liever direct op de site typen? Klik op de website zelf op <strong>“Pagina bewerken”</strong>.\n </li>\n <li>\n <strong>Nieuws</strong>: plaats artikelen. <strong>Media</strong>: upload foto&apos;s — die worden\n automatisch verkleind om je site snel te houden.\n </li>\n <li>\n Maak af en toe een <strong>back-up</strong> onder <strong>Instellingen</strong>; terugzetten kan altijd.\n </li>\n </ul>\n </div>\n );\n}\n"],"mappings":";AAmBM,SACE,KADF;AAjBN,SAAS,WAAW,gBAAgB;AAEpC,MAAM,MAAM;AAGL,SAAS,YAAY;AAC1B,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,IAAI;AACzC,YAAU,MAAM;AACd,QAAI;AACF,gBAAU,aAAa,QAAQ,GAAG,MAAM,GAAG;AAAA,IAC7C,QAAQ;AACN,gBAAU,KAAK;AAAA,IACjB;AAAA,EACF,GAAG,CAAC,CAAC;AACL,MAAI,OAAQ,QAAO;AACnB,SACE,qBAAC,SAAI,WAAU,6EACb;AAAA,yBAAC,SAAI,WAAU,0CACb;AAAA,0BAAC,YAAO,4CAAuB;AAAA,MAC/B;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,WAAU;AAAA,UACV,SAAS,MAAM;AACb,gBAAI;AACF,2BAAa,QAAQ,KAAK,GAAG;AAAA,YAC/B,QAAQ;AAAA,YAER;AACA,sBAAU,IAAI;AAAA,UAChB;AAAA,UACD;AAAA;AAAA,MAED;AAAA,OACF;AAAA,IACA,qBAAC,QAAG,WAAU,6BACZ;AAAA,2BAAC,QACC;AAAA,4BAAC,YAAO,sBAAa;AAAA,QAAS;AAAA,QAC9B,oBAAC,YAAO,yBAAW;AAAA,QAAS;AAAA,SAC9B;AAAA,MACA,qBAAC,QAAG;AAAA;AAAA,QACyD,oBAAC,YAAO,yCAAiB;AAAA,QAAS;AAAA,SAC/F;AAAA,MACA,qBAAC,QACC;AAAA,4BAAC,YAAO,oBAAM;AAAA,QAAS;AAAA,QAAoB,oBAAC,YAAO,mBAAK;AAAA,QAAS;AAAA,SAEnE;AAAA,MACA,qBAAC,QAAG;AAAA;AAAA,QACiB,oBAAC,YAAO,qBAAO;AAAA,QAAS;AAAA,QAAO,oBAAC,YAAO,0BAAY;AAAA,QAAS;AAAA,SACjF;AAAA,OACF;AAAA,KACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/admin/AdminHelp.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\n\nconst KEY = \"xicms-help-dismissed\";\n\n/**\n * Korte, vriendelijke uitleg voor (a-technische) gebruikers die voor het eerst inloggen.\n *\n * BEWUST INGETOGEN en onder het werk geplaatst. In merkkleur bovenaan was dit het grootste en\n * felste element op het scherm — uitleg die je één keer leest, die visueel won van de pagina's\n * waarvoor je kwam. Nu een rustige notitie in de neutrale schaal: vindbaar zolang je hem nodig\n * hebt, wegklikbaar zodra dat niet meer zo is.\n */\nexport function AdminHelp() {\n const [hidden, setHidden] = useState(true);\n useEffect(() => {\n try {\n setHidden(localStorage.getItem(KEY) === \"1\");\n } catch {\n setHidden(false);\n }\n }, []);\n if (hidden) return null;\n return (\n <div className=\"rounded-xl border border-slate-200 p-4 text-sm text-slate-600\" style={{ background: \"var(--color-background-card)\" }}>\n <div className=\"mb-2 flex items-center justify-between\">\n <strong className=\"text-slate-700\">Zo werkt het beheer</strong>\n <button\n type=\"button\"\n className=\"text-xs text-slate-500 hover:underline\"\n onClick={() => {\n try {\n localStorage.setItem(KEY, \"1\");\n } catch {\n /* opslag geweigerd */\n }\n setHidden(true);\n }}\n >\n Verbergen\n </button>\n </div>\n <ul className=\"grid list-disc gap-1 pl-5\">\n <li>\n <strong>Pagina&apos;s</strong>: pas teksten en secties aan. Je wijzigingen gaan pas live als je op\n <strong> Publiceren</strong> klikt — experimenteren kan dus veilig.\n </li>\n <li>\n Liever direct op de site typen? Klik op de website zelf op <strong>“Pagina bewerken”</strong>.\n </li>\n <li>\n <strong>Nieuws</strong>: plaats artikelen. <strong>Media</strong>: upload foto&apos;s — die worden\n automatisch verkleind om je site snel te houden.\n </li>\n <li>\n Maak af en toe een <strong>back-up</strong> onder <strong>Instellingen</strong>; terugzetten kan altijd.\n </li>\n </ul>\n </div>\n );\n}\n"],"mappings":";AA0BM,SACE,KADF;AAxBN,SAAS,WAAW,gBAAgB;AAEpC,MAAM,MAAM;AAUL,SAAS,YAAY;AAC1B,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,IAAI;AACzC,YAAU,MAAM;AACd,QAAI;AACF,gBAAU,aAAa,QAAQ,GAAG,MAAM,GAAG;AAAA,IAC7C,QAAQ;AACN,gBAAU,KAAK;AAAA,IACjB;AAAA,EACF,GAAG,CAAC,CAAC;AACL,MAAI,OAAQ,QAAO;AACnB,SACE,qBAAC,SAAI,WAAU,iEAAgE,OAAO,EAAE,YAAY,+BAA+B,GACjI;AAAA,yBAAC,SAAI,WAAU,0CACb;AAAA,0BAAC,YAAO,WAAU,kBAAiB,iCAAmB;AAAA,MACtD;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,WAAU;AAAA,UACV,SAAS,MAAM;AACb,gBAAI;AACF,2BAAa,QAAQ,KAAK,GAAG;AAAA,YAC/B,QAAQ;AAAA,YAER;AACA,sBAAU,IAAI;AAAA,UAChB;AAAA,UACD;AAAA;AAAA,MAED;AAAA,OACF;AAAA,IACA,qBAAC,QAAG,WAAU,6BACZ;AAAA,2BAAC,QACC;AAAA,4BAAC,YAAO,sBAAa;AAAA,QAAS;AAAA,QAC9B,oBAAC,YAAO,yBAAW;AAAA,QAAS;AAAA,SAC9B;AAAA,MACA,qBAAC,QAAG;AAAA;AAAA,QACyD,oBAAC,YAAO,yCAAiB;AAAA,QAAS;AAAA,SAC/F;AAAA,MACA,qBAAC,QACC;AAAA,4BAAC,YAAO,oBAAM;AAAA,QAAS;AAAA,QAAoB,oBAAC,YAAO,mBAAK;AAAA,QAAS;AAAA,SAEnE;AAAA,MACA,qBAAC,QAAG;AAAA;AAAA,QACiB,oBAAC,YAAO,qBAAO;AAAA,QAAS;AAAA,QAAO,oBAAC,YAAO,0BAAY;AAAA,QAAS;AAAA,SACjF;AAAA,OACF;AAAA,KACF;AAEJ;","names":[]}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Toont op welke engine deze site draait, en of er een nieuwere is.
3
+ *
4
+ * De laatste versie komt rechtstreeks van de npm-registry. Dat is een publiek, CORS-vrij endpoint
5
+ * en het is de enige bron die niet kan verouderen — een waarde die we zelf ergens zouden bijhouden
6
+ * loopt per definitie achter op de release die hem zou moeten bijwerken.
7
+ *
8
+ * Faalt stil: geen netwerk of een registry die hapert mag het beheer niet in de weg zitten. Je ziet
9
+ * dan alleen de eigen versie, wat nog steeds het antwoord is op de vraag "waar draai ik op".
10
+ */
11
+ export declare function CoreVersion({ compact }: {
12
+ compact?: boolean;
13
+ }): import("react").JSX.Element;
@@ -0,0 +1,38 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useState } from "react";
4
+ import { CORE_VERSION, CORE_PACKAGE, isNieuwer } from "../version.js";
5
+ function CoreVersion({ compact = false }) {
6
+ const [laatste, setLaatste] = useState(null);
7
+ useEffect(() => {
8
+ const ctrl = new AbortController();
9
+ fetch(`https://registry.npmjs.org/${CORE_PACKAGE}/latest`, { signal: ctrl.signal }).then((r) => r.ok ? r.json() : null).then((d) => {
10
+ if (typeof d?.version === "string") setLaatste(d.version);
11
+ }).catch(() => {
12
+ });
13
+ return () => ctrl.abort();
14
+ }, []);
15
+ const update = laatste ? isNieuwer(laatste, CORE_VERSION) : false;
16
+ if (compact) {
17
+ return /* @__PURE__ */ jsxs("span", { className: "font-mono text-[11px] text-slate-500", children: [
18
+ "core ",
19
+ CORE_VERSION,
20
+ update && /* @__PURE__ */ jsxs("span", { className: "ml-1 text-amber-600", children: [
21
+ "\u2192 ",
22
+ laatste
23
+ ] })
24
+ ] });
25
+ }
26
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 text-xs", children: [
27
+ /* @__PURE__ */ jsx("span", { className: "text-slate-500", children: "Engine" }),
28
+ /* @__PURE__ */ jsx("span", { className: "rounded px-1.5 py-0.5 font-mono text-slate-700", style: { background: "var(--color-background-muted)" }, children: CORE_VERSION }),
29
+ update ? /* @__PURE__ */ jsxs("span", { className: "text-amber-700", children: [
30
+ "Nieuwer beschikbaar: ",
31
+ /* @__PURE__ */ jsx("span", { className: "font-mono", children: laatste })
32
+ ] }) : laatste ? /* @__PURE__ */ jsx("span", { className: "text-slate-500", children: "Bijgewerkt" }) : null
33
+ ] });
34
+ }
35
+ export {
36
+ CoreVersion
37
+ };
38
+ //# sourceMappingURL=CoreVersion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/admin/CoreVersion.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { CORE_VERSION, CORE_PACKAGE, isNieuwer } from \"../version\";\n\n/**\n * Toont op welke engine deze site draait, en of er een nieuwere is.\n *\n * De laatste versie komt rechtstreeks van de npm-registry. Dat is een publiek, CORS-vrij endpoint\n * en het is de enige bron die niet kan verouderen — een waarde die we zelf ergens zouden bijhouden\n * loopt per definitie achter op de release die hem zou moeten bijwerken.\n *\n * Faalt stil: geen netwerk of een registry die hapert mag het beheer niet in de weg zitten. Je ziet\n * dan alleen de eigen versie, wat nog steeds het antwoord is op de vraag \"waar draai ik op\".\n */\nexport function CoreVersion({ compact = false }: { compact?: boolean }) {\n const [laatste, setLaatste] = useState<string | null>(null);\n\n useEffect(() => {\n const ctrl = new AbortController();\n fetch(`https://registry.npmjs.org/${CORE_PACKAGE}/latest`, { signal: ctrl.signal })\n .then((r) => (r.ok ? r.json() : null))\n .then((d: { version?: string } | null) => {\n if (typeof d?.version === \"string\") setLaatste(d.version);\n })\n .catch(() => {});\n return () => ctrl.abort();\n }, []);\n\n const update = laatste ? isNieuwer(laatste, CORE_VERSION) : false;\n\n if (compact) {\n return (\n <span className=\"font-mono text-[11px] text-slate-500\">\n core {CORE_VERSION}\n {update && <span className=\"ml-1 text-amber-600\">→ {laatste}</span>}\n </span>\n );\n }\n\n return (\n <div className=\"flex flex-wrap items-center gap-2 text-xs\">\n <span className=\"text-slate-500\">Engine</span>\n <span className=\"rounded px-1.5 py-0.5 font-mono text-slate-700\" style={{ background: \"var(--color-background-muted)\" }}>\n {CORE_VERSION}\n </span>\n {update ? (\n <span className=\"text-amber-700\">\n Nieuwer beschikbaar: <span className=\"font-mono\">{laatste}</span>\n </span>\n ) : laatste ? (\n <span className=\"text-slate-500\">Bijgewerkt</span>\n ) : null}\n </div>\n );\n}\n"],"mappings":";AAmCmB,SAOb,KAPa;AAjCnB,SAAS,WAAW,gBAAgB;AACpC,SAAS,cAAc,cAAc,iBAAiB;AAY/C,SAAS,YAAY,EAAE,UAAU,MAAM,GAA0B;AACtE,QAAM,CAAC,SAAS,UAAU,IAAI,SAAwB,IAAI;AAE1D,YAAU,MAAM;AACd,UAAM,OAAO,IAAI,gBAAgB;AACjC,UAAM,8BAA8B,YAAY,WAAW,EAAE,QAAQ,KAAK,OAAO,CAAC,EAC/E,KAAK,CAAC,MAAO,EAAE,KAAK,EAAE,KAAK,IAAI,IAAK,EACpC,KAAK,CAAC,MAAmC;AACxC,UAAI,OAAO,GAAG,YAAY,SAAU,YAAW,EAAE,OAAO;AAAA,IAC1D,CAAC,EACA,MAAM,MAAM;AAAA,IAAC,CAAC;AACjB,WAAO,MAAM,KAAK,MAAM;AAAA,EAC1B,GAAG,CAAC,CAAC;AAEL,QAAM,SAAS,UAAU,UAAU,SAAS,YAAY,IAAI;AAE5D,MAAI,SAAS;AACX,WACE,qBAAC,UAAK,WAAU,wCAAuC;AAAA;AAAA,MAC/C;AAAA,MACL,UAAU,qBAAC,UAAK,WAAU,uBAAsB;AAAA;AAAA,QAAG;AAAA,SAAQ;AAAA,OAC9D;AAAA,EAEJ;AAEA,SACE,qBAAC,SAAI,WAAU,6CACb;AAAA,wBAAC,UAAK,WAAU,kBAAiB,oBAAM;AAAA,IACvC,oBAAC,UAAK,WAAU,kDAAiD,OAAO,EAAE,YAAY,gCAAgC,GACnH,wBACH;AAAA,IACC,SACC,qBAAC,UAAK,WAAU,kBAAiB;AAAA;AAAA,MACV,oBAAC,UAAK,WAAU,aAAa,mBAAQ;AAAA,OAC5D,IACE,UACF,oBAAC,UAAK,WAAU,kBAAiB,wBAAU,IACzC;AAAA,KACN;AAEJ;","names":[]}
@@ -2,15 +2,15 @@
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useRef, useState } from "react";
4
4
  import { addDoc, collection, deleteDoc, doc, serverTimestamp, updateDoc } from "firebase/firestore";
5
- import { getDb } from "../firebase/client";
6
- import { useSiteConfig } from "../config";
7
- import { useNews } from "../components/News";
8
- import { uploadImage } from "./lib/upload";
9
- import { slugify } from "./lib/slugify";
10
- import { useConfirm } from "./ui/ConfirmDialog";
11
- import { useToast } from "./ui/toast";
12
- import { Button, Card, Field, Input, Badge } from "./ui/primitives";
13
- import { RichTextEditor } from "./editors/RichTextEditor";
5
+ import { getDb } from "../firebase/client.js";
6
+ import { useSiteConfig } from "../config.js";
7
+ import { useNews } from "../components/News.js";
8
+ import { uploadImage } from "./lib/upload.js";
9
+ import { slugify } from "./lib/slugify.js";
10
+ import { useConfirm } from "./ui/ConfirmDialog.js";
11
+ import { useToast } from "./ui/toast.js";
12
+ import { Button, Card, Field, Input, Badge } from "./ui/primitives.js";
13
+ import { RichTextEditor } from "./editors/RichTextEditor.js";
14
14
  const empty = { title: "", excerpt: "", body: "", bodyHtml: "", coverUrl: "", date: "", published: false };
15
15
  function NewsEditor() {
16
16
  const { siteId } = useSiteConfig();
@@ -3,11 +3,11 @@ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useState } from "react";
4
4
  import { collection, getDocs, limit, orderBy, query, where } from "firebase/firestore";
5
5
  import { getFunctions, httpsCallable } from "firebase/functions";
6
- import { getDb, getFirebaseApp } from "../firebase/client";
7
- import { functionsRegion } from "../firebase/region";
8
- import { Button, Card, Field, Input } from "./ui/primitives";
9
- import { Select } from "./ui/select";
10
- import { useToast } from "./ui/toast";
6
+ import { getDb, getFirebaseApp } from "../firebase/client.js";
7
+ import { functionsRegion } from "../firebase/region.js";
8
+ import { Button, Card, Field, Input } from "./ui/primitives.js";
9
+ import { Select } from "./ui/select.js";
10
+ import { useToast } from "./ui/toast.js";
11
11
  function callable(name) {
12
12
  const app = getFirebaseApp();
13
13
  if (!app) throw new Error("Firebase niet geconfigureerd.");
@@ -1,5 +1,5 @@
1
- import type { ZodTypeAny } from "zod";
2
- import { type Field } from "../../schema/fields";
1
+ import type { $ZodType as ZodTypeAny } from "zod/v4/core";
2
+ import { type Field } from "../../schema/fields.js";
3
3
  export interface GenericEditorProps {
4
4
  block: Record<string, unknown>;
5
5
  siteId: string;
@@ -1,16 +1,17 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { safeParse } from "zod/v4/core";
3
4
  import { useEffect, useMemo, useState } from "react";
4
- import { Field as FieldRow, Input, Textarea } from "../ui/primitives";
5
- import { Select } from "../ui/select";
6
- import { Switch } from "../ui/switch";
7
- import { ArrayEditor } from "../ui/ArrayEditor";
8
- import { RichTextEditor } from "../editors/RichTextEditor";
9
- import { MediaPickerButton } from "../media/MediaPicker";
10
- import { useRegistry } from "../../blocks/context";
11
- import { subscribePages } from "../lib/pagesClient";
12
- import { introspectToFields, introspectObjectSchema } from "../lib/zodFields";
13
- import { fieldsDefaults, isFieldVisible } from "../../schema/fields";
5
+ import { Field as FieldRow, Input, Textarea } from "../ui/primitives.js";
6
+ import { Select } from "../ui/select.js";
7
+ import { Switch } from "../ui/switch.js";
8
+ import { ArrayEditor } from "../ui/ArrayEditor.js";
9
+ import { RichTextEditor } from "../editors/RichTextEditor.js";
10
+ import { MediaPickerButton } from "../media/MediaPicker.js";
11
+ import { useRegistry } from "../../blocks/context.js";
12
+ import { subscribePages } from "../lib/pagesClient.js";
13
+ import { introspectToFields, introspectObjectSchema } from "../lib/zodFields.js";
14
+ import { fieldsDefaults, isFieldVisible } from "../../schema/fields.js";
14
15
  function FieldsForm({
15
16
  fields,
16
17
  values,
@@ -139,7 +140,7 @@ function JsonField({
139
140
  try {
140
141
  const parsed = JSON.parse(raw);
141
142
  if (schema) {
142
- const res = schema.safeParse(parsed);
143
+ const res = safeParse(schema, parsed);
143
144
  if (!res.success) {
144
145
  if (!opts.silent) setError("Voldoet niet aan het verwachte formaat van dit veld.");
145
146
  return;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/blockEditors/GenericEditor.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * GenericEditor 2.0 (5.0) — het automatisch gegenereerde formulier voor blokken zonder eigen\n * editor, aangedreven door de Field-IR (schema/fields.ts, primo-port):\n *\n * - **repeaters** en **groepen** zijn echte UI (ArrayEditor met toevoegen/verwijderen/↑↓ en een\n * omkaderde subgroep) i.p.v. een JSON-textarea;\n * - **showIf** verbergt velden conditioneel;\n * - date/slider/link/icoon/pagina-verwijzing hebben eigen widgets;\n * - de JSON-textarea bestaat alleen nog als laatste redmiddel (records/scalar-arrays), met\n * schema-validatie vóór commit.\n *\n * `FieldsForm` is los herbruikbaar: custom blocks (Fase B) renderen er hun opgeslagen Field-IR mee.\n */\nimport { useEffect, useMemo, useState } from \"react\";\nimport type { ZodTypeAny } from \"zod\";\nimport { Field as FieldRow, Input, Textarea } from \"../ui/primitives\";\nimport { Select } from \"../ui/select\";\nimport { Switch } from \"../ui/switch\";\nimport { ArrayEditor } from \"../ui/ArrayEditor\";\nimport { RichTextEditor } from \"../editors/RichTextEditor\";\nimport { MediaPickerButton } from \"../media/MediaPicker\";\nimport { useRegistry } from \"../../blocks/context\";\nimport { subscribePages, type DraftPage } from \"../lib/pagesClient\";\nimport { introspectToFields, introspectObjectSchema } from \"../lib/zodFields\";\nimport { fieldsDefaults, isFieldVisible, type Field } from \"../../schema/fields\";\n\nexport interface GenericEditorProps {\n block: Record<string, unknown>;\n siteId: string;\n onChange: (next: Record<string, unknown>) => void;\n}\n\n/** Formulier voor een Field-IR over een waarden-object. Recursief (groepen/repeaters). */\nexport function FieldsForm({\n fields,\n values,\n onChange,\n siteId,\n jsonSchemas,\n}: {\n fields: Field[];\n values: Record<string, unknown>;\n onChange: (patch: Record<string, unknown>) => void;\n siteId: string;\n /** Optioneel: sub-schema per json-veldsleutel voor validatie vóór commit. */\n jsonSchemas?: Record<string, ZodTypeAny>;\n}) {\n return (\n <div className=\"grid gap-3\">\n {fields.filter((f) => isFieldVisible(f, values)).map((f) => (\n <FieldWidget key={f.key} f={f} values={values} onChange={onChange} siteId={siteId} jsonSchema={jsonSchemas?.[f.key]} />\n ))}\n </div>\n );\n}\n\nfunction FieldWidget({\n f,\n values,\n onChange,\n siteId,\n jsonSchema,\n}: {\n f: Field;\n values: Record<string, unknown>;\n onChange: (patch: Record<string, unknown>) => void;\n siteId: string;\n jsonSchema?: ZodTypeAny;\n}) {\n const v = values[f.key];\n const set = (val: unknown) => onChange({ [f.key]: val });\n\n switch (f.type) {\n case \"switch\":\n return (\n <label className=\"flex items-center gap-2 text-sm\">\n <Switch checked={Boolean(v)} onCheckedChange={set} />\n {f.label}\n </label>\n );\n case \"select\":\n return <SelectField f={f} v={v} set={set} />;\n case \"number\":\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <Input\n type=\"number\"\n min={f.min}\n max={f.max}\n step={f.step}\n value={v == null ? \"\" : String(v)}\n onChange={(e) => set(e.target.value === \"\" ? undefined : Number(e.target.value))}\n />\n </FieldRow>\n );\n case \"slider\": {\n const val = typeof v === \"number\" ? v : f.min;\n return (\n <FieldRow label={`${f.label} (${val})`} hint={f.hint}>\n <input\n type=\"range\"\n min={f.min}\n max={f.max}\n step={f.step ?? 1}\n value={val}\n onChange={(e) => set(Number(e.target.value))}\n className=\"w-full\"\n />\n </FieldRow>\n );\n }\n case \"date\":\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <Input type=\"date\" value={String(v ?? \"\")} onChange={(e) => set(e.target.value)} />\n </FieldRow>\n );\n case \"richtext\":\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <RichTextEditor siteId={siteId} value={String(v ?? \"\")} onChange={set} />\n </FieldRow>\n );\n case \"image\":\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <div className=\"flex items-center gap-2\">\n <Input value={String(v ?? \"\")} onChange={(e) => set(e.target.value)} placeholder=\"URL of kies →\" />\n <MediaPickerButton siteId={siteId} onSelect={(m) => set(m.url)} />\n </div>\n </FieldRow>\n );\n case \"url\":\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <Input value={String(v ?? \"\")} onChange={(e) => set(e.target.value)} placeholder=\"https://… of /pagina\" />\n </FieldRow>\n );\n case \"icon\":\n return (\n <FieldRow label={f.label} hint={f.hint ?? \"Emoji, bv. ✂️\"}>\n <Input value={String(v ?? \"\")} maxLength={16} onChange={(e) => set(e.target.value)} className=\"w-24\" />\n </FieldRow>\n );\n case \"link\": {\n const link = (v as { label?: string; href?: string } | undefined) ?? {};\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <div className=\"grid grid-cols-2 gap-2\">\n <Input value={link.label ?? \"\"} placeholder=\"Tekst\" onChange={(e) => set({ ...link, label: e.target.value })} />\n <Input value={link.href ?? \"\"} placeholder=\"/pagina of https://…\" onChange={(e) => set({ ...link, href: e.target.value })} />\n </div>\n </FieldRow>\n );\n }\n case \"group\": {\n const group = (v as Record<string, unknown> | undefined) ?? {};\n return (\n <fieldset className=\"grid gap-2 rounded-lg border border-slate-200 p-3\">\n <legend className=\"px-1 text-xs font-semibold text-slate-500\">{f.label}</legend>\n <FieldsForm fields={f.fields} values={group} onChange={(p) => set({ ...group, ...p })} siteId={siteId} />\n </fieldset>\n );\n }\n case \"repeater\": {\n const items = (Array.isArray(v) ? v : []) as Record<string, unknown>[];\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <ArrayEditor<Record<string, unknown>>\n items={items}\n onChange={set}\n newItem={() => fieldsDefaults(f.fields)}\n addLabel={`+ ${f.itemLabel ?? \"Item\"}`}\n renderRow={(item, update) => (\n <FieldsForm fields={f.fields} values={item} onChange={(p) => update(p)} siteId={siteId} />\n )}\n />\n </FieldRow>\n );\n }\n case \"pageRef\":\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <PageRefPicker siteId={siteId} value={String(v ?? \"\")} onChange={set} />\n </FieldRow>\n );\n case \"info\":\n return <p className=\"text-xs text-slate-500\">{f.text}</p>;\n case \"textarea\":\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <Textarea rows={f.rows ?? 4} value={String(v ?? \"\")} onChange={(e) => set(e.target.value)} />\n </FieldRow>\n );\n case \"json\":\n return <JsonField label={f.label} value={v} onCommit={set} schema={jsonSchema} />;\n case \"text\":\n default:\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <Input value={String(v ?? \"\")} placeholder={f.type === \"text\" ? f.placeholder : undefined} onChange={(e) => set(e.target.value)} />\n </FieldRow>\n );\n }\n}\n\n/** Pagina-verwijzing: kies uit de bestaande pagina's van de site (live lijst). */\nfunction PageRefPicker({ siteId, value, onChange }: { siteId: string; value: string; onChange: (v: string) => void }) {\n const [pages, setPages] = useState<DraftPage[]>([]);\n useEffect(() => subscribePages(siteId, setPages), [siteId]);\n const options = useMemo(() => {\n const opts = pages\n .slice()\n .sort((a, b) => (a.path < b.path ? -1 : 1))\n .map((p) => ({ value: p.path, label: `${p.title || p.path} (${p.path})` }));\n // Bewaar een waarde die (nog) niet in de lijst staat, zodat niets stilletjes wegvalt.\n if (value && !opts.some((o) => o.value === value)) opts.unshift({ value, label: value });\n return [{ value: \"\", label: \"— geen —\" }, ...opts];\n }, [pages, value]);\n return <Select value={value} onValueChange={onChange} options={options} />;\n}\n\n/** Laatste redmiddel voor complexe velden: JSON met schema-validatie vóór commit. */\nfunction JsonField({\n label,\n value,\n onCommit,\n schema,\n}: {\n label: string;\n value: unknown;\n onCommit: (v: unknown) => void;\n schema?: ZodTypeAny;\n}) {\n const [text, setText] = useState(() => JSON.stringify(value ?? null, null, 2));\n const [error, setError] = useState<string | null>(null);\n // Committeert de gegeven tekst (niet de state — die is bij een net getypte toets nog niet\n // bijgewerkt). `silent`: tijdens het typen geen foutmelding tonen voor onvolledige JSON (bv.\n // een net geopende `[` of `{`) — pas bij blur laten we die zien. VEILIGHEIDSNET: committeert bij\n // élke wijziging i.p.v. uitsluitend op blur, want blur bleek op mobiel Safari niet altijd te\n // vuren vóór een externe \"Opslaan\"-klik (race), waardoor een bewerking hier stil verloren ging.\n const commit = (raw: string, opts: { silent: boolean }) => {\n try {\n const parsed = JSON.parse(raw) as unknown;\n if (schema) {\n const res = schema.safeParse(parsed);\n if (!res.success) {\n if (!opts.silent) setError(\"Voldoet niet aan het verwachte formaat van dit veld.\");\n return;\n }\n setError(null);\n onCommit(res.data);\n return;\n }\n setError(null);\n onCommit(parsed);\n } catch {\n if (!opts.silent) setError(\"Geen geldige JSON.\");\n }\n };\n return (\n <FieldRow label={`${label} (JSON)`} hint=\"Geavanceerd veld — wordt gecontroleerd vóór opslaan.\">\n <Textarea\n rows={6}\n value={text}\n onChange={(e) => {\n setText(e.target.value);\n commit(e.target.value, { silent: true });\n }}\n onBlur={() => commit(text, { silent: false })}\n spellCheck={false}\n className=\"font-mono text-xs\"\n />\n {error && <p className=\"text-xs text-red-600\">{error}</p>}\n </FieldRow>\n );\n}\n\n/**\n * De blok-editor-fallback: leidt de Field-IR af uit het Zod-schema van het blok (+ optionele\n * `fields`-hints van `defineBlock`) en rendert het formulier. Zo is een blok zonder maatwerk-\n * editor nooit een doodlopend pad voor de klant.\n */\nexport function GenericEditor({ block, siteId, onChange }: GenericEditorProps) {\n const registry = useRegistry();\n const def = registry.get(String(block._type ?? \"\"));\n const fields = useMemo(() => {\n if (!def) return [];\n // Custom blocks (5.0) dragen hun opgeslagen Field-IR mee — rijker dan introspectie\n // (showIf/date/slider-metadata gaat door fieldsToZod heen verloren).\n const custom = (def as { customFields?: Field[] }).customFields;\n return custom ?? introspectToFields(def.schema, def.fields);\n }, [def]);\n // Sub-schema's voor json-velden (validatie vóór commit), uit de platte introspectie.\n const jsonSchemas = useMemo(() => {\n if (!def) return {};\n const out: Record<string, ZodTypeAny> = {};\n for (const f of introspectObjectSchema(def.schema)) if (f.kind === \"json\") out[f.key] = f.schema;\n return out;\n }, [def]);\n if (fields.length === 0) {\n return <p className=\"text-sm text-slate-500\">Voor dit bloktype is geen editor beschikbaar.</p>;\n }\n return (\n <FieldsForm\n fields={fields}\n values={block}\n onChange={(p) => onChange({ ...block, ...p })}\n siteId={siteId}\n jsonSchemas={jsonSchemas}\n />\n );\n}\n\n/**\n * Select als eigen component: commit de default-optie bij mount wanneer er nog geen waarde is.\n * De dropdown TOONDE optie 1 maar sloeg 'm niet op (weergave ≠ opslag) — een verplichte enum kon\n * zo leeg gepubliceerd worden tenzij het Zod-schema toevallig een default had.\n */\nfunction SelectField({\n f,\n v,\n set,\n}: {\n f: Extract<Field, { type: \"select\" }>;\n v: unknown;\n set: (val: unknown) => void;\n}) {\n useEffect(() => {\n if (v == null && f.options[0] !== undefined) set(f.options[0].value);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <Select\n value={String(v ?? f.options[0]?.value ?? \"\")}\n onValueChange={set}\n options={f.options.map((o) => ({ value: o.value, label: o.label || o.value }))}\n />\n </FieldRow>\n );\n}\n"],"mappings":";AAoDQ,cAyBA,YAzBA;AArCR,SAAS,WAAW,SAAS,gBAAgB;AAE7C,SAAS,SAAS,UAAU,OAAO,gBAAgB;AACnD,SAAS,cAAc;AACvB,SAAS,cAAc;AACvB,SAAS,mBAAmB;AAC5B,SAAS,sBAAsB;AAC/B,SAAS,yBAAyB;AAClC,SAAS,mBAAmB;AAC5B,SAAS,sBAAsC;AAC/C,SAAS,oBAAoB,8BAA8B;AAC3D,SAAS,gBAAgB,sBAAkC;AASpD,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAOG;AACD,SACE,oBAAC,SAAI,WAAU,cACZ,iBAAO,OAAO,CAAC,MAAM,eAAe,GAAG,MAAM,CAAC,EAAE,IAAI,CAAC,MACpD,oBAAC,eAAwB,GAAM,QAAgB,UAAoB,QAAgB,YAAY,cAAc,EAAE,GAAG,KAAhG,EAAE,GAAiG,CACtH,GACH;AAEJ;AAEA,SAAS,YAAY;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAMG;AACD,QAAM,IAAI,OAAO,EAAE,GAAG;AACtB,QAAM,MAAM,CAAC,QAAiB,SAAS,EAAE,CAAC,EAAE,GAAG,GAAG,IAAI,CAAC;AAEvD,UAAQ,EAAE,MAAM;AAAA,IACd,KAAK;AACH,aACE,qBAAC,WAAM,WAAU,mCACf;AAAA,4BAAC,UAAO,SAAS,QAAQ,CAAC,GAAG,iBAAiB,KAAK;AAAA,QAClD,EAAE;AAAA,SACL;AAAA,IAEJ,KAAK;AACH,aAAO,oBAAC,eAAY,GAAM,GAAM,KAAU;AAAA,IAC5C,KAAK;AACH,aACE,oBAAC,YAAS,OAAO,EAAE,OAAO,MAAM,EAAE,MAChC;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,KAAK,EAAE;AAAA,UACP,KAAK,EAAE;AAAA,UACP,MAAM,EAAE;AAAA,UACR,OAAO,KAAK,OAAO,KAAK,OAAO,CAAC;AAAA,UAChC,UAAU,CAAC,MAAM,IAAI,EAAE,OAAO,UAAU,KAAK,SAAY,OAAO,EAAE,OAAO,KAAK,CAAC;AAAA;AAAA,MACjF,GACF;AAAA,IAEJ,KAAK,UAAU;AACb,YAAM,MAAM,OAAO,MAAM,WAAW,IAAI,EAAE;AAC1C,aACE,oBAAC,YAAS,OAAO,GAAG,EAAE,KAAK,KAAK,GAAG,KAAK,MAAM,EAAE,MAC9C;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,KAAK,EAAE;AAAA,UACP,KAAK,EAAE;AAAA,UACP,MAAM,EAAE,QAAQ;AAAA,UAChB,OAAO;AAAA,UACP,UAAU,CAAC,MAAM,IAAI,OAAO,EAAE,OAAO,KAAK,CAAC;AAAA,UAC3C,WAAU;AAAA;AAAA,MACZ,GACF;AAAA,IAEJ;AAAA,IACA,KAAK;AACH,aACE,oBAAC,YAAS,OAAO,EAAE,OAAO,MAAM,EAAE,MAChC,8BAAC,SAAM,MAAK,QAAO,OAAO,OAAO,KAAK,EAAE,GAAG,UAAU,CAAC,MAAM,IAAI,EAAE,OAAO,KAAK,GAAG,GACnF;AAAA,IAEJ,KAAK;AACH,aACE,oBAAC,YAAS,OAAO,EAAE,OAAO,MAAM,EAAE,MAChC,8BAAC,kBAAe,QAAgB,OAAO,OAAO,KAAK,EAAE,GAAG,UAAU,KAAK,GACzE;AAAA,IAEJ,KAAK;AACH,aACE,oBAAC,YAAS,OAAO,EAAE,OAAO,MAAM,EAAE,MAChC,+BAAC,SAAI,WAAU,2BACb;AAAA,4BAAC,SAAM,OAAO,OAAO,KAAK,EAAE,GAAG,UAAU,CAAC,MAAM,IAAI,EAAE,OAAO,KAAK,GAAG,aAAY,sBAAgB;AAAA,QACjG,oBAAC,qBAAkB,QAAgB,UAAU,CAAC,MAAM,IAAI,EAAE,GAAG,GAAG;AAAA,SAClE,GACF;AAAA,IAEJ,KAAK;AACH,aACE,oBAAC,YAAS,OAAO,EAAE,OAAO,MAAM,EAAE,MAChC,8BAAC,SAAM,OAAO,OAAO,KAAK,EAAE,GAAG,UAAU,CAAC,MAAM,IAAI,EAAE,OAAO,KAAK,GAAG,aAAY,6BAAuB,GAC1G;AAAA,IAEJ,KAAK;AACH,aACE,oBAAC,YAAS,OAAO,EAAE,OAAO,MAAM,EAAE,QAAQ,2BACxC,8BAAC,SAAM,OAAO,OAAO,KAAK,EAAE,GAAG,WAAW,IAAI,UAAU,CAAC,MAAM,IAAI,EAAE,OAAO,KAAK,GAAG,WAAU,QAAO,GACvG;AAAA,IAEJ,KAAK,QAAQ;AACX,YAAM,OAAQ,KAAuD,CAAC;AACtE,aACE,oBAAC,YAAS,OAAO,EAAE,OAAO,MAAM,EAAE,MAChC,+BAAC,SAAI,WAAU,0BACb;AAAA,4BAAC,SAAM,OAAO,KAAK,SAAS,IAAI,aAAY,SAAQ,UAAU,CAAC,MAAM,IAAI,EAAE,GAAG,MAAM,OAAO,EAAE,OAAO,MAAM,CAAC,GAAG;AAAA,QAC9G,oBAAC,SAAM,OAAO,KAAK,QAAQ,IAAI,aAAY,6BAAuB,UAAU,CAAC,MAAM,IAAI,EAAE,GAAG,MAAM,MAAM,EAAE,OAAO,MAAM,CAAC,GAAG;AAAA,SAC7H,GACF;AAAA,IAEJ;AAAA,IACA,KAAK,SAAS;AACZ,YAAM,QAAS,KAA6C,CAAC;AAC7D,aACE,qBAAC,cAAS,WAAU,qDAClB;AAAA,4BAAC,YAAO,WAAU,6CAA6C,YAAE,OAAM;AAAA,QACvE,oBAAC,cAAW,QAAQ,EAAE,QAAQ,QAAQ,OAAO,UAAU,CAAC,MAAM,IAAI,EAAE,GAAG,OAAO,GAAG,EAAE,CAAC,GAAG,QAAgB;AAAA,SACzG;AAAA,IAEJ;AAAA,IACA,KAAK,YAAY;AACf,YAAM,QAAS,MAAM,QAAQ,CAAC,IAAI,IAAI,CAAC;AACvC,aACE,oBAAC,YAAS,OAAO,EAAE,OAAO,MAAM,EAAE,MAChC;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA,UAAU;AAAA,UACV,SAAS,MAAM,eAAe,EAAE,MAAM;AAAA,UACtC,UAAU,KAAK,EAAE,aAAa,MAAM;AAAA,UACpC,WAAW,CAAC,MAAM,WAChB,oBAAC,cAAW,QAAQ,EAAE,QAAQ,QAAQ,MAAM,UAAU,CAAC,MAAM,OAAO,CAAC,GAAG,QAAgB;AAAA;AAAA,MAE5F,GACF;AAAA,IAEJ;AAAA,IACA,KAAK;AACH,aACE,oBAAC,YAAS,OAAO,EAAE,OAAO,MAAM,EAAE,MAChC,8BAAC,iBAAc,QAAgB,OAAO,OAAO,KAAK,EAAE,GAAG,UAAU,KAAK,GACxE;AAAA,IAEJ,KAAK;AACH,aAAO,oBAAC,OAAE,WAAU,0BAA0B,YAAE,MAAK;AAAA,IACvD,KAAK;AACH,aACE,oBAAC,YAAS,OAAO,EAAE,OAAO,MAAM,EAAE,MAChC,8BAAC,YAAS,MAAM,EAAE,QAAQ,GAAG,OAAO,OAAO,KAAK,EAAE,GAAG,UAAU,CAAC,MAAM,IAAI,EAAE,OAAO,KAAK,GAAG,GAC7F;AAAA,IAEJ,KAAK;AACH,aAAO,oBAAC,aAAU,OAAO,EAAE,OAAO,OAAO,GAAG,UAAU,KAAK,QAAQ,YAAY;AAAA,IACjF,KAAK;AAAA,IACL;AACE,aACE,oBAAC,YAAS,OAAO,EAAE,OAAO,MAAM,EAAE,MAChC,8BAAC,SAAM,OAAO,OAAO,KAAK,EAAE,GAAG,aAAa,EAAE,SAAS,SAAS,EAAE,cAAc,QAAW,UAAU,CAAC,MAAM,IAAI,EAAE,OAAO,KAAK,GAAG,GACnI;AAAA,EAEN;AACF;AAGA,SAAS,cAAc,EAAE,QAAQ,OAAO,SAAS,GAAqE;AACpH,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAsB,CAAC,CAAC;AAClD,YAAU,MAAM,eAAe,QAAQ,QAAQ,GAAG,CAAC,MAAM,CAAC;AAC1D,QAAM,UAAU,QAAQ,MAAM;AAC5B,UAAM,OAAO,MACV,MAAM,EACN,KAAK,CAAC,GAAG,MAAO,EAAE,OAAO,EAAE,OAAO,KAAK,CAAE,EACzC,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,GAAG,EAAE,SAAS,EAAE,IAAI,KAAK,EAAE,IAAI,IAAI,EAAE;AAE5E,QAAI,SAAS,CAAC,KAAK,KAAK,CAAC,MAAM,EAAE,UAAU,KAAK,EAAG,MAAK,QAAQ,EAAE,OAAO,OAAO,MAAM,CAAC;AACvF,WAAO,CAAC,EAAE,OAAO,IAAI,OAAO,qBAAW,GAAG,GAAG,IAAI;AAAA,EACnD,GAAG,CAAC,OAAO,KAAK,CAAC;AACjB,SAAO,oBAAC,UAAO,OAAc,eAAe,UAAU,SAAkB;AAC1E;AAGA,SAAS,UAAU;AAAA,EACjB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKG;AACD,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,MAAM,KAAK,UAAU,SAAS,MAAM,MAAM,CAAC,CAAC;AAC7E,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAwB,IAAI;AAMtD,QAAM,SAAS,CAAC,KAAa,SAA8B;AACzD,QAAI;AACF,YAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,UAAI,QAAQ;AACV,cAAM,MAAM,OAAO,UAAU,MAAM;AACnC,YAAI,CAAC,IAAI,SAAS;AAChB,cAAI,CAAC,KAAK,OAAQ,UAAS,sDAAsD;AACjF;AAAA,QACF;AACA,iBAAS,IAAI;AACb,iBAAS,IAAI,IAAI;AACjB;AAAA,MACF;AACA,eAAS,IAAI;AACb,eAAS,MAAM;AAAA,IACjB,QAAQ;AACN,UAAI,CAAC,KAAK,OAAQ,UAAS,oBAAoB;AAAA,IACjD;AAAA,EACF;AACA,SACE,qBAAC,YAAS,OAAO,GAAG,KAAK,WAAW,MAAK,mEACvC;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAM;AAAA,QACN,OAAO;AAAA,QACP,UAAU,CAAC,MAAM;AACf,kBAAQ,EAAE,OAAO,KAAK;AACtB,iBAAO,EAAE,OAAO,OAAO,EAAE,QAAQ,KAAK,CAAC;AAAA,QACzC;AAAA,QACA,QAAQ,MAAM,OAAO,MAAM,EAAE,QAAQ,MAAM,CAAC;AAAA,QAC5C,YAAY;AAAA,QACZ,WAAU;AAAA;AAAA,IACZ;AAAA,IACC,SAAS,oBAAC,OAAE,WAAU,wBAAwB,iBAAM;AAAA,KACvD;AAEJ;AAOO,SAAS,cAAc,EAAE,OAAO,QAAQ,SAAS,GAAuB;AAC7E,QAAM,WAAW,YAAY;AAC7B,QAAM,MAAM,SAAS,IAAI,OAAO,MAAM,SAAS,EAAE,CAAC;AAClD,QAAM,SAAS,QAAQ,MAAM;AAC3B,QAAI,CAAC,IAAK,QAAO,CAAC;AAGlB,UAAM,SAAU,IAAmC;AACnD,WAAO,UAAU,mBAAmB,IAAI,QAAQ,IAAI,MAAM;AAAA,EAC5D,GAAG,CAAC,GAAG,CAAC;AAER,QAAM,cAAc,QAAQ,MAAM;AAChC,QAAI,CAAC,IAAK,QAAO,CAAC;AAClB,UAAM,MAAkC,CAAC;AACzC,eAAW,KAAK,uBAAuB,IAAI,MAAM,EAAG,KAAI,EAAE,SAAS,OAAQ,KAAI,EAAE,GAAG,IAAI,EAAE;AAC1F,WAAO;AAAA,EACT,GAAG,CAAC,GAAG,CAAC;AACR,MAAI,OAAO,WAAW,GAAG;AACvB,WAAO,oBAAC,OAAE,WAAU,0BAAyB,2DAA6C;AAAA,EAC5F;AACA,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,QAAQ;AAAA,MACR,UAAU,CAAC,MAAM,SAAS,EAAE,GAAG,OAAO,GAAG,EAAE,CAAC;AAAA,MAC5C;AAAA,MACA;AAAA;AAAA,EACF;AAEJ;AAOA,SAAS,YAAY;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,YAAU,MAAM;AACd,QAAI,KAAK,QAAQ,EAAE,QAAQ,CAAC,MAAM,OAAW,KAAI,EAAE,QAAQ,CAAC,EAAE,KAAK;AAAA,EAErE,GAAG,CAAC,CAAC;AACL,SACE,oBAAC,YAAS,OAAO,EAAE,OAAO,MAAM,EAAE,MAChC;AAAA,IAAC;AAAA;AAAA,MACC,OAAO,OAAO,KAAK,EAAE,QAAQ,CAAC,GAAG,SAAS,EAAE;AAAA,MAC5C,eAAe;AAAA,MACf,SAAS,EAAE,QAAQ,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE;AAAA;AAAA,EAC/E,GACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/blockEditors/GenericEditor.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * GenericEditor 2.0 (5.0) — het automatisch gegenereerde formulier voor blokken zonder eigen\n * editor, aangedreven door de Field-IR (schema/fields.ts, primo-port):\n *\n * - **repeaters** en **groepen** zijn echte UI (ArrayEditor met toevoegen/verwijderen/↑↓ en een\n * omkaderde subgroep) i.p.v. een JSON-textarea;\n * - **showIf** verbergt velden conditioneel;\n * - date/slider/link/icoon/pagina-verwijzing hebben eigen widgets;\n * - de JSON-textarea bestaat alleen nog als laatste redmiddel (records/scalar-arrays), met\n * schema-validatie vóór commit.\n *\n * `FieldsForm` is los herbruikbaar: custom blocks (Fase B) renderen er hun opgeslagen Field-IR mee.\n */\nimport { safeParse } from \"zod/v4/core\";\nimport { useEffect, useMemo, useState } from \"react\";\nimport type { $ZodType as ZodTypeAny } from \"zod/v4/core\";\nimport { Field as FieldRow, Input, Textarea } from \"../ui/primitives\";\nimport { Select } from \"../ui/select\";\nimport { Switch } from \"../ui/switch\";\nimport { ArrayEditor } from \"../ui/ArrayEditor\";\nimport { RichTextEditor } from \"../editors/RichTextEditor\";\nimport { MediaPickerButton } from \"../media/MediaPicker\";\nimport { useRegistry } from \"../../blocks/context\";\nimport { subscribePages, type DraftPage } from \"../lib/pagesClient\";\nimport { introspectToFields, introspectObjectSchema } from \"../lib/zodFields\";\nimport { fieldsDefaults, isFieldVisible, type Field } from \"../../schema/fields\";\n\nexport interface GenericEditorProps {\n block: Record<string, unknown>;\n siteId: string;\n onChange: (next: Record<string, unknown>) => void;\n}\n\n/** Formulier voor een Field-IR over een waarden-object. Recursief (groepen/repeaters). */\nexport function FieldsForm({\n fields,\n values,\n onChange,\n siteId,\n jsonSchemas,\n}: {\n fields: Field[];\n values: Record<string, unknown>;\n onChange: (patch: Record<string, unknown>) => void;\n siteId: string;\n /** Optioneel: sub-schema per json-veldsleutel voor validatie vóór commit. */\n jsonSchemas?: Record<string, ZodTypeAny>;\n}) {\n return (\n <div className=\"grid gap-3\">\n {fields.filter((f) => isFieldVisible(f, values)).map((f) => (\n <FieldWidget key={f.key} f={f} values={values} onChange={onChange} siteId={siteId} jsonSchema={jsonSchemas?.[f.key]} />\n ))}\n </div>\n );\n}\n\nfunction FieldWidget({\n f,\n values,\n onChange,\n siteId,\n jsonSchema,\n}: {\n f: Field;\n values: Record<string, unknown>;\n onChange: (patch: Record<string, unknown>) => void;\n siteId: string;\n jsonSchema?: ZodTypeAny;\n}) {\n const v = values[f.key];\n const set = (val: unknown) => onChange({ [f.key]: val });\n\n switch (f.type) {\n case \"switch\":\n return (\n <label className=\"flex items-center gap-2 text-sm\">\n <Switch checked={Boolean(v)} onCheckedChange={set} />\n {f.label}\n </label>\n );\n case \"select\":\n return <SelectField f={f} v={v} set={set} />;\n case \"number\":\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <Input\n type=\"number\"\n min={f.min}\n max={f.max}\n step={f.step}\n value={v == null ? \"\" : String(v)}\n onChange={(e) => set(e.target.value === \"\" ? undefined : Number(e.target.value))}\n />\n </FieldRow>\n );\n case \"slider\": {\n const val = typeof v === \"number\" ? v : f.min;\n return (\n <FieldRow label={`${f.label} (${val})`} hint={f.hint}>\n <input\n type=\"range\"\n min={f.min}\n max={f.max}\n step={f.step ?? 1}\n value={val}\n onChange={(e) => set(Number(e.target.value))}\n className=\"w-full\"\n />\n </FieldRow>\n );\n }\n case \"date\":\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <Input type=\"date\" value={String(v ?? \"\")} onChange={(e) => set(e.target.value)} />\n </FieldRow>\n );\n case \"richtext\":\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <RichTextEditor siteId={siteId} value={String(v ?? \"\")} onChange={set} />\n </FieldRow>\n );\n case \"image\":\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <div className=\"flex items-center gap-2\">\n <Input value={String(v ?? \"\")} onChange={(e) => set(e.target.value)} placeholder=\"URL of kies →\" />\n <MediaPickerButton siteId={siteId} onSelect={(m) => set(m.url)} />\n </div>\n </FieldRow>\n );\n case \"url\":\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <Input value={String(v ?? \"\")} onChange={(e) => set(e.target.value)} placeholder=\"https://… of /pagina\" />\n </FieldRow>\n );\n case \"icon\":\n return (\n <FieldRow label={f.label} hint={f.hint ?? \"Emoji, bv. ✂️\"}>\n <Input value={String(v ?? \"\")} maxLength={16} onChange={(e) => set(e.target.value)} className=\"w-24\" />\n </FieldRow>\n );\n case \"link\": {\n const link = (v as { label?: string; href?: string } | undefined) ?? {};\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <div className=\"grid grid-cols-2 gap-2\">\n <Input value={link.label ?? \"\"} placeholder=\"Tekst\" onChange={(e) => set({ ...link, label: e.target.value })} />\n <Input value={link.href ?? \"\"} placeholder=\"/pagina of https://…\" onChange={(e) => set({ ...link, href: e.target.value })} />\n </div>\n </FieldRow>\n );\n }\n case \"group\": {\n const group = (v as Record<string, unknown> | undefined) ?? {};\n return (\n <fieldset className=\"grid gap-2 rounded-lg border border-slate-200 p-3\">\n <legend className=\"px-1 text-xs font-semibold text-slate-500\">{f.label}</legend>\n <FieldsForm fields={f.fields} values={group} onChange={(p) => set({ ...group, ...p })} siteId={siteId} />\n </fieldset>\n );\n }\n case \"repeater\": {\n const items = (Array.isArray(v) ? v : []) as Record<string, unknown>[];\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <ArrayEditor<Record<string, unknown>>\n items={items}\n onChange={set}\n newItem={() => fieldsDefaults(f.fields)}\n addLabel={`+ ${f.itemLabel ?? \"Item\"}`}\n renderRow={(item, update) => (\n <FieldsForm fields={f.fields} values={item} onChange={(p) => update(p)} siteId={siteId} />\n )}\n />\n </FieldRow>\n );\n }\n case \"pageRef\":\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <PageRefPicker siteId={siteId} value={String(v ?? \"\")} onChange={set} />\n </FieldRow>\n );\n case \"info\":\n return <p className=\"text-xs text-slate-500\">{f.text}</p>;\n case \"textarea\":\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <Textarea rows={f.rows ?? 4} value={String(v ?? \"\")} onChange={(e) => set(e.target.value)} />\n </FieldRow>\n );\n case \"json\":\n return <JsonField label={f.label} value={v} onCommit={set} schema={jsonSchema} />;\n case \"text\":\n default:\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <Input value={String(v ?? \"\")} placeholder={f.type === \"text\" ? f.placeholder : undefined} onChange={(e) => set(e.target.value)} />\n </FieldRow>\n );\n }\n}\n\n/** Pagina-verwijzing: kies uit de bestaande pagina's van de site (live lijst). */\nfunction PageRefPicker({ siteId, value, onChange }: { siteId: string; value: string; onChange: (v: string) => void }) {\n const [pages, setPages] = useState<DraftPage[]>([]);\n useEffect(() => subscribePages(siteId, setPages), [siteId]);\n const options = useMemo(() => {\n const opts = pages\n .slice()\n .sort((a, b) => (a.path < b.path ? -1 : 1))\n .map((p) => ({ value: p.path, label: `${p.title || p.path} (${p.path})` }));\n // Bewaar een waarde die (nog) niet in de lijst staat, zodat niets stilletjes wegvalt.\n if (value && !opts.some((o) => o.value === value)) opts.unshift({ value, label: value });\n return [{ value: \"\", label: \"— geen —\" }, ...opts];\n }, [pages, value]);\n return <Select value={value} onValueChange={onChange} options={options} />;\n}\n\n/** Laatste redmiddel voor complexe velden: JSON met schema-validatie vóór commit. */\nfunction JsonField({\n label,\n value,\n onCommit,\n schema,\n}: {\n label: string;\n value: unknown;\n onCommit: (v: unknown) => void;\n schema?: ZodTypeAny;\n}) {\n const [text, setText] = useState(() => JSON.stringify(value ?? null, null, 2));\n const [error, setError] = useState<string | null>(null);\n // Committeert de gegeven tekst (niet de state — die is bij een net getypte toets nog niet\n // bijgewerkt). `silent`: tijdens het typen geen foutmelding tonen voor onvolledige JSON (bv.\n // een net geopende `[` of `{`) — pas bij blur laten we die zien. VEILIGHEIDSNET: committeert bij\n // élke wijziging i.p.v. uitsluitend op blur, want blur bleek op mobiel Safari niet altijd te\n // vuren vóór een externe \"Opslaan\"-klik (race), waardoor een bewerking hier stil verloren ging.\n const commit = (raw: string, opts: { silent: boolean }) => {\n try {\n const parsed = JSON.parse(raw) as unknown;\n if (schema) {\n const res = safeParse(schema, parsed);\n if (!res.success) {\n if (!opts.silent) setError(\"Voldoet niet aan het verwachte formaat van dit veld.\");\n return;\n }\n setError(null);\n onCommit(res.data);\n return;\n }\n setError(null);\n onCommit(parsed);\n } catch {\n if (!opts.silent) setError(\"Geen geldige JSON.\");\n }\n };\n return (\n <FieldRow label={`${label} (JSON)`} hint=\"Geavanceerd veld — wordt gecontroleerd vóór opslaan.\">\n <Textarea\n rows={6}\n value={text}\n onChange={(e) => {\n setText(e.target.value);\n commit(e.target.value, { silent: true });\n }}\n onBlur={() => commit(text, { silent: false })}\n spellCheck={false}\n className=\"font-mono text-xs\"\n />\n {error && <p className=\"text-xs text-red-600\">{error}</p>}\n </FieldRow>\n );\n}\n\n/**\n * De blok-editor-fallback: leidt de Field-IR af uit het Zod-schema van het blok (+ optionele\n * `fields`-hints van `defineBlock`) en rendert het formulier. Zo is een blok zonder maatwerk-\n * editor nooit een doodlopend pad voor de klant.\n */\nexport function GenericEditor({ block, siteId, onChange }: GenericEditorProps) {\n const registry = useRegistry();\n const def = registry.get(String(block._type ?? \"\"));\n const fields = useMemo(() => {\n if (!def) return [];\n // Custom blocks (5.0) dragen hun opgeslagen Field-IR mee — rijker dan introspectie\n // (showIf/date/slider-metadata gaat door fieldsToZod heen verloren).\n const custom = (def as { customFields?: Field[] }).customFields;\n return custom ?? introspectToFields(def.schema, def.fields);\n }, [def]);\n // Sub-schema's voor json-velden (validatie vóór commit), uit de platte introspectie.\n const jsonSchemas = useMemo(() => {\n if (!def) return {};\n const out: Record<string, ZodTypeAny> = {};\n for (const f of introspectObjectSchema(def.schema)) if (f.kind === \"json\") out[f.key] = f.schema;\n return out;\n }, [def]);\n if (fields.length === 0) {\n return <p className=\"text-sm text-slate-500\">Voor dit bloktype is geen editor beschikbaar.</p>;\n }\n return (\n <FieldsForm\n fields={fields}\n values={block}\n onChange={(p) => onChange({ ...block, ...p })}\n siteId={siteId}\n jsonSchemas={jsonSchemas}\n />\n );\n}\n\n/**\n * Select als eigen component: commit de default-optie bij mount wanneer er nog geen waarde is.\n * De dropdown TOONDE optie 1 maar sloeg 'm niet op (weergave ≠ opslag) — een verplichte enum kon\n * zo leeg gepubliceerd worden tenzij het Zod-schema toevallig een default had.\n */\nfunction SelectField({\n f,\n v,\n set,\n}: {\n f: Extract<Field, { type: \"select\" }>;\n v: unknown;\n set: (val: unknown) => void;\n}) {\n useEffect(() => {\n if (v == null && f.options[0] !== undefined) set(f.options[0].value);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <Select\n value={String(v ?? f.options[0]?.value ?? \"\")}\n onValueChange={set}\n options={f.options.map((o) => ({ value: o.value, label: o.label || o.value }))}\n />\n </FieldRow>\n );\n}\n"],"mappings":"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","names":[]}
@@ -1,4 +1,4 @@
1
- import type { BlockStyle } from "../../schema/blocks";
1
+ import type { BlockStyle } from "../../schema/blocks.js";
2
2
  /**
3
3
  * Inklapbare "Blokstijl"-sectie die voor ELK block beschikbaar is (gedeelde `_style`): verticale
4
4
  * ruimte, achtergrond, breedte, uitlijning en een anker-id. Wordt generiek aan de block-editors
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
- import { Field, Input } from "../ui/primitives";
4
- import { Select } from "../ui/select";
3
+ import { Field, Input } from "../ui/primitives.js";
4
+ import { Select } from "../ui/select.js";
5
5
  function StyleEditor({
6
6
  value = {},
7
7
  onChange
@@ -1,2 +1,2 @@
1
- import type { BlockEditorProps } from "./index";
1
+ import type { BlockEditorProps } from "./index.js";
2
2
  export declare function SymbolRefEditor({ block, siteId, onChange }: BlockEditorProps): import("react").JSX.Element;
@@ -1,11 +1,11 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useMemo, useState } from "react";
4
- import { Button, Field, Input } from "../ui/primitives";
5
- import { Select } from "../ui/select";
6
- import { useToast } from "../ui/toast";
7
- import { subscribeSymbols, saveSymbol } from "../lib/symbolsClient";
8
- import { NestedBlocksEditor } from "./index";
4
+ import { Button, Field, Input } from "../ui/primitives.js";
5
+ import { Select } from "../ui/select.js";
6
+ import { useToast } from "../ui/toast.js";
7
+ import { subscribeSymbols, saveSymbol } from "../lib/symbolsClient.js";
8
+ import { NestedBlocksEditor } from "./index.js";
9
9
  function SymbolRefEditor({ block, siteId, onChange }) {
10
10
  const { toast } = useToast();
11
11
  const [symbols, setSymbols] = useState([]);
@@ -1,5 +1,5 @@
1
1
  import { type ComponentType } from "react";
2
- import { GenericEditor } from "./GenericEditor";
2
+ import { GenericEditor } from "./GenericEditor.js";
3
3
  export interface BlockEditorProps {
4
4
  block: Record<string, unknown>;
5
5
  siteId: string;
@@ -2,16 +2,16 @@
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useState } from "react";
4
4
  import { collection, getDocs } from "firebase/firestore";
5
- import { getDb } from "../../firebase/client";
6
- import { Button, Field, Input, Textarea } from "../ui/primitives";
7
- import { Select } from "../ui/select";
8
- import { Switch } from "../ui/switch";
9
- import { ArrayEditor } from "../ui/ArrayEditor";
10
- import { RichTextEditor } from "../editors/RichTextEditor";
11
- import { MediaPickerButton } from "../media/MediaPicker";
12
- import { useRegistry } from "../../blocks/context";
13
- import { GenericEditor } from "./GenericEditor";
14
- import { SymbolRefEditor } from "./SymbolRefEditor";
5
+ import { getDb } from "../../firebase/client.js";
6
+ import { Button, Field, Input, Textarea } from "../ui/primitives.js";
7
+ import { Select } from "../ui/select.js";
8
+ import { Switch } from "../ui/switch.js";
9
+ import { ArrayEditor } from "../ui/ArrayEditor.js";
10
+ import { RichTextEditor } from "../editors/RichTextEditor.js";
11
+ import { MediaPickerButton } from "../media/MediaPicker.js";
12
+ import { useRegistry } from "../../blocks/context.js";
13
+ import { GenericEditor } from "./GenericEditor.js";
14
+ import { SymbolRefEditor } from "./SymbolRefEditor.js";
15
15
  const VARIANT_OPTIONS = [
16
16
  { value: "primary", label: "Primair" },
17
17
  { value: "secondary", label: "Secundair" },
@@ -3,13 +3,13 @@ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useState } from "react";
4
4
  import { collection, onSnapshot } from "firebase/firestore";
5
5
  import { getFunctions, httpsCallable } from "firebase/functions";
6
- import { getDb, getFirebaseApp } from "../../firebase/client";
7
- import { functionsRegion } from "../../firebase/region";
8
- import { CustomBlockSchema } from "../../schema/customBlocks";
9
- import { FieldsSchema } from "../../schema/fields";
10
- import { Button, Field, Input, Textarea } from "../ui/primitives";
11
- import { useToast } from "../ui/toast";
12
- import { useConfirm } from "../ui/ConfirmDialog";
6
+ import { getDb, getFirebaseApp } from "../../firebase/client.js";
7
+ import { functionsRegion } from "../../firebase/region.js";
8
+ import { CustomBlockSchema } from "../../schema/customBlocks.js";
9
+ import { FieldsSchema } from "../../schema/fields.js";
10
+ import { Button, Field, Input, Textarea } from "../ui/primitives.js";
11
+ import { useToast } from "../ui/toast.js";
12
+ import { useConfirm } from "../ui/ConfirmDialog.js";
13
13
  const EMPTY = {
14
14
  type: "custom-",
15
15
  label: "",
@@ -4,12 +4,11 @@ import { useEditor, EditorContent } from "@tiptap/react";
4
4
  import StarterKit from "@tiptap/starter-kit";
5
5
  import Link from "@tiptap/extension-link";
6
6
  import Image from "@tiptap/extension-image";
7
- import Underline from "@tiptap/extension-underline";
8
7
  import DOMPurify from "dompurify";
9
8
  import { useCallback, useEffect } from "react";
10
- import { cn } from "../ui/cn";
11
- import { MediaPickerButton } from "../media/MediaPicker";
12
- import { FontSize, FONT_SIZES } from "./fontSizeExtension";
9
+ import { cn } from "../ui/cn.js";
10
+ import { MediaPickerButton } from "../media/MediaPicker.js";
11
+ import { FontSize, FONT_SIZES } from "./fontSizeExtension.js";
13
12
  function RichTextEditor({
14
13
  value,
15
14
  onChange,
@@ -18,8 +17,10 @@ function RichTextEditor({
18
17
  const editor = useEditor({
19
18
  immediatelyRender: false,
20
19
  extensions: [
21
- StarterKit.configure({ heading: { levels: [2, 3, 4] } }),
22
- Underline,
20
+ // Tiptap 3 bundelt Link en Underline IN StarterKit. Los toevoegen geeft dubbele
21
+ // extensienamen (en stille gedragsfouten), dus: Underline komt uit StarterKit, en zijn Link
22
+ // zetten we uit omdat we die hieronder zelf configureren (geen open-on-click, eigen rel).
23
+ StarterKit.configure({ heading: { levels: [2, 3, 4] }, link: false }),
23
24
  FontSize,
24
25
  Link.configure({ openOnClick: false, autolink: true, HTMLAttributes: { rel: "noopener noreferrer nofollow" } }),
25
26
  Image.configure({ inline: false, allowBase64: false, HTMLAttributes: { loading: "lazy", decoding: "async" } })
@@ -34,7 +35,7 @@ function RichTextEditor({
34
35
  if (!editor || editor.isFocused) return;
35
36
  const current = editor.getHTML();
36
37
  const next = value || "<p></p>";
37
- if (current !== next) editor.commands.setContent(next, false);
38
+ if (current !== next) editor.commands.setContent(next, { emitUpdate: false });
38
39
  }, [value, editor]);
39
40
  const setLink = useCallback(() => {
40
41
  if (!editor) return;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/editors/RichTextEditor.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEditor, EditorContent } from \"@tiptap/react\";\nimport StarterKit from \"@tiptap/starter-kit\";\nimport Link from \"@tiptap/extension-link\";\nimport Image from \"@tiptap/extension-image\";\nimport Underline from \"@tiptap/extension-underline\";\nimport DOMPurify from \"dompurify\";\nimport { useCallback, useEffect, type ReactNode } from \"react\";\nimport { cn } from \"../ui/cn\";\nimport { MediaPickerButton } from \"../media/MediaPicker\";\nimport { FontSize, FONT_SIZES } from \"./fontSizeExtension\";\n\n/**\n * WYSIWYG-editor (TipTap) met opmaak én afbeeldingen. Output wordt client-side met DOMPurify\n * gesanitized voor preview/opslag; de server sanitized nogmaals bij publiceren (server/sanitize.ts)\n * — nooit op de client vertrouwen. Geef `siteId` mee om afbeeldingen te kunnen uploaden/invoegen.\n */\nexport function RichTextEditor({\n value,\n onChange,\n siteId,\n}: {\n value: string;\n onChange: (html: string) => void;\n siteId?: string;\n}) {\n\n const editor = useEditor({\n immediatelyRender: false,\n extensions: [\n StarterKit.configure({ heading: { levels: [2, 3, 4] } }),\n Underline,\n FontSize,\n Link.configure({ openOnClick: false, autolink: true, HTMLAttributes: { rel: \"noopener noreferrer nofollow\" } }),\n Image.configure({ inline: false, allowBase64: false, HTMLAttributes: { loading: \"lazy\", decoding: \"async\" } }),\n ],\n content: value || \"<p></p>\",\n onUpdate: ({ editor }) => onChange(DOMPurify.sanitize(editor.getHTML())),\n editorProps: {\n attributes: { class: \"min-h-[8rem] rounded-b-lg border border-t-0 border-slate-300 bg-white p-3 text-sm\" },\n },\n });\n\n // Externe waarde-wijzigingen (undo/redo in de VisualEditor, taal-wissel in het vertaalpaneel)\n // in de editor doorvoeren: TipTap leest `content` alleen bij init. Zonder deze sync blijft de\n // zichtbare HTML stale terwijl de opgeslagen waarde al veranderd is — data-integriteitsval.\n // Niet syncen terwijl de gebruiker zelf typt (editor.isFocused) — dan is de editor de bron.\n useEffect(() => {\n if (!editor || editor.isFocused) return;\n const current = editor.getHTML();\n const next = value || \"<p></p>\";\n if (current !== next) editor.commands.setContent(next, false); // false = geen onUpdate-echo\n }, [value, editor]);\n\n const setLink = useCallback(() => {\n if (!editor) return;\n const prev = editor.getAttributes(\"link\").href as string | undefined;\n const url = window.prompt(\"Link-URL (leeg = verwijderen)\", prev ?? \"https://\");\n if (url === null) return;\n if (url === \"\") editor.chain().focus().extendMarkRange(\"link\").unsetLink().run();\n else editor.chain().focus().extendMarkRange(\"link\").setLink({ href: url }).run();\n }, [editor]);\n\n const insertImageByUrl = useCallback(() => {\n if (!editor) return;\n const url = window.prompt(\"Afbeeldings-URL (https)\");\n if (!url) return;\n const alt = window.prompt(\"Beschrijving (alt-tekst, voor toegankelijkheid)\") ?? \"\";\n editor.chain().focus().setImage({ src: url, alt }).run();\n }, [editor]);\n\n const setFontSize = useCallback(\n (value: string) => {\n if (!editor) return;\n editor.chain().focus().setMark(\"textStyle\", { fontSize: value || null }).run();\n },\n [editor],\n );\n\n if (!editor) return null;\n\n const Btn = ({ on, active, label, title }: { on: () => void; active?: boolean; label: ReactNode; title?: string }) => (\n <button\n type=\"button\"\n title={title}\n onMouseDown={(e) => e.preventDefault()}\n onClick={on}\n className={cn(\n \"h-8 min-w-8 rounded px-2 text-sm hover:bg-slate-100\",\n active && \"bg-slate-200 font-semibold\",\n )}\n >\n {label}\n </button>\n );\n\n return (\n <div>\n <div className=\"flex flex-wrap gap-1 rounded-t-lg border border-slate-300 bg-slate-50 p-1\">\n <Btn on={() => editor.chain().focus().toggleBold().run()} active={editor.isActive(\"bold\")} label=\"B\" title=\"Vet\" />\n <Btn on={() => editor.chain().focus().toggleItalic().run()} active={editor.isActive(\"italic\")} label=\"I\" title=\"Cursief\" />\n <Btn on={() => editor.chain().focus().toggleUnderline().run()} active={editor.isActive(\"underline\")} label={<span style={{ textDecoration: \"underline\" }}>U</span>} title=\"Onderstrepen\" />\n <Btn on={() => editor.chain().focus().toggleStrike().run()} active={editor.isActive(\"strike\")} label={<span style={{ textDecoration: \"line-through\" }}>S</span>} title=\"Doorstrepen\" />\n <select\n className=\"h-8 rounded border border-slate-300 bg-white px-1 text-sm\"\n value={(editor.getAttributes(\"textStyle\").fontSize as string | undefined) ?? \"\"}\n onChange={(e) => setFontSize(e.target.value)}\n title=\"Lettergrootte\"\n aria-label=\"Lettergrootte\"\n >\n {FONT_SIZES.map((s) => (\n <option key={s.label} value={s.value}>\n {s.label}\n </option>\n ))}\n </select>\n <Btn on={() => editor.chain().focus().toggleHeading({ level: 2 }).run()} active={editor.isActive(\"heading\", { level: 2 })} label=\"H2\" title=\"Kop\" />\n <Btn on={() => editor.chain().focus().toggleHeading({ level: 3 }).run()} active={editor.isActive(\"heading\", { level: 3 })} label=\"H3\" title=\"Subkop\" />\n <Btn on={() => editor.chain().focus().toggleBulletList().run()} active={editor.isActive(\"bulletList\")} label=\"• Lijst\" title=\"Opsomming\" />\n <Btn on={() => editor.chain().focus().toggleOrderedList().run()} active={editor.isActive(\"orderedList\")} label=\"1. Lijst\" title=\"Genummerde lijst\" />\n <Btn on={() => editor.chain().focus().toggleBlockquote().run()} active={editor.isActive(\"blockquote\")} label=\"❝\" title=\"Citaat\" />\n <Btn on={setLink} active={editor.isActive(\"link\")} label=\"🔗\" title=\"Link\" />\n {siteId && (\n <MediaPickerButton\n siteId={siteId}\n label=\"🖼 Afbeelding\"\n variant=\"ghost\"\n onSelect={(m) => editor.chain().focus().setImage({ src: m.url, alt: m.alt ?? \"\" }).run()}\n />\n )}\n <Btn on={insertImageByUrl} label=\"🖼 URL\" title=\"Externe afbeelding via URL invoegen\" />\n </div>\n <EditorContent editor={editor} />\n </div>\n );\n}\n"],"mappings":"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","names":["editor","value"]}
1
+ {"version":3,"sources":["../../../src/admin/editors/RichTextEditor.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEditor, EditorContent } from \"@tiptap/react\";\nimport StarterKit from \"@tiptap/starter-kit\";\nimport Link from \"@tiptap/extension-link\";\nimport Image from \"@tiptap/extension-image\";\nimport DOMPurify from \"dompurify\";\nimport { useCallback, useEffect, type ReactNode } from \"react\";\nimport { cn } from \"../ui/cn\";\nimport { MediaPickerButton } from \"../media/MediaPicker\";\nimport { FontSize, FONT_SIZES } from \"./fontSizeExtension\";\n\n/**\n * WYSIWYG-editor (TipTap) met opmaak én afbeeldingen. Output wordt client-side met DOMPurify\n * gesanitized voor preview/opslag; de server sanitized nogmaals bij publiceren (server/sanitize.ts)\n * — nooit op de client vertrouwen. Geef `siteId` mee om afbeeldingen te kunnen uploaden/invoegen.\n */\nexport function RichTextEditor({\n value,\n onChange,\n siteId,\n}: {\n value: string;\n onChange: (html: string) => void;\n siteId?: string;\n}) {\n\n const editor = useEditor({\n immediatelyRender: false,\n extensions: [\n // Tiptap 3 bundelt Link en Underline IN StarterKit. Los toevoegen geeft dubbele\n // extensienamen (en stille gedragsfouten), dus: Underline komt uit StarterKit, en zijn Link\n // zetten we uit omdat we die hieronder zelf configureren (geen open-on-click, eigen rel).\n StarterKit.configure({ heading: { levels: [2, 3, 4] }, link: false }),\n FontSize,\n Link.configure({ openOnClick: false, autolink: true, HTMLAttributes: { rel: \"noopener noreferrer nofollow\" } }),\n Image.configure({ inline: false, allowBase64: false, HTMLAttributes: { loading: \"lazy\", decoding: \"async\" } }),\n ],\n content: value || \"<p></p>\",\n onUpdate: ({ editor }) => onChange(DOMPurify.sanitize(editor.getHTML())),\n editorProps: {\n attributes: { class: \"min-h-[8rem] rounded-b-lg border border-t-0 border-slate-300 bg-white p-3 text-sm\" },\n },\n });\n\n // Externe waarde-wijzigingen (undo/redo in de VisualEditor, taal-wissel in het vertaalpaneel)\n // in de editor doorvoeren: TipTap leest `content` alleen bij init. Zonder deze sync blijft de\n // zichtbare HTML stale terwijl de opgeslagen waarde al veranderd is — data-integriteitsval.\n // Niet syncen terwijl de gebruiker zelf typt (editor.isFocused) — dan is de editor de bron.\n useEffect(() => {\n if (!editor || editor.isFocused) return;\n const current = editor.getHTML();\n const next = value || \"<p></p>\";\n // Tiptap 3: de tweede parameter is een options-object i.p.v. een boolean.\n if (current !== next) editor.commands.setContent(next, { emitUpdate: false });\n }, [value, editor]);\n\n const setLink = useCallback(() => {\n if (!editor) return;\n const prev = editor.getAttributes(\"link\").href as string | undefined;\n const url = window.prompt(\"Link-URL (leeg = verwijderen)\", prev ?? \"https://\");\n if (url === null) return;\n if (url === \"\") editor.chain().focus().extendMarkRange(\"link\").unsetLink().run();\n else editor.chain().focus().extendMarkRange(\"link\").setLink({ href: url }).run();\n }, [editor]);\n\n const insertImageByUrl = useCallback(() => {\n if (!editor) return;\n const url = window.prompt(\"Afbeeldings-URL (https)\");\n if (!url) return;\n const alt = window.prompt(\"Beschrijving (alt-tekst, voor toegankelijkheid)\") ?? \"\";\n editor.chain().focus().setImage({ src: url, alt }).run();\n }, [editor]);\n\n const setFontSize = useCallback(\n (value: string) => {\n if (!editor) return;\n editor.chain().focus().setMark(\"textStyle\", { fontSize: value || null }).run();\n },\n [editor],\n );\n\n if (!editor) return null;\n\n const Btn = ({ on, active, label, title }: { on: () => void; active?: boolean; label: ReactNode; title?: string }) => (\n <button\n type=\"button\"\n title={title}\n onMouseDown={(e) => e.preventDefault()}\n onClick={on}\n className={cn(\n \"h-8 min-w-8 rounded px-2 text-sm hover:bg-slate-100\",\n active && \"bg-slate-200 font-semibold\",\n )}\n >\n {label}\n </button>\n );\n\n return (\n <div>\n <div className=\"flex flex-wrap gap-1 rounded-t-lg border border-slate-300 bg-slate-50 p-1\">\n <Btn on={() => editor.chain().focus().toggleBold().run()} active={editor.isActive(\"bold\")} label=\"B\" title=\"Vet\" />\n <Btn on={() => editor.chain().focus().toggleItalic().run()} active={editor.isActive(\"italic\")} label=\"I\" title=\"Cursief\" />\n <Btn on={() => editor.chain().focus().toggleUnderline().run()} active={editor.isActive(\"underline\")} label={<span style={{ textDecoration: \"underline\" }}>U</span>} title=\"Onderstrepen\" />\n <Btn on={() => editor.chain().focus().toggleStrike().run()} active={editor.isActive(\"strike\")} label={<span style={{ textDecoration: \"line-through\" }}>S</span>} title=\"Doorstrepen\" />\n <select\n className=\"h-8 rounded border border-slate-300 bg-white px-1 text-sm\"\n value={(editor.getAttributes(\"textStyle\").fontSize as string | undefined) ?? \"\"}\n onChange={(e) => setFontSize(e.target.value)}\n title=\"Lettergrootte\"\n aria-label=\"Lettergrootte\"\n >\n {FONT_SIZES.map((s) => (\n <option key={s.label} value={s.value}>\n {s.label}\n </option>\n ))}\n </select>\n <Btn on={() => editor.chain().focus().toggleHeading({ level: 2 }).run()} active={editor.isActive(\"heading\", { level: 2 })} label=\"H2\" title=\"Kop\" />\n <Btn on={() => editor.chain().focus().toggleHeading({ level: 3 }).run()} active={editor.isActive(\"heading\", { level: 3 })} label=\"H3\" title=\"Subkop\" />\n <Btn on={() => editor.chain().focus().toggleBulletList().run()} active={editor.isActive(\"bulletList\")} label=\"• Lijst\" title=\"Opsomming\" />\n <Btn on={() => editor.chain().focus().toggleOrderedList().run()} active={editor.isActive(\"orderedList\")} label=\"1. Lijst\" title=\"Genummerde lijst\" />\n <Btn on={() => editor.chain().focus().toggleBlockquote().run()} active={editor.isActive(\"blockquote\")} label=\"❝\" title=\"Citaat\" />\n <Btn on={setLink} active={editor.isActive(\"link\")} label=\"🔗\" title=\"Link\" />\n {siteId && (\n <MediaPickerButton\n siteId={siteId}\n label=\"🖼 Afbeelding\"\n variant=\"ghost\"\n onSelect={(m) => editor.chain().focus().setImage({ src: m.url, alt: m.alt ?? \"\" }).run()}\n />\n )}\n <Btn on={insertImageByUrl} label=\"🖼 URL\" title=\"Externe afbeelding via URL invoegen\" />\n </div>\n <EditorContent editor={editor} />\n </div>\n );\n}\n"],"mappings":"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","names":["editor","value"]}
@@ -1,4 +1,4 @@
1
- import TextStyle from "@tiptap/extension-text-style";
1
+ import { TextStyle } from "@tiptap/extension-text-style";
2
2
  const FONT_SIZES = [
3
3
  { label: "Klein", value: "0.85rem" },
4
4
  { label: "Normaal", value: "" },
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/editors/fontSizeExtension.ts"],"sourcesContent":["import TextStyle from \"@tiptap/extension-text-style\";\n\n/**\n * Vaste, veilige preset-set — geen vrij invoerveld. `sanitizeRichText` (server/sanitize.ts en de\n * spiegel-kopie in functions/src/lib/sanitize.ts) staat via `allowedStyles` UITSLUITEND exact deze\n * `font-size`-waarden toe; een los tekstveld voor lettergrootte zou een CSS-allowlist met een brede\n * regex vergen (groter injectie-oppervlak) voor weinig extra waarde t.o.v. een paar consistente maten.\n */\nexport const FONT_SIZES = [\n { label: \"Klein\", value: \"0.85rem\" },\n { label: \"Normaal\", value: \"\" },\n { label: \"Groot\", value: \"1.25rem\" },\n { label: \"Extra groot\", value: \"1.5rem\" },\n] as const;\n\n/** `textStyle`-mark (Tiptap) uitgebreid met een `fontSize`-attribuut → `<span style=\"font-size:...\">`. */\nexport const FontSize = TextStyle.extend({\n addAttributes() {\n return {\n ...this.parent?.(),\n fontSize: {\n default: null,\n parseHTML: (element) => element.style.fontSize || null,\n renderHTML: (attributes) => {\n if (!attributes.fontSize) return {};\n return { style: `font-size: ${attributes.fontSize}` };\n },\n },\n };\n },\n});\n"],"mappings":"AAAA,OAAO,eAAe;AAQf,MAAM,aAAa;AAAA,EACxB,EAAE,OAAO,SAAS,OAAO,UAAU;AAAA,EACnC,EAAE,OAAO,WAAW,OAAO,GAAG;AAAA,EAC9B,EAAE,OAAO,SAAS,OAAO,UAAU;AAAA,EACnC,EAAE,OAAO,eAAe,OAAO,SAAS;AAC1C;AAGO,MAAM,WAAW,UAAU,OAAO;AAAA,EACvC,gBAAgB;AACd,WAAO;AAAA,MACL,GAAG,KAAK,SAAS;AAAA,MACjB,UAAU;AAAA,QACR,SAAS;AAAA,QACT,WAAW,CAAC,YAAY,QAAQ,MAAM,YAAY;AAAA,QAClD,YAAY,CAAC,eAAe;AAC1B,cAAI,CAAC,WAAW,SAAU,QAAO,CAAC;AAClC,iBAAO,EAAE,OAAO,cAAc,WAAW,QAAQ,GAAG;AAAA,QACtD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF,CAAC;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/editors/fontSizeExtension.ts"],"sourcesContent":["// Tiptap 3: extension-text-style exporteert geen default meer.\nimport { TextStyle } from \"@tiptap/extension-text-style\";\n\n/**\n * Vaste, veilige preset-set — geen vrij invoerveld. `sanitizeRichText` (server/sanitize.ts en de\n * spiegel-kopie in functions/src/lib/sanitize.ts) staat via `allowedStyles` UITSLUITEND exact deze\n * `font-size`-waarden toe; een los tekstveld voor lettergrootte zou een CSS-allowlist met een brede\n * regex vergen (groter injectie-oppervlak) voor weinig extra waarde t.o.v. een paar consistente maten.\n */\nexport const FONT_SIZES = [\n { label: \"Klein\", value: \"0.85rem\" },\n { label: \"Normaal\", value: \"\" },\n { label: \"Groot\", value: \"1.25rem\" },\n { label: \"Extra groot\", value: \"1.5rem\" },\n] as const;\n\n/** `textStyle`-mark (Tiptap) uitgebreid met een `fontSize`-attribuut → `<span style=\"font-size:...\">`. */\nexport const FontSize = TextStyle.extend({\n addAttributes() {\n return {\n ...this.parent?.(),\n fontSize: {\n default: null,\n parseHTML: (element: HTMLElement) => element.style.fontSize || null,\n renderHTML: (attributes: Record<string, unknown>) => {\n if (!attributes.fontSize) return {};\n return { style: `font-size: ${attributes.fontSize}` };\n },\n },\n };\n },\n});\n"],"mappings":"AACA,SAAS,iBAAiB;AAQnB,MAAM,aAAa;AAAA,EACxB,EAAE,OAAO,SAAS,OAAO,UAAU;AAAA,EACnC,EAAE,OAAO,WAAW,OAAO,GAAG;AAAA,EAC9B,EAAE,OAAO,SAAS,OAAO,UAAU;AAAA,EACnC,EAAE,OAAO,eAAe,OAAO,SAAS;AAC1C;AAGO,MAAM,WAAW,UAAU,OAAO;AAAA,EACvC,gBAAgB;AACd,WAAO;AAAA,MACL,GAAG,KAAK,SAAS;AAAA,MACjB,UAAU;AAAA,QACR,SAAS;AAAA,QACT,WAAW,CAAC,YAAyB,QAAQ,MAAM,YAAY;AAAA,QAC/D,YAAY,CAAC,eAAwC;AACnD,cAAI,CAAC,WAAW,SAAU,QAAO,CAAC;AAClC,iBAAO,EAAE,OAAO,cAAc,WAAW,QAAQ,GAAG;AAAA,QACtD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF,CAAC;","names":[]}
@@ -11,15 +11,15 @@ import {
11
11
  query,
12
12
  setDoc
13
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
- import { MailTemplates } from "../settings/MailTemplates";
22
- import { FORM_TEMPLATE_KEYS } from "../../schema/mail";
14
+ import { getDb } from "../../firebase/client.js";
15
+ import { FormSchema } from "../../schema/forms.js";
16
+ import { Button, Card, Field, Input, Badge } from "../ui/primitives.js";
17
+ import { Select } from "../ui/select.js";
18
+ import { Switch } from "../ui/switch.js";
19
+ import { useToast } from "../ui/toast.js";
20
+ import { useConfirm } from "../ui/ConfirmDialog.js";
21
+ import { MailTemplates } from "../settings/MailTemplates.js";
22
+ import { FORM_TEMPLATE_KEYS } from "../../schema/mail.js";
23
23
  const FIELD_TYPES = [
24
24
  { value: "text", label: "Tekst" },
25
25
  { value: "email", label: "E-mail" },