@xiwebdesign/xicms-core 7.31.0 → 8.16.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 (482) hide show
  1. package/dist/XiCMSProvider.js +23 -7
  2. package/dist/XiCMSProvider.js.map +1 -1
  3. package/dist/admin/AdminApp.js +175 -93
  4. package/dist/admin/AdminApp.js.map +1 -1
  5. package/dist/admin/AdminHelp.d.ts +1 -1
  6. package/dist/admin/AdminHelp.js +44 -36
  7. package/dist/admin/AdminHelp.js.map +1 -1
  8. package/dist/admin/CoreVersion.d.ts +6 -3
  9. package/dist/admin/CoreVersion.js +60 -24
  10. package/dist/admin/CoreVersion.js.map +1 -1
  11. package/dist/admin/NewsEditor.d.ts +7 -0
  12. package/dist/admin/NewsEditor.js +117 -75
  13. package/dist/admin/NewsEditor.js.map +1 -1
  14. package/dist/admin/SuperadminPanel.js +1 -1
  15. package/dist/admin/SuperadminPanel.js.map +1 -1
  16. package/dist/admin/ZoekPalet.d.ts +15 -0
  17. package/dist/admin/ZoekPalet.js +123 -0
  18. package/dist/admin/ZoekPalet.js.map +1 -0
  19. package/dist/admin/blockEditors/GenericEditor.js +36 -18
  20. package/dist/admin/blockEditors/GenericEditor.js.map +1 -1
  21. package/dist/admin/blockEditors/StyleEditor.js +37 -3
  22. package/dist/admin/blockEditors/StyleEditor.js.map +1 -1
  23. package/dist/admin/blockEditors/SymbolRefEditor.js +44 -2
  24. package/dist/admin/blockEditors/SymbolRefEditor.js.map +1 -1
  25. package/dist/admin/blockEditors/index.js +154 -73
  26. package/dist/admin/blockEditors/index.js.map +1 -1
  27. package/dist/admin/customBlocks/CustomBlocksPanel.js +18 -7
  28. package/dist/admin/customBlocks/CustomBlocksPanel.js.map +1 -1
  29. package/dist/admin/dashboard/DashboardPanel.js +26 -16
  30. package/dist/admin/dashboard/DashboardPanel.js.map +1 -1
  31. package/dist/admin/editors/RichTextEditor.d.ts +4 -3
  32. package/dist/admin/editors/RichTextEditor.js +19 -60
  33. package/dist/admin/editors/RichTextEditor.js.map +1 -1
  34. package/dist/admin/editors/fontSizeExtension.js +7 -1
  35. package/dist/admin/editors/fontSizeExtension.js.map +1 -1
  36. package/dist/admin/editors/slashMenu.d.ts +18 -0
  37. package/dist/admin/editors/slashMenu.js +34 -0
  38. package/dist/admin/editors/slashMenu.js.map +1 -0
  39. package/dist/admin/editors/useRijkeTekst.d.ts +48 -0
  40. package/dist/admin/editors/useRijkeTekst.js +310 -0
  41. package/dist/admin/editors/useRijkeTekst.js.map +1 -0
  42. package/dist/admin/forms/FormsPanel.d.ts +11 -2
  43. package/dist/admin/forms/FormsPanel.js +218 -65
  44. package/dist/admin/forms/FormsPanel.js.map +1 -1
  45. package/dist/admin/lib/blokInfo.d.ts +17 -0
  46. package/dist/admin/lib/blokInfo.js +80 -0
  47. package/dist/admin/lib/blokInfo.js.map +1 -0
  48. package/dist/admin/lib/foutNaarMens.d.ts +1 -0
  49. package/dist/admin/lib/foutNaarMens.js +22 -0
  50. package/dist/admin/lib/foutNaarMens.js.map +1 -0
  51. package/dist/admin/lib/gewisteVelden.d.ts +13 -0
  52. package/dist/admin/lib/gewisteVelden.js +31 -0
  53. package/dist/admin/lib/gewisteVelden.js.map +1 -0
  54. package/dist/admin/lib/heroHulp.d.ts +11 -0
  55. package/dist/admin/lib/heroHulp.js +18 -0
  56. package/dist/admin/lib/heroHulp.js.map +1 -0
  57. package/dist/admin/lib/kleur.js +2 -3
  58. package/dist/admin/lib/kleur.js.map +1 -1
  59. package/dist/admin/lib/leesbaarheid.d.ts +34 -0
  60. package/dist/admin/lib/leesbaarheid.js +82 -0
  61. package/dist/admin/lib/leesbaarheid.js.map +1 -0
  62. package/dist/admin/lib/lengteAdvies.d.ts +1 -0
  63. package/dist/admin/lib/lengteAdvies.js +16 -0
  64. package/dist/admin/lib/lengteAdvies.js.map +1 -0
  65. package/dist/admin/lib/mediaLijst.d.ts +26 -0
  66. package/dist/admin/lib/mediaLijst.js +42 -0
  67. package/dist/admin/lib/mediaLijst.js.map +1 -0
  68. package/dist/admin/lib/mediaUsage.d.ts +12 -1
  69. package/dist/admin/lib/mediaUsage.js +43 -33
  70. package/dist/admin/lib/mediaUsage.js.map +1 -1
  71. package/dist/admin/lib/menuHulp.d.ts +16 -0
  72. package/dist/admin/lib/menuHulp.js +41 -0
  73. package/dist/admin/lib/menuHulp.js.map +1 -0
  74. package/dist/admin/lib/onopgeslagen.d.ts +10 -0
  75. package/dist/admin/lib/onopgeslagen.js +35 -0
  76. package/dist/admin/lib/onopgeslagen.js.map +1 -0
  77. package/dist/admin/lib/pagesClient.d.ts +55 -2
  78. package/dist/admin/lib/pagesClient.js +126 -28
  79. package/dist/admin/lib/pagesClient.js.map +1 -1
  80. package/dist/admin/lib/paginaGebruik.d.ts +56 -0
  81. package/dist/admin/lib/paginaGebruik.js +109 -0
  82. package/dist/admin/lib/paginaGebruik.js.map +1 -0
  83. package/dist/admin/lib/publicatieCheck.d.ts +25 -0
  84. package/dist/admin/lib/publicatieCheck.js +114 -0
  85. package/dist/admin/lib/publicatieCheck.js.map +1 -0
  86. package/dist/admin/lib/scrollNaarDetail.d.ts +6 -0
  87. package/dist/admin/lib/scrollNaarDetail.js +20 -0
  88. package/dist/admin/lib/scrollNaarDetail.js.map +1 -0
  89. package/dist/admin/lib/symbolsClient.d.ts +10 -0
  90. package/dist/admin/lib/symbolsClient.js +14 -1
  91. package/dist/admin/lib/symbolsClient.js.map +1 -1
  92. package/dist/admin/lib/tekstVeld.d.ts +8 -0
  93. package/dist/admin/lib/tekstVeld.js +10 -0
  94. package/dist/admin/lib/tekstVeld.js.map +1 -0
  95. package/dist/admin/lib/useIsSuperadmin.d.ts +2 -0
  96. package/dist/admin/lib/useIsSuperadmin.js +13 -0
  97. package/dist/admin/lib/useIsSuperadmin.js.map +1 -0
  98. package/dist/admin/lib/veldTaal.d.ts +14 -0
  99. package/dist/admin/lib/veldTaal.js +248 -0
  100. package/dist/admin/lib/veldTaal.js.map +1 -0
  101. package/dist/admin/lib/versieVergelijk.d.ts +17 -0
  102. package/dist/admin/lib/versieVergelijk.js +24 -0
  103. package/dist/admin/lib/versieVergelijk.js.map +1 -0
  104. package/dist/admin/lib/zodFields.js +22 -4
  105. package/dist/admin/lib/zodFields.js.map +1 -1
  106. package/dist/admin/lib/zodFout.d.ts +9 -0
  107. package/dist/admin/lib/zodFout.js +63 -0
  108. package/dist/admin/lib/zodFout.js.map +1 -0
  109. package/dist/admin/media/MediaManager.js +162 -32
  110. package/dist/admin/media/MediaManager.js.map +1 -1
  111. package/dist/admin/media/MediaPicker.d.ts +3 -0
  112. package/dist/admin/media/MediaPicker.js +84 -31
  113. package/dist/admin/media/MediaPicker.js.map +1 -1
  114. package/dist/admin/menu/MenuPanel.js +144 -83
  115. package/dist/admin/menu/MenuPanel.js.map +1 -1
  116. package/dist/admin/navContext.d.ts +3 -0
  117. package/dist/admin/navContext.js +13 -1
  118. package/dist/admin/navContext.js.map +1 -1
  119. package/dist/admin/pages/PageEditor.js +146 -30
  120. package/dist/admin/pages/PageEditor.js.map +1 -1
  121. package/dist/admin/pages/PagesPanel.js +215 -61
  122. package/dist/admin/pages/PagesPanel.js.map +1 -1
  123. package/dist/admin/pages/StarterKiezer.d.ts +12 -0
  124. package/dist/admin/pages/StarterKiezer.js +113 -0
  125. package/dist/admin/pages/StarterKiezer.js.map +1 -0
  126. package/dist/admin/panelRegistry.d.ts +1 -1
  127. package/dist/admin/panelRegistry.js +2 -1
  128. package/dist/admin/panelRegistry.js.map +1 -1
  129. package/dist/admin/patterns/PatternInserter.js +1 -1
  130. package/dist/admin/patterns/PatternInserter.js.map +1 -1
  131. package/dist/admin/settings/BedrijfInstellingen.d.ts +20 -0
  132. package/dist/admin/settings/BedrijfInstellingen.js +159 -0
  133. package/dist/admin/settings/BedrijfInstellingen.js.map +1 -0
  134. package/dist/admin/settings/MailTemplates.js +5 -4
  135. package/dist/admin/settings/MailTemplates.js.map +1 -1
  136. package/dist/admin/settings/SettingsPanel.js +120 -78
  137. package/dist/admin/settings/SettingsPanel.js.map +1 -1
  138. package/dist/admin/settings/SnapshotsCard.js +4 -4
  139. package/dist/admin/settings/SnapshotsCard.js.map +1 -1
  140. package/dist/admin/settings/VorigeTerugzetten.d.ts +15 -0
  141. package/dist/admin/settings/VorigeTerugzetten.js +89 -0
  142. package/dist/admin/settings/VorigeTerugzetten.js.map +1 -0
  143. package/dist/admin/shop/CategoriesTab.js +6 -4
  144. package/dist/admin/shop/CategoriesTab.js.map +1 -1
  145. package/dist/admin/shop/DiscountsTab.js +4 -2
  146. package/dist/admin/shop/DiscountsTab.js.map +1 -1
  147. package/dist/admin/shop/OrdersTab.js +57 -19
  148. package/dist/admin/shop/OrdersTab.js.map +1 -1
  149. package/dist/admin/shop/ProductPaneel.js +2 -1
  150. package/dist/admin/shop/ProductPaneel.js.map +1 -1
  151. package/dist/admin/shop/ProductsTab.js +40 -19
  152. package/dist/admin/shop/ProductsTab.js.map +1 -1
  153. package/dist/admin/shop/ShopPanel.js +13 -1
  154. package/dist/admin/shop/ShopPanel.js.map +1 -1
  155. package/dist/admin/shop/ShopSettingsTab.js +56 -17
  156. package/dist/admin/shop/ShopSettingsTab.js.map +1 -1
  157. package/dist/admin/shop/productClient.js +7 -3
  158. package/dist/admin/shop/productClient.js.map +1 -1
  159. package/dist/admin/stats/NietGevonden.d.ts +10 -0
  160. package/dist/admin/stats/NietGevonden.js +78 -0
  161. package/dist/admin/stats/NietGevonden.js.map +1 -0
  162. package/dist/admin/stats/StatsPanel.js +6 -1
  163. package/dist/admin/stats/StatsPanel.js.map +1 -1
  164. package/dist/admin/stats/lib.d.ts +2 -0
  165. package/dist/admin/stats/lib.js.map +1 -1
  166. package/dist/admin/ui/ConfirmDialog.js +10 -1
  167. package/dist/admin/ui/ConfirmDialog.js.map +1 -1
  168. package/dist/admin/ui/Icon.d.ts +34 -0
  169. package/dist/admin/ui/Icon.js +44 -0
  170. package/dist/admin/ui/Icon.js.map +1 -0
  171. package/dist/admin/ui/ImageField.d.ts +15 -0
  172. package/dist/admin/ui/ImageField.js +67 -0
  173. package/dist/admin/ui/ImageField.js.map +1 -0
  174. package/dist/admin/ui/KleurVeld.d.ts +25 -0
  175. package/dist/admin/ui/KleurVeld.js +76 -0
  176. package/dist/admin/ui/KleurVeld.js.map +1 -0
  177. package/dist/admin/ui/LaadStaat.d.ts +21 -0
  178. package/dist/admin/ui/LaadStaat.js +43 -0
  179. package/dist/admin/ui/LaadStaat.js.map +1 -0
  180. package/dist/admin/ui/LeesbaarheidPaneel.d.ts +10 -0
  181. package/dist/admin/ui/LeesbaarheidPaneel.js +68 -0
  182. package/dist/admin/ui/LeesbaarheidPaneel.js.map +1 -0
  183. package/dist/admin/ui/LinkDialog.d.ts +22 -0
  184. package/dist/admin/ui/LinkDialog.js +131 -0
  185. package/dist/admin/ui/LinkDialog.js.map +1 -0
  186. package/dist/admin/ui/LinkVeld.d.ts +14 -0
  187. package/dist/admin/ui/LinkVeld.js +49 -0
  188. package/dist/admin/ui/LinkVeld.js.map +1 -0
  189. package/dist/admin/ui/PreviewLinkResultaat.d.ts +14 -0
  190. package/dist/admin/ui/PreviewLinkResultaat.js +52 -0
  191. package/dist/admin/ui/PreviewLinkResultaat.js.map +1 -0
  192. package/dist/admin/ui/charts.js +5 -5
  193. package/dist/admin/ui/charts.js.map +1 -1
  194. package/dist/admin/ui/primitives.d.ts +16 -6
  195. package/dist/admin/ui/primitives.js +98 -26
  196. package/dist/admin/ui/primitives.js.map +1 -1
  197. package/dist/admin/ui/select.js +5 -2
  198. package/dist/admin/ui/select.js.map +1 -1
  199. package/dist/admin/ui/switch.d.ts +3 -1
  200. package/dist/admin/ui/switch.js +6 -3
  201. package/dist/admin/ui/switch.js.map +1 -1
  202. package/dist/admin/ui/toast.d.ts +2 -1
  203. package/dist/admin/ui/toast.js +17 -11
  204. package/dist/admin/ui/toast.js.map +1 -1
  205. package/dist/admin/visual/BlokKiezer.d.ts +34 -0
  206. package/dist/admin/visual/BlokKiezer.js +225 -0
  207. package/dist/admin/visual/BlokKiezer.js.map +1 -0
  208. package/dist/admin/visual/InlineGereedschap.d.ts +52 -0
  209. package/dist/admin/visual/InlineGereedschap.js +356 -0
  210. package/dist/admin/visual/InlineGereedschap.js.map +1 -0
  211. package/dist/admin/visual/InlineRijkeTekst.d.ts +23 -0
  212. package/dist/admin/visual/InlineRijkeTekst.js +152 -0
  213. package/dist/admin/visual/InlineRijkeTekst.js.map +1 -0
  214. package/dist/admin/visual/VisualEditor.js +273 -125
  215. package/dist/admin/visual/VisualEditor.js.map +1 -1
  216. package/dist/admin.css +1 -1
  217. package/dist/auth/AuthProvider.js +1 -0
  218. package/dist/auth/AuthProvider.js.map +1 -1
  219. package/dist/bedrijf/bedrijf.d.ts +44 -0
  220. package/dist/bedrijf/bedrijf.js +125 -0
  221. package/dist/bedrijf/bedrijf.js.map +1 -0
  222. package/dist/blocks/BlockRenderer.js +11 -1
  223. package/dist/blocks/BlockRenderer.js.map +1 -1
  224. package/dist/blocks/blockStyle.d.ts +7 -0
  225. package/dist/blocks/blockStyle.js +16 -3
  226. package/dist/blocks/blockStyle.js.map +1 -1
  227. package/dist/blocks/builtinSchemas.js +6 -0
  228. package/dist/blocks/builtinSchemas.js.map +1 -1
  229. package/dist/blocks/builtins/AgendaRender.d.ts +22 -0
  230. package/dist/blocks/builtins/AgendaRender.js +69 -9
  231. package/dist/blocks/builtins/AgendaRender.js.map +1 -1
  232. package/dist/blocks/builtins/Availability.d.ts +1 -0
  233. package/dist/blocks/builtins/Availability.js +23 -0
  234. package/dist/blocks/builtins/Availability.js.map +1 -0
  235. package/dist/blocks/builtins/AvailabilityRender.d.ts +12 -0
  236. package/dist/blocks/builtins/AvailabilityRender.js +105 -0
  237. package/dist/blocks/builtins/AvailabilityRender.js.map +1 -0
  238. package/dist/blocks/builtins/BeforeAfterRender.js +4 -3
  239. package/dist/blocks/builtins/BeforeAfterRender.js.map +1 -1
  240. package/dist/blocks/builtins/Booking.js +1 -1
  241. package/dist/blocks/builtins/Booking.js.map +1 -1
  242. package/dist/blocks/builtins/BreadcrumbsRender.d.ts +4 -3
  243. package/dist/blocks/builtins/BreadcrumbsRender.js +14 -8
  244. package/dist/blocks/builtins/BreadcrumbsRender.js.map +1 -1
  245. package/dist/blocks/builtins/ColumnsRender.d.ts +1 -1
  246. package/dist/blocks/builtins/ColumnsRender.js +3 -1
  247. package/dist/blocks/builtins/ColumnsRender.js.map +1 -1
  248. package/dist/blocks/builtins/Contact.js +1 -1
  249. package/dist/blocks/builtins/Contact.js.map +1 -1
  250. package/dist/blocks/builtins/ContactRender.d.ts +1 -1
  251. package/dist/blocks/builtins/ContactRender.js +37 -8
  252. package/dist/blocks/builtins/ContactRender.js.map +1 -1
  253. package/dist/blocks/builtins/Cta.js +1 -1
  254. package/dist/blocks/builtins/Cta.js.map +1 -1
  255. package/dist/blocks/builtins/CtaRender.d.ts +1 -1
  256. package/dist/blocks/builtins/CtaRender.js +5 -4
  257. package/dist/blocks/builtins/CtaRender.js.map +1 -1
  258. package/dist/blocks/builtins/Embed.js +1 -1
  259. package/dist/blocks/builtins/Embed.js.map +1 -1
  260. package/dist/blocks/builtins/FormBlock.js +1 -0
  261. package/dist/blocks/builtins/FormBlock.js.map +1 -1
  262. package/dist/blocks/builtins/FormBlockRender.d.ts +6 -0
  263. package/dist/blocks/builtins/FormBlockRender.js +88 -16
  264. package/dist/blocks/builtins/FormBlockRender.js.map +1 -1
  265. package/dist/blocks/builtins/GalleryRender.js +3 -2
  266. package/dist/blocks/builtins/GalleryRender.js.map +1 -1
  267. package/dist/blocks/builtins/Hero.js +1 -1
  268. package/dist/blocks/builtins/Hero.js.map +1 -1
  269. package/dist/blocks/builtins/HeroRender.js +7 -3
  270. package/dist/blocks/builtins/HeroRender.js.map +1 -1
  271. package/dist/blocks/builtins/OpenNowBadge.d.ts +4 -2
  272. package/dist/blocks/builtins/OpenNowBadge.js +6 -6
  273. package/dist/blocks/builtins/OpenNowBadge.js.map +1 -1
  274. package/dist/blocks/builtins/OpeningHoursRender.d.ts +8 -2
  275. package/dist/blocks/builtins/OpeningHoursRender.js +27 -6
  276. package/dist/blocks/builtins/OpeningHoursRender.js.map +1 -1
  277. package/dist/blocks/builtins/PriceList.d.ts +1 -0
  278. package/dist/blocks/builtins/PriceList.js +25 -0
  279. package/dist/blocks/builtins/PriceList.js.map +1 -0
  280. package/dist/blocks/builtins/PriceListRender.d.ts +17 -0
  281. package/dist/blocks/builtins/PriceListRender.js +87 -0
  282. package/dist/blocks/builtins/PriceListRender.js.map +1 -0
  283. package/dist/blocks/builtins/RichTextBlockRender.js +6 -1
  284. package/dist/blocks/builtins/RichTextBlockRender.js.map +1 -1
  285. package/dist/blocks/builtins/SectionHeading.d.ts +1 -0
  286. package/dist/blocks/builtins/SectionHeading.js +14 -0
  287. package/dist/blocks/builtins/SectionHeading.js.map +1 -0
  288. package/dist/blocks/builtins/SectionHeadingRender.d.ts +7 -0
  289. package/dist/blocks/builtins/SectionHeadingRender.js +14 -0
  290. package/dist/blocks/builtins/SectionHeadingRender.js.map +1 -0
  291. package/dist/blocks/builtins/SliderRender.js +3 -2
  292. package/dist/blocks/builtins/SliderRender.js.map +1 -1
  293. package/dist/blocks/builtins/SymbolRef.js +1 -1
  294. package/dist/blocks/builtins/SymbolRef.js.map +1 -1
  295. package/dist/blocks/builtins/TeamRender.js +6 -1
  296. package/dist/blocks/builtins/TeamRender.js.map +1 -1
  297. package/dist/blocks/builtins/Testimonials.js +3 -1
  298. package/dist/blocks/builtins/Testimonials.js.map +1 -1
  299. package/dist/blocks/builtins/TestimonialsRender.js +4 -0
  300. package/dist/blocks/builtins/TestimonialsRender.js.map +1 -1
  301. package/dist/blocks/builtins/WhatsappChatRender.d.ts +1 -1
  302. package/dist/blocks/builtins/WhatsappChatRender.js +3 -1
  303. package/dist/blocks/builtins/WhatsappChatRender.js.map +1 -1
  304. package/dist/blocks/builtins/Widgets.js +2 -2
  305. package/dist/blocks/builtins/Widgets.js.map +1 -1
  306. package/dist/blocks/builtins/WidgetsRender.js +3 -1
  307. package/dist/blocks/builtins/WidgetsRender.js.map +1 -1
  308. package/dist/blocks/builtins/consentEmbed.js +26 -9
  309. package/dist/blocks/builtins/consentEmbed.js.map +1 -1
  310. package/dist/blocks/builtins/index.d.ts +3 -0
  311. package/dist/blocks/builtins/index.js +6 -0
  312. package/dist/blocks/builtins/index.js.map +1 -1
  313. package/dist/blocks/builtins/lib/autoComplete.d.ts +7 -0
  314. package/dist/blocks/builtins/lib/autoComplete.js +20 -0
  315. package/dist/blocks/builtins/lib/autoComplete.js.map +1 -0
  316. package/dist/blocks/builtins/lib/bookingUrl.d.ts +2 -2
  317. package/dist/blocks/builtins/lib/bookingUrl.js +1 -1
  318. package/dist/blocks/builtins/lib/bookingUrl.js.map +1 -1
  319. package/dist/blocks/builtins/lib/ical.d.ts +24 -0
  320. package/dist/blocks/builtins/lib/ical.js +82 -0
  321. package/dist/blocks/builtins/lib/ical.js.map +1 -0
  322. package/dist/blocks/builtins/lib/vandaag.d.ts +8 -0
  323. package/dist/blocks/builtins/lib/vandaag.js +22 -0
  324. package/dist/blocks/builtins/lib/vandaag.js.map +1 -0
  325. package/dist/blocks/registry.d.ts +6 -0
  326. package/dist/blocks/registry.js.map +1 -1
  327. package/dist/blocks/ui/index.js +1 -0
  328. package/dist/blocks/ui/index.js.map +1 -1
  329. package/dist/components/EditBar.js +2 -4
  330. package/dist/components/EditBar.js.map +1 -1
  331. package/dist/components/News.js +26 -18
  332. package/dist/components/News.js.map +1 -1
  333. package/dist/components/PageView.d.ts +7 -1
  334. package/dist/components/PageView.js +17 -0
  335. package/dist/components/PageView.js.map +1 -1
  336. package/dist/components/ResponsiveImage.js +18 -20
  337. package/dist/components/ResponsiveImage.js.map +1 -1
  338. package/dist/components/onderOffset.d.ts +13 -0
  339. package/dist/components/onderOffset.js +74 -0
  340. package/dist/components/onderOffset.js.map +1 -0
  341. package/dist/components/site/Bedrijfsnummers.d.ts +9 -0
  342. package/dist/components/site/Bedrijfsnummers.js +12 -0
  343. package/dist/components/site/Bedrijfsnummers.js.map +1 -0
  344. package/dist/components/site/SiteFooter.d.ts +6 -1
  345. package/dist/components/site/SiteFooter.js +62 -9
  346. package/dist/components/site/SiteFooter.js.map +1 -1
  347. package/dist/components/site/SiteHeader.d.ts +15 -0
  348. package/dist/components/site/SiteHeader.js +70 -0
  349. package/dist/components/site/SiteHeader.js.map +1 -0
  350. package/dist/components/site/SiteNav.d.ts +2 -0
  351. package/dist/components/site/SiteNav.js +29 -5
  352. package/dist/components/site/SiteNav.js.map +1 -1
  353. package/dist/demo/DemoStudio.js +2 -2
  354. package/dist/demo/DemoStudio.js.map +1 -1
  355. package/dist/demo/demoStore.d.ts +13 -0
  356. package/dist/demo/demoStore.js +36 -0
  357. package/dist/demo/demoStore.js.map +1 -1
  358. package/dist/editor/EditorProvider.js +15 -4
  359. package/dist/editor/EditorProvider.js.map +1 -1
  360. package/dist/firebase/functionsClient.js +5 -1
  361. package/dist/firebase/functionsClient.js.map +1 -1
  362. package/dist/index.d.ts +5 -1
  363. package/dist/index.js +20 -1
  364. package/dist/index.js.map +1 -1
  365. package/dist/media/ImageContext.d.ts +17 -0
  366. package/dist/media/ImageContext.js +65 -20
  367. package/dist/media/ImageContext.js.map +1 -1
  368. package/dist/media/altInHtml.d.ts +9 -0
  369. package/dist/media/altInHtml.js +17 -0
  370. package/dist/media/altInHtml.js.map +1 -0
  371. package/dist/media/srcset.d.ts +12 -0
  372. package/dist/media/srcset.js +13 -0
  373. package/dist/media/srcset.js.map +1 -0
  374. package/dist/modules/builtins.d.ts +1 -0
  375. package/dist/modules/builtins.js +22 -9
  376. package/dist/modules/builtins.js.map +1 -1
  377. package/dist/modules/consent/ConsentBanner.d.ts +5 -0
  378. package/dist/modules/consent/ConsentBanner.js +8 -7
  379. package/dist/modules/consent/ConsentBanner.js.map +1 -1
  380. package/dist/modules/consent/standaard.d.ts +12 -0
  381. package/dist/modules/consent/standaard.js +10 -0
  382. package/dist/modules/consent/standaard.js.map +1 -0
  383. package/dist/modules/consent/useConsent.d.ts +4 -0
  384. package/dist/modules/consent/useConsent.js +30 -1
  385. package/dist/modules/consent/useConsent.js.map +1 -1
  386. package/dist/modules/shop/blocks/Cart.js +5 -3
  387. package/dist/modules/shop/blocks/Cart.js.map +1 -1
  388. package/dist/modules/shop/blocks/Checkout.js +56 -35
  389. package/dist/modules/shop/blocks/Checkout.js.map +1 -1
  390. package/dist/modules/shop/blocks/Donation.js +4 -4
  391. package/dist/modules/shop/blocks/Donation.js.map +1 -1
  392. package/dist/modules/shop/blocks/ProductDetail.js +19 -3
  393. package/dist/modules/shop/blocks/ProductDetail.js.map +1 -1
  394. package/dist/modules/shop/blocks/ProductGrid.d.ts +3 -1
  395. package/dist/modules/shop/blocks/ProductGrid.js +8 -4
  396. package/dist/modules/shop/blocks/ProductGrid.js.map +1 -1
  397. package/dist/modules/shop/blocks/ShopCategories.js +4 -3
  398. package/dist/modules/shop/blocks/ShopCategories.js.map +1 -1
  399. package/dist/modules/shop/lib.d.ts +4 -0
  400. package/dist/modules/shop/lib.js +14 -0
  401. package/dist/modules/shop/lib.js.map +1 -1
  402. package/dist/modules/shop/settings.d.ts +3 -0
  403. package/dist/modules/shop/settings.js +14 -0
  404. package/dist/modules/shop/settings.js.map +1 -1
  405. package/dist/modules/shop/strings.d.ts +4 -0
  406. package/dist/modules/shop/strings.js +8 -0
  407. package/dist/modules/shop/strings.js.map +1 -1
  408. package/dist/next/envCheck.d.ts +42 -0
  409. package/dist/next/envCheck.js +106 -0
  410. package/dist/next/envCheck.js.map +1 -0
  411. package/dist/next/index.cjs +185 -2
  412. package/dist/next/index.d.ts +2 -0
  413. package/dist/next/index.js +8 -1
  414. package/dist/next/index.js.map +1 -1
  415. package/dist/next/securityHeaders.d.ts +36 -0
  416. package/dist/next/securityHeaders.js +74 -0
  417. package/dist/next/securityHeaders.js.map +1 -0
  418. package/dist/patterns/builtins.d.ts +7 -0
  419. package/dist/patterns/builtins.js +138 -3
  420. package/dist/patterns/builtins.js.map +1 -1
  421. package/dist/releaseNotes.js +148 -0
  422. package/dist/releaseNotes.js.map +1 -1
  423. package/dist/render/PublishedPage.d.ts +2 -1
  424. package/dist/render/PublishedPage.js +10 -4
  425. package/dist/render/PublishedPage.js.map +1 -1
  426. package/dist/schema/blocks.d.ts +473 -0
  427. package/dist/schema/blocks.js +105 -5
  428. package/dist/schema/blocks.js.map +1 -1
  429. package/dist/schema/business.d.ts +118 -0
  430. package/dist/schema/business.js +75 -0
  431. package/dist/schema/business.js.map +1 -0
  432. package/dist/schema/fields.d.ts +2 -1
  433. package/dist/schema/fields.js.map +1 -1
  434. package/dist/schema/forms.d.ts +18 -0
  435. package/dist/schema/forms.js +18 -3
  436. package/dist/schema/forms.js.map +1 -1
  437. package/dist/schema/mail.d.ts +10 -0
  438. package/dist/schema/mail.js +7 -0
  439. package/dist/schema/mail.js.map +1 -1
  440. package/dist/schema/settings.d.ts +103 -0
  441. package/dist/schema/settings.js +24 -3
  442. package/dist/schema/settings.js.map +1 -1
  443. package/dist/schema/shop.d.ts +2 -0
  444. package/dist/schema/shop.js +4 -0
  445. package/dist/schema/shop.js.map +1 -1
  446. package/dist/schema/shopGeld.d.ts +1 -0
  447. package/dist/schema/shopGeld.js +1 -0
  448. package/dist/schema/shopGeld.js.map +1 -1
  449. package/dist/schema/siteConfig.d.ts +69 -1
  450. package/dist/schema/siteConfig.js +7 -2
  451. package/dist/schema/siteConfig.js.map +1 -1
  452. package/dist/server/blockData.d.ts +1 -1
  453. package/dist/server/blockData.js +74 -18
  454. package/dist/server/blockData.js.map +1 -1
  455. package/dist/server/cspRapport.d.ts +20 -0
  456. package/dist/server/cspRapport.js +56 -0
  457. package/dist/server/cspRapport.js.map +1 -0
  458. package/dist/server/fonts.d.ts +3 -0
  459. package/dist/server/fonts.js +39 -0
  460. package/dist/server/fonts.js.map +1 -0
  461. package/dist/server/index.d.ts +3 -0
  462. package/dist/server/index.js +8 -0
  463. package/dist/server/index.js.map +1 -1
  464. package/dist/server/sanitize.js +2 -0
  465. package/dist/server/sanitize.js.map +1 -1
  466. package/dist/settings/useSiteSettings.js +4 -1
  467. package/dist/settings/useSiteSettings.js.map +1 -1
  468. package/dist/starters/starters.d.ts +69 -0
  469. package/dist/starters/starters.js +171 -0
  470. package/dist/starters/starters.js.map +1 -0
  471. package/dist/styles.css +1181 -212
  472. package/dist/theme/color.d.ts +10 -0
  473. package/dist/theme/color.js +17 -1
  474. package/dist/theme/color.js.map +1 -1
  475. package/dist/theme/fontFallback.d.ts +23 -0
  476. package/dist/theme/fontFallback.js +58 -0
  477. package/dist/theme/fontFallback.js.map +1 -0
  478. package/dist/theme/fonts.d.ts +11 -0
  479. package/dist/theme/fonts.js +16 -0
  480. package/dist/theme/fonts.js.map +1 -0
  481. package/dist/version.js +1 -1
  482. package/package.json +5 -4
@@ -0,0 +1,82 @@
1
+ const LEESSNELHEID = 220;
2
+ function htmlNaarTekst(html) {
3
+ return html.replace(/<(br|\/p|\/h[1-6]|\/li|\/blockquote|\/div|\/tr)\s*\/?>/gi, ".\n").replace(/<[^>]+>/g, " ").replace(/&nbsp;/g, " ").replace(/&amp;/g, "&").replace(/&[a-z]+;/gi, " ");
4
+ }
5
+ function lettergrepen(woord) {
6
+ const w = woord.toLowerCase().replace(/[^a-zà-ÿ]/g, "").replace(/[ëïöüä]/g, (c) => `-${{ \u00EB: "e", \u00EF: "i", \u00F6: "o", \u00FC: "u", \u00E4: "a" }[c]}`);
7
+ if (!w) return 0;
8
+ const groepen = w.split("-").reduce((n, deel) => n + (deel.match(/[aeiouyàáèéêíóôú]+/g)?.length ?? 0), 0);
9
+ return Math.max(1, groepen);
10
+ }
11
+ function woordenVan(tekst) {
12
+ return tekst.split(/\s+/).map((w) => w.replace(/^[^\p{L}\d]+|[^\p{L}\d]+$/gu, "")).filter((w) => /\p{L}/u.test(w));
13
+ }
14
+ function zinnenVan(tekst) {
15
+ return tekst.split(/(?<=[.!?…])\s+|\n+/).map((z) => z.trim()).filter((z) => woordenVan(z).length > 0);
16
+ }
17
+ function niveauVoor(score) {
18
+ if (score >= 80) return { naam: "Heel makkelijk", uitleg: "Rond taalniveau A2: begrijpelijk voor vrijwel iedereen.", toon: "goed" };
19
+ if (score >= 60) return { naam: "Makkelijk", uitleg: "Rond taalniveau B1: goed voor de meeste bezoekers. Dit is het streven.", toon: "goed" };
20
+ if (score >= 45) return { naam: "Redelijk", uitleg: "Rond taalniveau B2: prima voor een ge\xEFnteresseerde lezer, iets zwaarder voor anderen.", toon: "matig" };
21
+ if (score >= 30) return { naam: "Moeilijk", uitleg: "Rond taalniveau C1: veel lezers haken af. Kortere zinnen en woorden helpen.", toon: "slecht" };
22
+ return { naam: "Heel moeilijk", uitleg: "Vakliteratuur-niveau: voor een website bijna altijd te zwaar.", toon: "slecht" };
23
+ }
24
+ const PASSIEF = /\b(wordt|worden|werd|werden)\b(?:\s+\S+){0,4}?\s+(ge\p{L}+(?:d|t|en))\b/giu;
25
+ function meetLeesbaarheid(tekst) {
26
+ const zinnen = zinnenVan(tekst);
27
+ const woorden = woordenVan(tekst);
28
+ const n = woorden.length;
29
+ if (n === 0) {
30
+ return { woorden: 0, zinnen: 0, woordenPerZin: 0, score: 0, niveau: { naam: "\u2014", uitleg: "Nog geen tekst.", toon: "matig" }, leestijd: 0, tips: [] };
31
+ }
32
+ const zinAantal = Math.max(1, zinnen.length);
33
+ const grepen = woorden.reduce((s, w) => s + lettergrepen(w), 0);
34
+ const wpz = n / zinAantal;
35
+ const ruw = 206.84 - 0.77 * (grepen / n * 100) - 0.93 * wpz;
36
+ const score = Math.round(Math.min(100, Math.max(0, ruw)));
37
+ const tips = [];
38
+ const lang = zinnen.filter((z) => woordenVan(z).length > 25);
39
+ if (lang.length > 0) {
40
+ const begin = woordenVan(lang[0]).slice(0, 6).join(" ");
41
+ tips.push(
42
+ lang.length === 1 ? `1 zin is langer dan 25 woorden (\u201C${begin} \u2026\u201D). Knip hem in twee\xEBn.` : `${lang.length} zinnen zijn langer dan 25 woorden (bijvoorbeeld \u201C${begin} \u2026\u201D). Knip ze op.`
43
+ );
44
+ }
45
+ const langeWoorden = woorden.filter((w) => lettergrepen(w) >= 5);
46
+ if (n >= 40 && langeWoorden.length / n > 0.06) {
47
+ const voorbeelden = [...new Set(langeWoorden.map((w) => w.toLowerCase()))].slice(0, 3).join(", ");
48
+ tips.push(`Veel lange woorden (${voorbeelden}). Kies waar het kan een korter woord.`);
49
+ }
50
+ const passief = [...tekst.matchAll(PASSIEF)].length;
51
+ if (passief >= 2 && passief / zinAantal > 0.2) {
52
+ tips.push(`${passief} zinnen in de lijdende vorm (\u201Cwordt \u2026 gedaan\u201D). Schrijf actiever: wie doet wat?`);
53
+ }
54
+ if (wpz > 18 && lang.length === 0) tips.push(`Gemiddeld ${Math.round(wpz)} woorden per zin. Rond de 15 leest het prettigst.`);
55
+ if (n < 50) tips.push("Korte tekst: de score is dan nog weinig betrouwbaar.");
56
+ return { woorden: n, zinnen: zinAantal, woordenPerZin: Math.round(wpz * 10) / 10, score, niveau: niveauVoor(score), leestijd: Math.max(1, Math.ceil(n / LEESSNELHEID)), tips };
57
+ }
58
+ const GEEN_TEKST = /^(_key|_type|_style|id|icon|variant|layout|align|size|theme|mode|provider|formId|slug|path|anchorId)$|(url|href|src|Url|Href|Src|Id|Key|Color|color)$/;
59
+ function paginaTekst(blocks) {
60
+ const delen = [];
61
+ const loop = (v, sleutel, diepte) => {
62
+ if (diepte > 12 || v == null) return;
63
+ if (typeof v === "string") {
64
+ if (GEEN_TEKST.test(sleutel) || /^(https?:|mailto:|tel:|\/|#)/.test(v.trim())) return;
65
+ const t = /html$/i.test(sleutel) || /<[a-z][^>]*>/i.test(v) ? htmlNaarTekst(v) : v;
66
+ if (t.trim()) delen.push(t.trim().replace(/[.!?…]?$/, (m) => m || "."));
67
+ return;
68
+ }
69
+ if (Array.isArray(v)) return v.forEach((x) => loop(x, sleutel, diepte + 1));
70
+ if (typeof v === "object") for (const [k, x] of Object.entries(v)) loop(x, k, diepte + 1);
71
+ };
72
+ loop(blocks, "", 0);
73
+ return delen.join("\n");
74
+ }
75
+ export {
76
+ htmlNaarTekst,
77
+ lettergrepen,
78
+ meetLeesbaarheid,
79
+ niveauVoor,
80
+ paginaTekst
81
+ };
82
+ //# sourceMappingURL=leesbaarheid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/admin/lib/leesbaarheid.ts"],"sourcesContent":["/**\n * Leesbaarheid van Nederlandse tekst (8.5) — geïnspireerd op de analyse-tab van AstraCMS, maar voor\n * het Nederlands: Flesch-Douma i.p.v. de Engelse Flesch Reading Ease, en niveaus in termen van\n * taalniveau (B1 is de gangbare norm voor teksten voor een breed publiek). Puur, zonder DOM.\n *\n * score = 206,84 − 0,77 × (lettergrepen per 100 woorden) − 0,93 × (woorden per zin)\n *\n * De lettergreeptelling is een heuristiek (klinkergroepen, met trema als grens) — goed genoeg voor\n * een indicatie, niet voor taalkundig onderzoek. Het paneel zegt dat er ook bij.\n */\nexport interface Leesbaarheid {\n woorden: number;\n zinnen: number;\n woordenPerZin: number;\n score: number;\n niveau: { naam: string; uitleg: string; toon: \"goed\" | \"matig\" | \"slecht\" };\n /** Minuten, naar boven afgerond (minimaal 1 bij tekst). */\n leestijd: number;\n tips: string[];\n}\n\nconst LEESSNELHEID = 220; // woorden per minuut, gemiddelde volwassen lezer op een scherm\n\nexport function htmlNaarTekst(html: string): string {\n return html\n .replace(/<(br|\\/p|\\/h[1-6]|\\/li|\\/blockquote|\\/div|\\/tr)\\s*\\/?>/gi, \".\\n\")\n .replace(/<[^>]+>/g, \" \")\n .replace(/&nbsp;/g, \" \")\n .replace(/&amp;/g, \"&\")\n .replace(/&[a-z]+;/gi, \" \");\n}\n\nexport function lettergrepen(woord: string): number {\n const w = woord\n .toLowerCase()\n .replace(/[^a-zà-ÿ]/g, \"\")\n // Een trema begint een nieuwe lettergreep (\"beïnvloeden\", \"ideeën\").\n .replace(/[ëïöüä]/g, (c) => `-${({ ë: \"e\", ï: \"i\", ö: \"o\", ü: \"u\", ä: \"a\" } as Record<string, string>)[c]}`);\n if (!w) return 0;\n const groepen = w.split(\"-\").reduce((n, deel) => n + (deel.match(/[aeiouyàáèéêíóôú]+/g)?.length ?? 0), 0);\n return Math.max(1, groepen);\n}\n\nfunction woordenVan(tekst: string): string[] {\n return tekst.split(/\\s+/).map((w) => w.replace(/^[^\\p{L}\\d]+|[^\\p{L}\\d]+$/gu, \"\")).filter((w) => /\\p{L}/u.test(w));\n}\n\nfunction zinnenVan(tekst: string): string[] {\n return tekst\n .split(/(?<=[.!?…])\\s+|\\n+/)\n .map((z) => z.trim())\n .filter((z) => woordenVan(z).length > 0);\n}\n\nexport function niveauVoor(score: number): Leesbaarheid[\"niveau\"] {\n if (score >= 80) return { naam: \"Heel makkelijk\", uitleg: \"Rond taalniveau A2: begrijpelijk voor vrijwel iedereen.\", toon: \"goed\" };\n if (score >= 60) return { naam: \"Makkelijk\", uitleg: \"Rond taalniveau B1: goed voor de meeste bezoekers. Dit is het streven.\", toon: \"goed\" };\n if (score >= 45) return { naam: \"Redelijk\", uitleg: \"Rond taalniveau B2: prima voor een geïnteresseerde lezer, iets zwaarder voor anderen.\", toon: \"matig\" };\n if (score >= 30) return { naam: \"Moeilijk\", uitleg: \"Rond taalniveau C1: veel lezers haken af. Kortere zinnen en woorden helpen.\", toon: \"slecht\" };\n return { naam: \"Heel moeilijk\", uitleg: \"Vakliteratuur-niveau: voor een website bijna altijd te zwaar.\", toon: \"slecht\" };\n}\n\nconst PASSIEF = /\\b(wordt|worden|werd|werden)\\b(?:\\s+\\S+){0,4}?\\s+(ge\\p{L}+(?:d|t|en))\\b/giu;\n\nexport function meetLeesbaarheid(tekst: string): Leesbaarheid {\n const zinnen = zinnenVan(tekst);\n const woorden = woordenVan(tekst);\n const n = woorden.length;\n if (n === 0) {\n return { woorden: 0, zinnen: 0, woordenPerZin: 0, score: 0, niveau: { naam: \"—\", uitleg: \"Nog geen tekst.\", toon: \"matig\" }, leestijd: 0, tips: [] };\n }\n const zinAantal = Math.max(1, zinnen.length);\n const grepen = woorden.reduce((s, w) => s + lettergrepen(w), 0);\n const wpz = n / zinAantal;\n const ruw = 206.84 - 0.77 * ((grepen / n) * 100) - 0.93 * wpz;\n const score = Math.round(Math.min(100, Math.max(0, ruw)));\n\n const tips: string[] = [];\n const lang = zinnen.filter((z) => woordenVan(z).length > 25);\n if (lang.length > 0) {\n const begin = woordenVan(lang[0]).slice(0, 6).join(\" \");\n tips.push(\n lang.length === 1\n ? `1 zin is langer dan 25 woorden (“${begin} …”). Knip hem in tweeën.`\n : `${lang.length} zinnen zijn langer dan 25 woorden (bijvoorbeeld “${begin} …”). Knip ze op.`,\n );\n }\n const langeWoorden = woorden.filter((w) => lettergrepen(w) >= 5);\n if (n >= 40 && langeWoorden.length / n > 0.06) {\n const voorbeelden = [...new Set(langeWoorden.map((w) => w.toLowerCase()))].slice(0, 3).join(\", \");\n tips.push(`Veel lange woorden (${voorbeelden}). Kies waar het kan een korter woord.`);\n }\n const passief = [...tekst.matchAll(PASSIEF)].length;\n if (passief >= 2 && passief / zinAantal > 0.2) {\n tips.push(`${passief} zinnen in de lijdende vorm (“wordt … gedaan”). Schrijf actiever: wie doet wat?`);\n }\n if (wpz > 18 && lang.length === 0) tips.push(`Gemiddeld ${Math.round(wpz)} woorden per zin. Rond de 15 leest het prettigst.`);\n if (n < 50) tips.push(\"Korte tekst: de score is dan nog weinig betrouwbaar.\");\n\n return { woorden: n, zinnen: zinAantal, woordenPerZin: Math.round(wpz * 10) / 10, score, niveau: niveauVoor(score), leestijd: Math.max(1, Math.ceil(n / LEESSNELHEID)), tips };\n}\n\nconst GEEN_TEKST = /^(_key|_type|_style|id|icon|variant|layout|align|size|theme|mode|provider|formId|slug|path|anchorId)$|(url|href|src|Url|Href|Src|Id|Key|Color|color)$/;\n\n/**\n * De lopende tekst van een pagina uit zijn blokken (koppen, alinea's, kaartteksten), elk stuk als\n * eigen \"zin\" — zodat een kop niet aan de volgende alinea vastgeplakt wordt. Adressen, sleutels en\n * instellingen tellen niet mee.\n */\nexport function paginaTekst(blocks: unknown): string {\n const delen: string[] = [];\n const loop = (v: unknown, sleutel: string, diepte: number) => {\n if (diepte > 12 || v == null) return;\n if (typeof v === \"string\") {\n if (GEEN_TEKST.test(sleutel) || /^(https?:|mailto:|tel:|\\/|#)/.test(v.trim())) return;\n const t = /html$/i.test(sleutel) || /<[a-z][^>]*>/i.test(v) ? htmlNaarTekst(v) : v;\n if (t.trim()) delen.push(t.trim().replace(/[.!?…]?$/, (m) => m || \".\"));\n return;\n }\n if (Array.isArray(v)) return v.forEach((x) => loop(x, sleutel, diepte + 1));\n if (typeof v === \"object\") for (const [k, x] of Object.entries(v as Record<string, unknown>)) loop(x, k, diepte + 1);\n };\n loop(blocks, \"\", 0);\n return delen.join(\"\\n\");\n}\n"],"mappings":"AAqBA,MAAM,eAAe;AAEd,SAAS,cAAc,MAAsB;AAClD,SAAO,KACJ,QAAQ,4DAA4D,KAAK,EACzE,QAAQ,YAAY,GAAG,EACvB,QAAQ,WAAW,GAAG,EACtB,QAAQ,UAAU,GAAG,EACrB,QAAQ,cAAc,GAAG;AAC9B;AAEO,SAAS,aAAa,OAAuB;AAClD,QAAM,IAAI,MACP,YAAY,EACZ,QAAQ,cAAc,EAAE,EAExB,QAAQ,YAAY,CAAC,MAAM,IAAK,EAAE,QAAG,KAAK,QAAG,KAAK,QAAG,KAAK,QAAG,KAAK,QAAG,IAAI,EAA6B,CAAC,CAAC,EAAE;AAC7G,MAAI,CAAC,EAAG,QAAO;AACf,QAAM,UAAU,EAAE,MAAM,GAAG,EAAE,OAAO,CAAC,GAAG,SAAS,KAAK,KAAK,MAAM,qBAAqB,GAAG,UAAU,IAAI,CAAC;AACxG,SAAO,KAAK,IAAI,GAAG,OAAO;AAC5B;AAEA,SAAS,WAAW,OAAyB;AAC3C,SAAO,MAAM,MAAM,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,QAAQ,+BAA+B,EAAE,CAAC,EAAE,OAAO,CAAC,MAAM,SAAS,KAAK,CAAC,CAAC;AACnH;AAEA,SAAS,UAAU,OAAyB;AAC1C,SAAO,MACJ,MAAM,oBAAoB,EAC1B,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,OAAO,CAAC,MAAM,WAAW,CAAC,EAAE,SAAS,CAAC;AAC3C;AAEO,SAAS,WAAW,OAAuC;AAChE,MAAI,SAAS,GAAI,QAAO,EAAE,MAAM,kBAAkB,QAAQ,2DAA2D,MAAM,OAAO;AAClI,MAAI,SAAS,GAAI,QAAO,EAAE,MAAM,aAAa,QAAQ,0EAA0E,MAAM,OAAO;AAC5I,MAAI,SAAS,GAAI,QAAO,EAAE,MAAM,YAAY,QAAQ,4FAAyF,MAAM,QAAQ;AAC3J,MAAI,SAAS,GAAI,QAAO,EAAE,MAAM,YAAY,QAAQ,+EAA+E,MAAM,SAAS;AAClJ,SAAO,EAAE,MAAM,iBAAiB,QAAQ,iEAAiE,MAAM,SAAS;AAC1H;AAEA,MAAM,UAAU;AAET,SAAS,iBAAiB,OAA6B;AAC5D,QAAM,SAAS,UAAU,KAAK;AAC9B,QAAM,UAAU,WAAW,KAAK;AAChC,QAAM,IAAI,QAAQ;AAClB,MAAI,MAAM,GAAG;AACX,WAAO,EAAE,SAAS,GAAG,QAAQ,GAAG,eAAe,GAAG,OAAO,GAAG,QAAQ,EAAE,MAAM,UAAK,QAAQ,mBAAmB,MAAM,QAAQ,GAAG,UAAU,GAAG,MAAM,CAAC,EAAE;AAAA,EACrJ;AACA,QAAM,YAAY,KAAK,IAAI,GAAG,OAAO,MAAM;AAC3C,QAAM,SAAS,QAAQ,OAAO,CAAC,GAAG,MAAM,IAAI,aAAa,CAAC,GAAG,CAAC;AAC9D,QAAM,MAAM,IAAI;AAChB,QAAM,MAAM,SAAS,QAAS,SAAS,IAAK,OAAO,OAAO;AAC1D,QAAM,QAAQ,KAAK,MAAM,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,GAAG,CAAC,CAAC;AAExD,QAAM,OAAiB,CAAC;AACxB,QAAM,OAAO,OAAO,OAAO,CAAC,MAAM,WAAW,CAAC,EAAE,SAAS,EAAE;AAC3D,MAAI,KAAK,SAAS,GAAG;AACnB,UAAM,QAAQ,WAAW,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,CAAC,EAAE,KAAK,GAAG;AACtD,SAAK;AAAA,MACH,KAAK,WAAW,IACZ,yCAAoC,KAAK,2CACzC,GAAG,KAAK,MAAM,0DAAqD,KAAK;AAAA,IAC9E;AAAA,EACF;AACA,QAAM,eAAe,QAAQ,OAAO,CAAC,MAAM,aAAa,CAAC,KAAK,CAAC;AAC/D,MAAI,KAAK,MAAM,aAAa,SAAS,IAAI,MAAM;AAC7C,UAAM,cAAc,CAAC,GAAG,IAAI,IAAI,aAAa,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,MAAM,GAAG,CAAC,EAAE,KAAK,IAAI;AAChG,SAAK,KAAK,uBAAuB,WAAW,wCAAwC;AAAA,EACtF;AACA,QAAM,UAAU,CAAC,GAAG,MAAM,SAAS,OAAO,CAAC,EAAE;AAC7C,MAAI,WAAW,KAAK,UAAU,YAAY,KAAK;AAC7C,SAAK,KAAK,GAAG,OAAO,gGAAiF;AAAA,EACvG;AACA,MAAI,MAAM,MAAM,KAAK,WAAW,EAAG,MAAK,KAAK,aAAa,KAAK,MAAM,GAAG,CAAC,mDAAmD;AAC5H,MAAI,IAAI,GAAI,MAAK,KAAK,sDAAsD;AAE5E,SAAO,EAAE,SAAS,GAAG,QAAQ,WAAW,eAAe,KAAK,MAAM,MAAM,EAAE,IAAI,IAAI,OAAO,QAAQ,WAAW,KAAK,GAAG,UAAU,KAAK,IAAI,GAAG,KAAK,KAAK,IAAI,YAAY,CAAC,GAAG,KAAK;AAC/K;AAEA,MAAM,aAAa;AAOZ,SAAS,YAAY,QAAyB;AACnD,QAAM,QAAkB,CAAC;AACzB,QAAM,OAAO,CAAC,GAAY,SAAiB,WAAmB;AAC5D,QAAI,SAAS,MAAM,KAAK,KAAM;AAC9B,QAAI,OAAO,MAAM,UAAU;AACzB,UAAI,WAAW,KAAK,OAAO,KAAK,+BAA+B,KAAK,EAAE,KAAK,CAAC,EAAG;AAC/E,YAAM,IAAI,SAAS,KAAK,OAAO,KAAK,gBAAgB,KAAK,CAAC,IAAI,cAAc,CAAC,IAAI;AACjF,UAAI,EAAE,KAAK,EAAG,OAAM,KAAK,EAAE,KAAK,EAAE,QAAQ,YAAY,CAAC,MAAM,KAAK,GAAG,CAAC;AACtE;AAAA,IACF;AACA,QAAI,MAAM,QAAQ,CAAC,EAAG,QAAO,EAAE,QAAQ,CAAC,MAAM,KAAK,GAAG,SAAS,SAAS,CAAC,CAAC;AAC1E,QAAI,OAAO,MAAM,SAAU,YAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,CAA4B,EAAG,MAAK,GAAG,GAAG,SAAS,CAAC;AAAA,EACrH;AACA,OAAK,QAAQ,IAAI,CAAC;AAClB,SAAO,MAAM,KAAK,IAAI;AACxB;","names":[]}
@@ -0,0 +1 @@
1
+ export declare function lengteAdvies(veld: string, waarde: string | undefined): string | null;
@@ -0,0 +1,16 @@
1
+ const REGELS = [
2
+ { test: /(^|\.)(buttonLabel|ctaLabel|linkLabel|button2Label|label)$/, max: 28, wat: "Een knoptekst" },
3
+ { test: /(^|\.)(title|heading)$/, max: 70, wat: "Een kop" },
4
+ { test: /(^|\.)(subtitle|eyebrow)$/, max: 140, wat: "Deze regel" }
5
+ ];
6
+ function lengteAdvies(veld, waarde) {
7
+ const n = (waarde ?? "").trim().length;
8
+ if (!n) return null;
9
+ const regel = REGELS.find((r) => r.test.test(veld));
10
+ if (!regel || n <= regel.max) return null;
11
+ return `${regel.wat} van ${n} tekens wordt op een telefoon erg lang \u2014 liefst onder de ${regel.max}.`;
12
+ }
13
+ export {
14
+ lengteAdvies
15
+ };
16
+ //# sourceMappingURL=lengteAdvies.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/admin/lib/lengteAdvies.ts"],"sourcesContent":["/**\n * Zachte lengte-adviezen voor teksten die de opmaak dragen (8.10).\n *\n * Een hero-titel van drie regels of een knop met een hele zin breekt de layout, vooral op een\n * telefoon. Hard begrenzen is vervelend (en breekt bestaande inhoud); een rustige melding bij het\n * typen werkt beter. Alleen voor velden waarvan we zeker weten wat ze zijn, op hun sleutel.\n */\nconst REGELS: { test: RegExp; max: number; wat: string }[] = [\n { test: /(^|\\.)(buttonLabel|ctaLabel|linkLabel|button2Label|label)$/, max: 28, wat: \"Een knoptekst\" },\n { test: /(^|\\.)(title|heading)$/, max: 70, wat: \"Een kop\" },\n { test: /(^|\\.)(subtitle|eyebrow)$/, max: 140, wat: \"Deze regel\" },\n];\n\nexport function lengteAdvies(veld: string, waarde: string | undefined): string | null {\n const n = (waarde ?? \"\").trim().length;\n if (!n) return null;\n const regel = REGELS.find((r) => r.test.test(veld));\n if (!regel || n <= regel.max) return null;\n return `${regel.wat} van ${n} tekens wordt op een telefoon erg lang — liefst onder de ${regel.max}.`;\n}\n"],"mappings":"AAOA,MAAM,SAAuD;AAAA,EAC3D,EAAE,MAAM,8DAA8D,KAAK,IAAI,KAAK,gBAAgB;AAAA,EACpG,EAAE,MAAM,0BAA0B,KAAK,IAAI,KAAK,UAAU;AAAA,EAC1D,EAAE,MAAM,6BAA6B,KAAK,KAAK,KAAK,aAAa;AACnE;AAEO,SAAS,aAAa,MAAc,QAA2C;AACpF,QAAM,KAAK,UAAU,IAAI,KAAK,EAAE;AAChC,MAAI,CAAC,EAAG,QAAO;AACf,QAAM,QAAQ,OAAO,KAAK,CAAC,MAAM,EAAE,KAAK,KAAK,IAAI,CAAC;AAClD,MAAI,CAAC,SAAS,KAAK,MAAM,IAAK,QAAO;AACrC,SAAO,GAAG,MAAM,GAAG,QAAQ,CAAC,iEAA4D,MAAM,GAAG;AACnG;","names":[]}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Pure helpers voor de mediabibliotheek (8.5): sorteren en de meta-regel per foto.
3
+ * Los van de component zodat ze testbaar zijn.
4
+ */
5
+ export type MediaSortering = "nieuw" | "oud" | "naam" | "groot";
6
+ export declare const MEDIA_SORTERINGEN: {
7
+ value: MediaSortering;
8
+ label: string;
9
+ }[];
10
+ type Sorteerbaar = {
11
+ name?: string;
12
+ size?: number;
13
+ createdAt?: unknown;
14
+ };
15
+ /** Firestore-Timestamp (of iets met toMillis) → ms; ontbreekt = 0 (net geüpload, nog geen servertijd). */
16
+ export declare function aangemaaktOp(v: unknown): number;
17
+ /**
18
+ * Sorteert een kopie. Bij "nieuw" telt een ontbrekende tijd als nieuwst: een verse upload heeft
19
+ * nog geen servertimestamp en hoort bovenaan. Gelijke waarden houden hun volgorde (stabiel).
20
+ */
21
+ export declare function sorteerMedia<T extends Sorteerbaar>(items: T[], s: MediaSortering): T[];
22
+ /** 834 B · 12 kB · 1,4 MB — Nederlandse komma, geen schijnprecisie. */
23
+ export declare function leesbareGrootte(bytes: number | undefined): string;
24
+ /** "12 okt 2026" of leeg als er (nog) geen datum is. */
25
+ export declare function korteDatum(v: unknown): string;
26
+ export {};
@@ -0,0 +1,42 @@
1
+ const MEDIA_SORTERINGEN = [
2
+ { value: "nieuw", label: "Nieuwste eerst" },
3
+ { value: "oud", label: "Oudste eerst" },
4
+ { value: "naam", label: "Op naam (A\u2013Z)" },
5
+ { value: "groot", label: "Grootste eerst" }
6
+ ];
7
+ function aangemaaktOp(v) {
8
+ const t = v;
9
+ return typeof t?.toMillis === "function" ? t.toMillis() : 0;
10
+ }
11
+ function sorteerMedia(items, s) {
12
+ const tijd = (m) => aangemaaktOp(m.createdAt) || Number.MAX_SAFE_INTEGER;
13
+ const kopie = [...items];
14
+ switch (s) {
15
+ case "nieuw":
16
+ return kopie.sort((a, b) => tijd(b) - tijd(a));
17
+ case "oud":
18
+ return kopie.sort((a, b) => tijd(a) - tijd(b));
19
+ case "naam":
20
+ return kopie.sort((a, b) => (a.name ?? "").localeCompare(b.name ?? "", "nl", { numeric: true, sensitivity: "base" }));
21
+ case "groot":
22
+ return kopie.sort((a, b) => (b.size ?? 0) - (a.size ?? 0));
23
+ }
24
+ }
25
+ function leesbareGrootte(bytes) {
26
+ if (!bytes || bytes < 0) return "";
27
+ if (bytes < 1024) return `${bytes} B`;
28
+ if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} kB`;
29
+ return `${(bytes / (1024 * 1024)).toLocaleString("nl-NL", { maximumFractionDigits: 1 })} MB`;
30
+ }
31
+ function korteDatum(v) {
32
+ const ms = aangemaaktOp(v);
33
+ return ms ? new Date(ms).toLocaleDateString("nl-NL", { day: "numeric", month: "short", year: "numeric" }) : "";
34
+ }
35
+ export {
36
+ MEDIA_SORTERINGEN,
37
+ aangemaaktOp,
38
+ korteDatum,
39
+ leesbareGrootte,
40
+ sorteerMedia
41
+ };
42
+ //# sourceMappingURL=mediaLijst.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/admin/lib/mediaLijst.ts"],"sourcesContent":["/**\n * Pure helpers voor de mediabibliotheek (8.5): sorteren en de meta-regel per foto.\n * Los van de component zodat ze testbaar zijn.\n */\n\nexport type MediaSortering = \"nieuw\" | \"oud\" | \"naam\" | \"groot\";\n\nexport const MEDIA_SORTERINGEN: { value: MediaSortering; label: string }[] = [\n { value: \"nieuw\", label: \"Nieuwste eerst\" },\n { value: \"oud\", label: \"Oudste eerst\" },\n { value: \"naam\", label: \"Op naam (A–Z)\" },\n { value: \"groot\", label: \"Grootste eerst\" },\n];\n\ntype Sorteerbaar = { name?: string; size?: number; createdAt?: unknown };\n\n/** Firestore-Timestamp (of iets met toMillis) → ms; ontbreekt = 0 (net geüpload, nog geen servertijd). */\nexport function aangemaaktOp(v: unknown): number {\n const t = v as { toMillis?: () => number } | null;\n return typeof t?.toMillis === \"function\" ? t.toMillis() : 0;\n}\n\n/**\n * Sorteert een kopie. Bij \"nieuw\" telt een ontbrekende tijd als nieuwst: een verse upload heeft\n * nog geen servertimestamp en hoort bovenaan. Gelijke waarden houden hun volgorde (stabiel).\n */\nexport function sorteerMedia<T extends Sorteerbaar>(items: T[], s: MediaSortering): T[] {\n const tijd = (m: T) => aangemaaktOp(m.createdAt) || Number.MAX_SAFE_INTEGER;\n const kopie = [...items];\n switch (s) {\n case \"nieuw\":\n return kopie.sort((a, b) => tijd(b) - tijd(a));\n case \"oud\":\n return kopie.sort((a, b) => tijd(a) - tijd(b));\n case \"naam\":\n return kopie.sort((a, b) => (a.name ?? \"\").localeCompare(b.name ?? \"\", \"nl\", { numeric: true, sensitivity: \"base\" }));\n case \"groot\":\n return kopie.sort((a, b) => (b.size ?? 0) - (a.size ?? 0));\n }\n}\n\n/** 834 B · 12 kB · 1,4 MB — Nederlandse komma, geen schijnprecisie. */\nexport function leesbareGrootte(bytes: number | undefined): string {\n if (!bytes || bytes < 0) return \"\";\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} kB`;\n return `${(bytes / (1024 * 1024)).toLocaleString(\"nl-NL\", { maximumFractionDigits: 1 })} MB`;\n}\n\n/** \"12 okt 2026\" of leeg als er (nog) geen datum is. */\nexport function korteDatum(v: unknown): string {\n const ms = aangemaaktOp(v);\n return ms ? new Date(ms).toLocaleDateString(\"nl-NL\", { day: \"numeric\", month: \"short\", year: \"numeric\" }) : \"\";\n}\n"],"mappings":"AAOO,MAAM,oBAAgE;AAAA,EAC3E,EAAE,OAAO,SAAS,OAAO,iBAAiB;AAAA,EAC1C,EAAE,OAAO,OAAO,OAAO,eAAe;AAAA,EACtC,EAAE,OAAO,QAAQ,OAAO,qBAAgB;AAAA,EACxC,EAAE,OAAO,SAAS,OAAO,iBAAiB;AAC5C;AAKO,SAAS,aAAa,GAAoB;AAC/C,QAAM,IAAI;AACV,SAAO,OAAO,GAAG,aAAa,aAAa,EAAE,SAAS,IAAI;AAC5D;AAMO,SAAS,aAAoC,OAAY,GAAwB;AACtF,QAAM,OAAO,CAAC,MAAS,aAAa,EAAE,SAAS,KAAK,OAAO;AAC3D,QAAM,QAAQ,CAAC,GAAG,KAAK;AACvB,UAAQ,GAAG;AAAA,IACT,KAAK;AACH,aAAO,MAAM,KAAK,CAAC,GAAG,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC;AAAA,IAC/C,KAAK;AACH,aAAO,MAAM,KAAK,CAAC,GAAG,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC;AAAA,IAC/C,KAAK;AACH,aAAO,MAAM,KAAK,CAAC,GAAG,OAAO,EAAE,QAAQ,IAAI,cAAc,EAAE,QAAQ,IAAI,MAAM,EAAE,SAAS,MAAM,aAAa,OAAO,CAAC,CAAC;AAAA,IACtH,KAAK;AACH,aAAO,MAAM,KAAK,CAAC,GAAG,OAAO,EAAE,QAAQ,MAAM,EAAE,QAAQ,EAAE;AAAA,EAC7D;AACF;AAGO,SAAS,gBAAgB,OAAmC;AACjE,MAAI,CAAC,SAAS,QAAQ,EAAG,QAAO;AAChC,MAAI,QAAQ,KAAM,QAAO,GAAG,KAAK;AACjC,MAAI,QAAQ,OAAO,KAAM,QAAO,GAAG,KAAK,MAAM,QAAQ,IAAI,CAAC;AAC3D,SAAO,IAAI,SAAS,OAAO,OAAO,eAAe,SAAS,EAAE,uBAAuB,EAAE,CAAC,CAAC;AACzF;AAGO,SAAS,WAAW,GAAoB;AAC7C,QAAM,KAAK,aAAa,CAAC;AACzB,SAAO,KAAK,IAAI,KAAK,EAAE,EAAE,mBAAmB,SAAS,EAAE,KAAK,WAAW,OAAO,SAAS,MAAM,UAAU,CAAC,IAAI;AAC9G;","names":[]}
@@ -7,7 +7,7 @@
7
7
  export declare function findMediaUsage(siteId: string, item: {
8
8
  id: string;
9
9
  url?: string;
10
- }): Promise<string[]>;
10
+ }): Promise<string[] | null>;
11
11
  /**
12
12
  * Bulk-variant voor het opruim-paneel (7.14): één content-read voor de HELE bibliotheek in
13
13
  * plaats van (zoals `findMediaUsage`) alle content per afbeelding opnieuw te lezen — bij
@@ -25,3 +25,14 @@ export declare function findUnusedMedia(siteId: string, items: Array<{
25
25
  createdAt?: unknown;
26
26
  size?: number;
27
27
  }>): Promise<string[]>;
28
+ /**
29
+ * Puur deel (testbaar): welke items komen in het corpus nergens voor? Voorzichtig: nog in
30
+ * verwerking of jonger dan een uur telt níét als ongebruikt — een net geüploade foto die de klant
31
+ * nog in een blok gaat zetten, hoort niet in een opruimlijst.
32
+ */
33
+ export declare function ongebruikteMedia(items: Array<{
34
+ id: string;
35
+ url?: string;
36
+ status?: string;
37
+ createdAt?: unknown;
38
+ }>, corpus: string, nu: number): string[];
@@ -5,49 +5,58 @@ async function findMediaUsage(siteId, item) {
5
5
  const db = getDb();
6
6
  if (!db) return [];
7
7
  const matches = (blob) => item.url && blob.includes(item.url) || blob.includes(item.id);
8
- const hits = [];
8
+ const collecties = [
9
+ ["pages", (d, id) => String(d.path ?? d.title ?? id)],
10
+ ["drafts", (d, id) => `${String(d.path ?? id)} (concept)`],
11
+ ["news", (d, id) => `nieuws: ${String(d.title ?? id)}`],
12
+ ["symbols", (d, id) => `gedeeld blok: ${String(d.name ?? d.title ?? id)}`],
13
+ ["products", (d, id) => `product: ${String(d.name ?? d.title ?? id)}`],
14
+ ["shopCategories", (d, id) => `categorie: ${String(d.name ?? d.title ?? id)}`],
15
+ ["customBlocks", (d, id) => `bouwsteen: ${String(d.label ?? d.name ?? id)}`],
16
+ ["forms", (d, id) => `formulier: ${String(d.title ?? d.name ?? id)}`]
17
+ ];
9
18
  try {
10
- const [pages, drafts, news, settings] = await Promise.all([
11
- getDocs(collection(db, "sites", siteId, "pages")),
12
- getDocs(collection(db, "sites", siteId, "drafts")).catch(() => null),
13
- getDocs(collection(db, "sites", siteId, "news")).catch(() => null),
14
- getDoc(doc(db, "sites", siteId, "settings", "public")).catch(() => null)
19
+ const [snaps, pub, site] = await Promise.all([
20
+ Promise.all(collecties.map(([c]) => getDocs(collection(db, "sites", siteId, c)))),
21
+ getDoc(doc(db, "sites", siteId, "settings", "public")),
22
+ getDoc(doc(db, "sites", siteId, "settings", "site"))
15
23
  ]);
16
- for (const d of pages.docs) {
17
- const data = d.data();
18
- if (matches(JSON.stringify(data.blocks ?? []))) hits.push(String(data.path ?? data.title ?? d.id));
19
- }
20
- for (const d of drafts?.docs ?? []) {
21
- const data = d.data();
22
- if (matches(JSON.stringify(data.blocks ?? []))) hits.push(`${String(data.path ?? d.id)} (concept)`);
23
- }
24
- for (const d of news?.docs ?? []) {
25
- if (matches(JSON.stringify(d.data()))) hits.push(`nieuws: ${String(d.data().title ?? d.id)}`);
26
- }
27
- if (settings?.exists() && matches(JSON.stringify(settings.data()))) hits.push("site-instellingen");
24
+ const hits = [];
25
+ snaps.forEach((snap, i) => {
26
+ for (const d of snap.docs) {
27
+ const data = d.data();
28
+ if (matches(JSON.stringify(data))) hits.push(collecties[i][1](data, d.id));
29
+ }
30
+ });
31
+ if (pub.exists() && matches(JSON.stringify(pub.data())) || site.exists() && matches(JSON.stringify(site.data()))) hits.push("site-instellingen (logo, deelafbeelding \u2026)");
28
32
  return [...new Set(hits)];
29
33
  } catch {
30
- return hits;
34
+ return null;
31
35
  }
32
36
  }
33
37
  async function findUnusedMedia(siteId, items) {
34
38
  const db = getDb();
35
39
  if (!db) return [];
40
+ const collecties = ["pages", "drafts", "news", "symbols", "products", "shopCategories", "customBlocks", "forms"];
41
+ let snaps;
42
+ try {
43
+ snaps = await Promise.all([
44
+ ...collecties.map((c) => getDocs(collection(db, "sites", siteId, c))),
45
+ getDoc(doc(db, "sites", siteId, "settings", "public")),
46
+ getDoc(doc(db, "sites", siteId, "settings", "site"))
47
+ ]);
48
+ } catch (e) {
49
+ throw new Error(`Niet alle inhoud kon worden gelezen, dus de scan is afgebroken (er wordt niets als ongebruikt gemeld). ${e.message ?? ""}`.trim());
50
+ }
36
51
  const blobs = [];
37
- const [pages, drafts, news, symbols, products, settings] = await Promise.all([
38
- getDocs(collection(db, "sites", siteId, "pages")),
39
- getDocs(collection(db, "sites", siteId, "drafts")).catch(() => null),
40
- getDocs(collection(db, "sites", siteId, "news")).catch(() => null),
41
- getDocs(collection(db, "sites", siteId, "symbols")).catch(() => null),
42
- getDocs(collection(db, "sites", siteId, "products")).catch(() => null),
43
- getDoc(doc(db, "sites", siteId, "settings", "public")).catch(() => null)
44
- ]);
45
- for (const snap of [pages, drafts, news, symbols, products]) {
46
- for (const d of snap?.docs ?? []) blobs.push(JSON.stringify(d.data()));
52
+ for (const snap of snaps) {
53
+ if ("docs" in snap) for (const d of snap.docs) blobs.push(JSON.stringify(d.data()));
54
+ else if (snap.exists()) blobs.push(JSON.stringify(snap.data()));
47
55
  }
48
- if (settings?.exists()) blobs.push(JSON.stringify(settings.data()));
49
- const corpus = blobs.join("\n");
50
- const uurGeleden = Date.now() - 60 * 60 * 1e3;
56
+ return ongebruikteMedia(items, blobs.join("\n"), Date.now());
57
+ }
58
+ function ongebruikteMedia(items, corpus, nu) {
59
+ const uurGeleden = nu - 60 * 60 * 1e3;
51
60
  const millis = (v) => {
52
61
  const t = v;
53
62
  return typeof t?.toMillis === "function" ? t.toMillis() : 0;
@@ -62,6 +71,7 @@ async function findUnusedMedia(siteId, items) {
62
71
  }
63
72
  export {
64
73
  findMediaUsage,
65
- findUnusedMedia
74
+ findUnusedMedia,
75
+ ongebruikteMedia
66
76
  };
67
77
  //# sourceMappingURL=mediaUsage.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/lib/mediaUsage.ts"],"sourcesContent":["\"use client\";\n\nimport { collection, doc, getDoc, getDocs } from \"firebase/firestore\";\nimport { getDb } from \"../../firebase/client\";\n\n/**\n * Zoekt (goedkoop) of een media-item ergens in gebruik is, zodat we kunnen waarschuwen vóór\n * verwijderen (anders breekt een live pagina met een dode afbeelding). Gescand worden:\n * gepubliceerde pagina's, concepten (drafts), nieuwsartikelen (covers/inline beelden) en de\n * site-instellingen (og-beeld e.d.). Sites zijn klein → een JSON-substring-scan is ruim voldoende.\n */\nexport async function findMediaUsage(\n siteId: string,\n item: { id: string; url?: string },\n): Promise<string[]> {\n const db = getDb();\n if (!db) return [];\n const matches = (blob: string) => (item.url && blob.includes(item.url)) || blob.includes(item.id);\n const hits: string[] = [];\n try {\n const [pages, drafts, news, settings] = await Promise.all([\n getDocs(collection(db, \"sites\", siteId, \"pages\")),\n getDocs(collection(db, \"sites\", siteId, \"drafts\")).catch(() => null),\n getDocs(collection(db, \"sites\", siteId, \"news\")).catch(() => null),\n getDoc(doc(db, \"sites\", siteId, \"settings\", \"public\")).catch(() => null),\n ]);\n for (const d of pages.docs) {\n const data = d.data() as Record<string, unknown>;\n if (matches(JSON.stringify(data.blocks ?? []))) hits.push(String(data.path ?? data.title ?? d.id));\n }\n for (const d of drafts?.docs ?? []) {\n const data = d.data() as Record<string, unknown>;\n if (matches(JSON.stringify(data.blocks ?? []))) hits.push(`${String(data.path ?? d.id)} (concept)`);\n }\n for (const d of news?.docs ?? []) {\n if (matches(JSON.stringify(d.data()))) hits.push(`nieuws: ${String((d.data() as { title?: string }).title ?? d.id)}`);\n }\n if (settings?.exists() && matches(JSON.stringify(settings.data()))) hits.push(\"site-instellingen\");\n return [...new Set(hits)];\n } catch {\n return hits;\n }\n}\n\n/**\n * Bulk-variant voor het opruim-paneel (7.14): één content-read voor de HELE bibliotheek in\n * plaats van (zoals `findMediaUsage`) alle content per afbeelding opnieuw te lezen — bij\n * 60 foto's scheelt dat ~59 volledige site-reads. Kijkt in meer bronnen dan de losse check:\n * ook symbolen en producten kunnen naar een foto verwijzen.\n *\n * \"Ongebruikt\" is bewust voorzichtig: een item dat nog verwerkt wordt (status \"processing\")\n * of jonger is dan een uur telt níét mee — een net geüploade foto die de klant nog in een\n * blok gaat zetten, hoort niet in een opruimlijst.\n */\nexport async function findUnusedMedia(\n siteId: string,\n items: Array<{ id: string; url?: string; status?: string; createdAt?: unknown; size?: number }>,\n): Promise<string[]> {\n const db = getDb();\n if (!db) return [];\n const blobs: string[] = [];\n const [pages, drafts, news, symbols, products, settings] = await Promise.all([\n getDocs(collection(db, \"sites\", siteId, \"pages\")),\n getDocs(collection(db, \"sites\", siteId, \"drafts\")).catch(() => null),\n getDocs(collection(db, \"sites\", siteId, \"news\")).catch(() => null),\n getDocs(collection(db, \"sites\", siteId, \"symbols\")).catch(() => null),\n getDocs(collection(db, \"sites\", siteId, \"products\")).catch(() => null),\n getDoc(doc(db, \"sites\", siteId, \"settings\", \"public\")).catch(() => null),\n ]);\n for (const snap of [pages, drafts, news, symbols, products]) {\n for (const d of snap?.docs ?? []) blobs.push(JSON.stringify(d.data()));\n }\n if (settings?.exists()) blobs.push(JSON.stringify(settings.data()));\n const corpus = blobs.join(\"\\n\");\n\n const uurGeleden = Date.now() - 60 * 60 * 1000;\n const millis = (v: unknown): number => {\n const t = v as { toMillis?: () => number } | null;\n return typeof t?.toMillis === \"function\" ? t.toMillis() : 0;\n };\n return items\n .filter((it) => {\n if (it.status === \"processing\") return false;\n if (millis(it.createdAt) > uurGeleden) return false;\n if (it.url && corpus.includes(it.url)) return false;\n if (corpus.includes(it.id)) return false;\n return true;\n })\n .map((it) => it.id);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/lib/mediaUsage.ts"],"sourcesContent":["\"use client\";\n\nimport { collection, doc, getDoc, getDocs } from \"firebase/firestore\";\nimport { getDb } from \"../../firebase/client\";\n\n/**\n * Zoekt (goedkoop) of een media-item ergens in gebruik is, zodat we kunnen waarschuwen vóór\n * verwijderen (anders breekt een live pagina met een dode afbeelding). Gescand worden:\n * gepubliceerde pagina's, concepten (drafts), nieuwsartikelen (covers/inline beelden) en de\n * site-instellingen (og-beeld e.d.). Sites zijn klein → een JSON-substring-scan is ruim voldoende.\n */\nexport async function findMediaUsage(\n siteId: string,\n item: { id: string; url?: string },\n): Promise<string[] | null> {\n const db = getDb();\n if (!db) return [];\n const matches = (blob: string) => (item.url && blob.includes(item.url)) || blob.includes(item.id);\n /**\n * 8.10: dezelfde bronnen als de bulkscan (`findUnusedMedia`). Deze losse check keek alleen in\n * pagina's, nieuws en de publieke instellingen — een productfoto of een foto in een symbool gold\n * daardoor als \"niet in gebruik\" en verdween na de belofte \"wordt definitief verwijderd\". En een\n * leesfout gaf een lege lijst; nu `null` = \"kon het niet nagaan\", zodat de bevestiging dat zegt.\n */\n const collecties: [string, (d: Record<string, unknown>, id: string) => string][] = [\n [\"pages\", (d, id) => String(d.path ?? d.title ?? id)],\n [\"drafts\", (d, id) => `${String(d.path ?? id)} (concept)`],\n [\"news\", (d, id) => `nieuws: ${String(d.title ?? id)}`],\n [\"symbols\", (d, id) => `gedeeld blok: ${String(d.name ?? d.title ?? id)}`],\n [\"products\", (d, id) => `product: ${String(d.name ?? d.title ?? id)}`],\n [\"shopCategories\", (d, id) => `categorie: ${String(d.name ?? d.title ?? id)}`],\n [\"customBlocks\", (d, id) => `bouwsteen: ${String(d.label ?? d.name ?? id)}`],\n [\"forms\", (d, id) => `formulier: ${String(d.title ?? d.name ?? id)}`],\n ];\n try {\n const [snaps, pub, site] = await Promise.all([\n Promise.all(collecties.map(([c]) => getDocs(collection(db, \"sites\", siteId, c)))),\n getDoc(doc(db, \"sites\", siteId, \"settings\", \"public\")),\n getDoc(doc(db, \"sites\", siteId, \"settings\", \"site\")),\n ]);\n const hits: string[] = [];\n snaps.forEach((snap, i) => {\n for (const d of snap.docs) {\n const data = d.data() as Record<string, unknown>;\n if (matches(JSON.stringify(data))) hits.push(collecties[i][1](data, d.id));\n }\n });\n if ((pub.exists() && matches(JSON.stringify(pub.data()))) || (site.exists() && matches(JSON.stringify(site.data())))) hits.push(\"site-instellingen (logo, deelafbeelding …)\");\n return [...new Set(hits)];\n } catch {\n return null;\n }\n}\n\n/**\n * Bulk-variant voor het opruim-paneel (7.14): één content-read voor de HELE bibliotheek in\n * plaats van (zoals `findMediaUsage`) alle content per afbeelding opnieuw te lezen — bij\n * 60 foto's scheelt dat ~59 volledige site-reads. Kijkt in meer bronnen dan de losse check:\n * ook symbolen en producten kunnen naar een foto verwijzen.\n *\n * \"Ongebruikt\" is bewust voorzichtig: een item dat nog verwerkt wordt (status \"processing\")\n * of jonger is dan een uur telt níét mee — een net geüploade foto die de klant nog in een\n * blok gaat zetten, hoort niet in een opruimlijst.\n */\nexport async function findUnusedMedia(\n siteId: string,\n items: Array<{ id: string; url?: string; status?: string; createdAt?: unknown; size?: number }>,\n): Promise<string[]> {\n const db = getDb();\n if (!db) return [];\n /**\n * 8.1: ELKE bron moet gelezen kunnen worden. Eerst werd een mislukte lezing stil \"leeg\", en\n * kwam een foto die in die bron stond op de opruimlijst — die daarna in bulk verwijderd werd.\n * Liever geen scan dan een verkeerde. Ook bijgekomen: winkelcategorieën, bouwstenen,\n * formulieren en settings/site (die konden allemaal naar een foto verwijzen).\n */\n const collecties = [\"pages\", \"drafts\", \"news\", \"symbols\", \"products\", \"shopCategories\", \"customBlocks\", \"forms\"] as const;\n let snaps;\n try {\n snaps = await Promise.all([\n ...collecties.map((c) => getDocs(collection(db, \"sites\", siteId, c))),\n getDoc(doc(db, \"sites\", siteId, \"settings\", \"public\")),\n getDoc(doc(db, \"sites\", siteId, \"settings\", \"site\")),\n ]);\n } catch (e) {\n throw new Error(`Niet alle inhoud kon worden gelezen, dus de scan is afgebroken (er wordt niets als ongebruikt gemeld). ${(e as Error).message ?? \"\"}`.trim());\n }\n const blobs: string[] = [];\n for (const snap of snaps) {\n if (\"docs\" in snap) for (const d of snap.docs) blobs.push(JSON.stringify(d.data()));\n else if (snap.exists()) blobs.push(JSON.stringify(snap.data()));\n }\n return ongebruikteMedia(items, blobs.join(\"\\n\"), Date.now());\n}\n\n/**\n * Puur deel (testbaar): welke items komen in het corpus nergens voor? Voorzichtig: nog in\n * verwerking of jonger dan een uur telt níét als ongebruikt — een net geüploade foto die de klant\n * nog in een blok gaat zetten, hoort niet in een opruimlijst.\n */\nexport function ongebruikteMedia(\n items: Array<{ id: string; url?: string; status?: string; createdAt?: unknown }>,\n corpus: string,\n nu: number,\n): string[] {\n const uurGeleden = nu - 60 * 60 * 1000;\n const millis = (v: unknown): number => {\n const t = v as { toMillis?: () => number } | null;\n return typeof t?.toMillis === \"function\" ? t.toMillis() : 0;\n };\n return items\n .filter((it) => {\n if (it.status === \"processing\") return false;\n if (millis(it.createdAt) > uurGeleden) return false;\n if (it.url && corpus.includes(it.url)) return false;\n if (corpus.includes(it.id)) return false;\n return true;\n })\n .map((it) => it.id);\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,16 @@
1
+ import { type NavItem } from "../../schema/settings.js";
2
+ /**
3
+ * Een pagina meteen in het hoofdmenu zetten (8.2). "Nieuwe pagina" eindigde eerder op een pagina
4
+ * die nergens te vinden was: de klant moest zelf ontdekken dat er een apart Menu-paneel bestaat
5
+ * (en dat alleen een siteadmin ziet). Zelfde route als het Menu-paneel: het hele instellingen-
6
+ * document via de saveSiteSettings-callable.
7
+ */
8
+ export declare function voegToeAanMenu(siteId: string, item: NavItem, terugval?: NavItem[]): Promise<void>;
9
+ /** Eerstvolgend vrij pad: `/over`, anders `/over-2`, `/over-3`, … */
10
+ export declare function vrijPad(pad: string, bestaande: string[]): string;
11
+ /**
12
+ * Doorverwijzing toevoegen (8.4, 404-monitor): oud adres → bestaande pagina. Zelfde route als het
13
+ * paneel Doorverwijzingen (saveSiteSettings, siteadmin). Een bestaande regel voor hetzelfde adres
14
+ * wordt vervangen, zodat er geen dubbele "from" ontstaat.
15
+ */
16
+ export declare function voegRedirectToe(siteId: string, from: string, to: string): Promise<void>;
@@ -0,0 +1,41 @@
1
+ "use client";
2
+ import { doc, getDoc } from "firebase/firestore";
3
+ import { getDb, getFirebaseApp } from "../../firebase/client.js";
4
+ import { callable } from "../../firebase/functionsClient.js";
5
+ import { SiteSettingsSchema } from "../../schema/settings.js";
6
+ import { zodFoutTekst } from "./zodFout.js";
7
+ async function voegToeAanMenu(siteId, item, terugval = []) {
8
+ const db = getDb();
9
+ const app = getFirebaseApp();
10
+ if (!db || !app) throw new Error("Firebase is niet geconfigureerd.");
11
+ const snap = await getDoc(doc(db, "sites", siteId, "settings", "site"));
12
+ const huidig = snap.exists() ? snap.data() : {};
13
+ const nav = huidig.nav ?? terugval;
14
+ if (nav.some((n) => n.href === item.href)) return;
15
+ const parsed = SiteSettingsSchema.safeParse({ ...huidig, nav: [...nav, item] });
16
+ if (!parsed.success) throw new Error(zodFoutTekst(parsed.error.issues[0]));
17
+ await callable(app, "saveSiteSettings")({ siteId, settings: parsed.data });
18
+ }
19
+ function vrijPad(pad, bestaande) {
20
+ const bezet = new Set(bestaande);
21
+ if (!bezet.has(pad)) return pad;
22
+ const basis = pad === "/" ? "/home" : pad;
23
+ for (let n = 2; ; n++) if (!bezet.has(`${basis}-${n}`)) return `${basis}-${n}`;
24
+ }
25
+ async function voegRedirectToe(siteId, from, to) {
26
+ const db = getDb();
27
+ const app = getFirebaseApp();
28
+ if (!db || !app) throw new Error("Firebase is niet geconfigureerd.");
29
+ const snap = await getDoc(doc(db, "sites", siteId, "settings", "site"));
30
+ const huidig = snap.exists() ? snap.data() : {};
31
+ const redirects = [...(huidig.redirects ?? []).filter((r) => r.from !== from), { from, to }];
32
+ const parsed = SiteSettingsSchema.safeParse({ ...huidig, redirects });
33
+ if (!parsed.success) throw new Error(zodFoutTekst(parsed.error.issues[0]));
34
+ await callable(app, "saveSiteSettings")({ siteId, settings: parsed.data });
35
+ }
36
+ export {
37
+ voegRedirectToe,
38
+ voegToeAanMenu,
39
+ vrijPad
40
+ };
41
+ //# sourceMappingURL=menuHulp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/admin/lib/menuHulp.ts"],"sourcesContent":["\"use client\";\n\nimport { doc, getDoc } from \"firebase/firestore\";\nimport { getDb, getFirebaseApp } from \"../../firebase/client\";\nimport { callable } from \"../../firebase/functionsClient\";\nimport { SiteSettingsSchema, type NavItem, type SiteSettings } from \"../../schema/settings\";\nimport { zodFoutTekst } from \"./zodFout\";\n\n/**\n * Een pagina meteen in het hoofdmenu zetten (8.2). \"Nieuwe pagina\" eindigde eerder op een pagina\n * die nergens te vinden was: de klant moest zelf ontdekken dat er een apart Menu-paneel bestaat\n * (en dat alleen een siteadmin ziet). Zelfde route als het Menu-paneel: het hele instellingen-\n * document via de saveSiteSettings-callable.\n */\nexport async function voegToeAanMenu(siteId: string, item: NavItem, terugval: NavItem[] = []): Promise<void> {\n const db = getDb();\n const app = getFirebaseApp();\n if (!db || !app) throw new Error(\"Firebase is niet geconfigureerd.\");\n const snap = await getDoc(doc(db, \"sites\", siteId, \"settings\", \"site\"));\n const huidig = (snap.exists() ? snap.data() : {}) as SiteSettings;\n const nav = huidig.nav ?? terugval;\n if (nav.some((n) => n.href === item.href)) return;\n const parsed = SiteSettingsSchema.safeParse({ ...huidig, nav: [...nav, item] });\n if (!parsed.success) throw new Error(zodFoutTekst(parsed.error.issues[0]));\n await callable(app, \"saveSiteSettings\")({ siteId, settings: parsed.data });\n}\n\n/** Eerstvolgend vrij pad: `/over`, anders `/over-2`, `/over-3`, … */\nexport function vrijPad(pad: string, bestaande: string[]): string {\n const bezet = new Set(bestaande);\n if (!bezet.has(pad)) return pad;\n const basis = pad === \"/\" ? \"/home\" : pad;\n for (let n = 2; ; n++) if (!bezet.has(`${basis}-${n}`)) return `${basis}-${n}`;\n}\n\n/**\n * Doorverwijzing toevoegen (8.4, 404-monitor): oud adres → bestaande pagina. Zelfde route als het\n * paneel Doorverwijzingen (saveSiteSettings, siteadmin). Een bestaande regel voor hetzelfde adres\n * wordt vervangen, zodat er geen dubbele \"from\" ontstaat.\n */\nexport async function voegRedirectToe(siteId: string, from: string, to: string): Promise<void> {\n const db = getDb();\n const app = getFirebaseApp();\n if (!db || !app) throw new Error(\"Firebase is niet geconfigureerd.\");\n const snap = await getDoc(doc(db, \"sites\", siteId, \"settings\", \"site\"));\n const huidig = (snap.exists() ? snap.data() : {}) as SiteSettings;\n const redirects = [...(huidig.redirects ?? []).filter((r) => r.from !== from), { from, to }];\n const parsed = SiteSettingsSchema.safeParse({ ...huidig, redirects });\n if (!parsed.success) throw new Error(zodFoutTekst(parsed.error.issues[0]));\n await callable(app, \"saveSiteSettings\")({ siteId, settings: parsed.data });\n}\n"],"mappings":";AAEA,SAAS,KAAK,cAAc;AAC5B,SAAS,OAAO,sBAAsB;AACtC,SAAS,gBAAgB;AACzB,SAAS,0BAA2D;AACpE,SAAS,oBAAoB;AAQ7B,eAAsB,eAAe,QAAgB,MAAe,WAAsB,CAAC,GAAkB;AAC3G,QAAM,KAAK,MAAM;AACjB,QAAM,MAAM,eAAe;AAC3B,MAAI,CAAC,MAAM,CAAC,IAAK,OAAM,IAAI,MAAM,kCAAkC;AACnE,QAAM,OAAO,MAAM,OAAO,IAAI,IAAI,SAAS,QAAQ,YAAY,MAAM,CAAC;AACtE,QAAM,SAAU,KAAK,OAAO,IAAI,KAAK,KAAK,IAAI,CAAC;AAC/C,QAAM,MAAM,OAAO,OAAO;AAC1B,MAAI,IAAI,KAAK,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,EAAG;AAC3C,QAAM,SAAS,mBAAmB,UAAU,EAAE,GAAG,QAAQ,KAAK,CAAC,GAAG,KAAK,IAAI,EAAE,CAAC;AAC9E,MAAI,CAAC,OAAO,QAAS,OAAM,IAAI,MAAM,aAAa,OAAO,MAAM,OAAO,CAAC,CAAC,CAAC;AACzE,QAAM,SAAS,KAAK,kBAAkB,EAAE,EAAE,QAAQ,UAAU,OAAO,KAAK,CAAC;AAC3E;AAGO,SAAS,QAAQ,KAAa,WAA6B;AAChE,QAAM,QAAQ,IAAI,IAAI,SAAS;AAC/B,MAAI,CAAC,MAAM,IAAI,GAAG,EAAG,QAAO;AAC5B,QAAM,QAAQ,QAAQ,MAAM,UAAU;AACtC,WAAS,IAAI,KAAK,IAAK,KAAI,CAAC,MAAM,IAAI,GAAG,KAAK,IAAI,CAAC,EAAE,EAAG,QAAO,GAAG,KAAK,IAAI,CAAC;AAC9E;AAOA,eAAsB,gBAAgB,QAAgB,MAAc,IAA2B;AAC7F,QAAM,KAAK,MAAM;AACjB,QAAM,MAAM,eAAe;AAC3B,MAAI,CAAC,MAAM,CAAC,IAAK,OAAM,IAAI,MAAM,kCAAkC;AACnE,QAAM,OAAO,MAAM,OAAO,IAAI,IAAI,SAAS,QAAQ,YAAY,MAAM,CAAC;AACtE,QAAM,SAAU,KAAK,OAAO,IAAI,KAAK,KAAK,IAAI,CAAC;AAC/C,QAAM,YAAY,CAAC,IAAI,OAAO,aAAa,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,SAAS,IAAI,GAAG,EAAE,MAAM,GAAG,CAAC;AAC3F,QAAM,SAAS,mBAAmB,UAAU,EAAE,GAAG,QAAQ,UAAU,CAAC;AACpE,MAAI,CAAC,OAAO,QAAS,OAAM,IAAI,MAAM,aAAa,OAAO,MAAM,OAAO,CAAC,CAAC,CAAC;AACzE,QAAM,SAAS,KAAK,kBAAkB,EAAE,EAAE,QAAQ,UAAU,OAAO,KAAK,CAAC;AAC3E;","names":[]}
@@ -0,0 +1,10 @@
1
+ export declare function useOnopgeslagen(isVuil: boolean): void;
2
+ export declare function heeftOnopgeslagen(): boolean;
3
+ /** Na "weggooien" bevestigd: niemand meer vuil (de panelen ontkoppelen toch). */
4
+ export declare function vergeetOnopgeslagen(): void;
5
+ export declare const ONOPGESLAGEN_VRAAG: {
6
+ title: string;
7
+ description: string;
8
+ confirmLabel: string;
9
+ tone: "danger";
10
+ };
@@ -0,0 +1,35 @@
1
+ "use client";
2
+ import { useEffect, useId } from "react";
3
+ const vuil = /* @__PURE__ */ new Set();
4
+ function useOnopgeslagen(isVuil) {
5
+ const id = useId();
6
+ useEffect(() => {
7
+ if (!isVuil) return;
8
+ vuil.add(id);
9
+ const waarschuw = (e) => e.preventDefault();
10
+ window.addEventListener("beforeunload", waarschuw);
11
+ return () => {
12
+ vuil.delete(id);
13
+ window.removeEventListener("beforeunload", waarschuw);
14
+ };
15
+ }, [id, isVuil]);
16
+ }
17
+ function heeftOnopgeslagen() {
18
+ return vuil.size > 0;
19
+ }
20
+ function vergeetOnopgeslagen() {
21
+ vuil.clear();
22
+ }
23
+ const ONOPGESLAGEN_VRAAG = {
24
+ title: "Wijzigingen weggooien?",
25
+ description: "Je hebt iets aangepast maar nog niet opgeslagen. Als je doorgaat, ben je die wijzigingen kwijt.",
26
+ confirmLabel: "Weggooien en doorgaan",
27
+ tone: "danger"
28
+ };
29
+ export {
30
+ ONOPGESLAGEN_VRAAG,
31
+ heeftOnopgeslagen,
32
+ useOnopgeslagen,
33
+ vergeetOnopgeslagen
34
+ };
35
+ //# sourceMappingURL=onopgeslagen.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/admin/lib/onopgeslagen.ts"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useId } from \"react\";\n\n/**\n * Onopgeslagen werk in /beheer (8.2). Tabbladen (Radix) ontkoppelen een paneel bij het wisselen;\n * wie halverwege een product, nieuwsartikel, formulier of de instellingen op een ander tabblad\n * klikte, was zijn wijzigingen zonder melding kwijt. Een paneel meldt hier \"ik heb onopgeslagen\n * werk\"; `AdminApp` (en lijsten die tussen items wisselen) vragen dan eerst bevestiging. Bewust\n * module-state i.p.v. context: er is één beheer per pagina, en zo hoeft niemand een provider te\n * kennen.\n */\nconst vuil = new Set<string>();\n\nexport function useOnopgeslagen(isVuil: boolean): void {\n const id = useId();\n useEffect(() => {\n if (!isVuil) return;\n vuil.add(id);\n // Tabblad sluiten of herladen met onopgeslagen werk: de browser vraagt het dan zelf.\n const waarschuw = (e: BeforeUnloadEvent) => e.preventDefault();\n window.addEventListener(\"beforeunload\", waarschuw);\n return () => {\n vuil.delete(id);\n window.removeEventListener(\"beforeunload\", waarschuw);\n };\n }, [id, isVuil]);\n}\n\nexport function heeftOnopgeslagen(): boolean {\n return vuil.size > 0;\n}\n\n/** Na \"weggooien\" bevestigd: niemand meer vuil (de panelen ontkoppelen toch). */\nexport function vergeetOnopgeslagen(): void {\n vuil.clear();\n}\n\nexport const ONOPGESLAGEN_VRAAG = {\n title: \"Wijzigingen weggooien?\",\n description: \"Je hebt iets aangepast maar nog niet opgeslagen. Als je doorgaat, ben je die wijzigingen kwijt.\",\n confirmLabel: \"Weggooien en doorgaan\",\n tone: \"danger\" as const,\n};\n"],"mappings":";AAEA,SAAS,WAAW,aAAa;AAUjC,MAAM,OAAO,oBAAI,IAAY;AAEtB,SAAS,gBAAgB,QAAuB;AACrD,QAAM,KAAK,MAAM;AACjB,YAAU,MAAM;AACd,QAAI,CAAC,OAAQ;AACb,SAAK,IAAI,EAAE;AAEX,UAAM,YAAY,CAAC,MAAyB,EAAE,eAAe;AAC7D,WAAO,iBAAiB,gBAAgB,SAAS;AACjD,WAAO,MAAM;AACX,WAAK,OAAO,EAAE;AACd,aAAO,oBAAoB,gBAAgB,SAAS;AAAA,IACtD;AAAA,EACF,GAAG,CAAC,IAAI,MAAM,CAAC;AACjB;AAEO,SAAS,oBAA6B;AAC3C,SAAO,KAAK,OAAO;AACrB;AAGO,SAAS,sBAA4B;AAC1C,OAAK,MAAM;AACb;AAEO,MAAM,qBAAqB;AAAA,EAChC,OAAO;AAAA,EACP,aAAa;AAAA,EACb,cAAc;AAAA,EACd,MAAM;AACR;","names":[]}
@@ -8,6 +8,11 @@ export type DraftPage = Page & {
8
8
  /** 7.24: in de prullenbak; wordt na 30 dagen definitief verwijderd (purgeTrashedPages). */
9
9
  trashed?: boolean;
10
10
  trashedAt?: unknown;
11
+ /** 8.4: ingepland publiceren / offline halen (Firestore-Timestamp); de planner wist ze na uitvoering. */
12
+ publishAt?: unknown;
13
+ unpublishAt?: unknown;
14
+ /** 8.4: waarom een ingeplande actie mislukte (gezet door de planner). */
15
+ planningFout?: string;
11
16
  };
12
17
  /**
13
18
  * Staat er op deze pagina iets dat nog niet live is? (7.24)
@@ -21,7 +26,7 @@ export declare function heeftOngepubliceerdeWijzigingen(p: Pick<DraftPage, "publ
21
26
  /** Leesbare toestand voor badges: live / gewijzigd / concept / prullenbak. */
22
27
  export declare function paginaToestand(p: DraftPage): "prullenbak" | "gewijzigd" | "live" | "concept";
23
28
  /** Live-lijst van pagina's (werkversies in `drafts/`). */
24
- export declare function subscribePages(siteId: string, cb: (pages: DraftPage[]) => void): () => void;
29
+ export declare function subscribePages(siteId: string, cb: (pages: DraftPage[]) => void, onError?: (e: Error) => void): () => void;
25
30
  export declare function subscribeVersions(siteId: string, pageId: string, cb: (versions: {
26
31
  id: string;
27
32
  createdAt?: unknown;
@@ -51,6 +56,28 @@ export declare function getPublishedPage(siteId: string, pageId: string): Promis
51
56
  export declare function subscribeDraft(siteId: string, pageId: string, cb: (draft: DraftPage | null, meta: {
52
57
  fromServer: boolean;
53
58
  }) => void): () => void;
59
+ /**
60
+ * Firestore WEIGERT `undefined` als veldwaarde: `setDoc({ seo: undefined })` gooit
61
+ * "Unsupported field value: undefined" en de hele schrijfactie mislukt. De editors geven hun
62
+ * pagina-state als geheel door (`{ title, slug, path, seo, blocks }`), en bij een nieuwe pagina is
63
+ * `seo` gewoon nog niet ingevuld — dus liep élke eerste publicatie van een nieuwe pagina stuk op
64
+ * de autosave die eraan voorafgaat. Gevonden op de staging-deploy van groenhof: "Publiceren
65
+ * mislukt — Unsupported field value: undefined (found in field seo)".
66
+ *
67
+ * Weglaten is hier ook de JUISTE semantiek: deze writes zijn `merge: true`, en "veld niet
68
+ * meegestuurd" betekent daar "laat staan wat er stond". Een veld écht wissen doe je met
69
+ * `deleteField()`, niet met `undefined` — dat onderscheid blijft zo intact.
70
+ */
71
+ export declare function zonderUndefined<T extends Record<string, unknown>>(o: T): Partial<T>;
72
+ /**
73
+ * Voor `setDoc(…, { merge: true })` (8.1). Bovenaan blijft "undefined = niet meesturen = laten
74
+ * staan". Maar ín een object (bv. `seo`) betekent `undefined` dat de gebruiker het veld leegmaakte:
75
+ * dat wordt `deleteField()`. Tot 8.1 gaf "niet in Google" uitvinken of de deelafbeelding weghalen
76
+ * een geneste `undefined` — Firestore weigert die en de hele autosave mislukte. En gewoon weglaten
77
+ * had het oude veld laten staan, want merge werkt per veld door in objecten.
78
+ * Binnen arrays kan `deleteField()` niet; daar wordt `undefined` gewoon weggelaten.
79
+ */
80
+ export declare function voorMerge<T extends Record<string, unknown>>(o: T, maakWis?: () => unknown): Record<string, unknown>;
54
81
  export declare function createPage(siteId: string, input: Partial<Page> & {
55
82
  title: string;
56
83
  slug: string;
@@ -61,7 +88,31 @@ export declare function saveDraft(siteId: string, pageId: string, partial: Parti
61
88
  * Pagina dupliceren (7.24): nieuwe draft met dezelfde blokken (verse `_key`s), als concept, met
62
89
  * "-kopie" achter pad en titel zodat hij nergens mee botst.
63
90
  */
64
- export declare function duplicatePage(siteId: string, bron: DraftPage): Promise<string>;
91
+ export declare function duplicatePage(siteId: string, bron: DraftPage, bestaandePaden?: string[]): Promise<string>;
92
+ /** Eerstvolgend vrij kopie-pad: `/over-kopie`, dan `/over-kopie-2`, `/over-kopie-3`, … */
93
+ export declare function vrijKopiePad(pad: string, bestaande: string[]): string;
94
+ /**
95
+ * Wat de publicatiecontrole moet weten (8.4): de paden die bestaan (werkversies, niet in de
96
+ * prullenbak) en de doorverwijzingen. Leesfouten = geen linkcontrole (liever geen waarschuwing dan
97
+ * een valse).
98
+ */
99
+ export declare function publicatieContext(siteId: string): Promise<{
100
+ bekendePaden?: string[];
101
+ livePaden?: string[];
102
+ redirects?: {
103
+ from: string;
104
+ }[];
105
+ }>;
106
+ /** Tijdstip uit een Firestore-Timestamp (of ms), voor de planning-UI. */
107
+ export declare function planningDatum(v: unknown): Date | null;
108
+ /**
109
+ * Planning zetten of wissen (8.4). `null` = wissen. De planner in de functions (`geplandePaginas`,
110
+ * elke 10 minuten) voert hem uit via dezelfde weg als de knoppen.
111
+ */
112
+ export declare function zetPlanning(siteId: string, pageId: string, plan: {
113
+ publishAt: Date | null;
114
+ unpublishAt: Date | null;
115
+ }): Promise<void>;
65
116
  /** Offline halen (7.24): de live versie verdwijnt, de werkversie blijft. Via de callable. */
66
117
  export declare function unpublishPage(siteId: string, pageId: string): Promise<void>;
67
118
  /**
@@ -73,6 +124,8 @@ export declare function trashPage(siteId: string, page: DraftPage): Promise<void
73
124
  /** Terug uit de prullenbak: als concept (publiceren is een bewuste tweede stap). */
74
125
  export declare function restorePage(siteId: string, pageId: string): Promise<void>;
75
126
  export declare function deletePage(siteId: string, pageId: string): Promise<void>;
127
+ /** De inhoud van één opgeslagen versie (8.4: om te vergelijken vóór herstellen). */
128
+ export declare function getVersieSnapshot(siteId: string, pageId: string, versionId: string): Promise<Partial<Page> | null>;
76
129
  /**
77
130
  * Herstelt een eerdere versie naar de WERKVERSIE (draft). De gebruiker moet daarna zelf
78
131
  * publiceren — zo is herstellen veilig en omkeerbaar (de live site verandert pas bij Publiceren).