includio-cms 0.25.0 → 0.26.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 (349) hide show
  1. package/API.md +57 -4
  2. package/CHANGELOG.md +53 -0
  3. package/DOCS.md +1 -1
  4. package/README.md +2 -0
  5. package/ROADMAP.md +6 -0
  6. package/dist/admin/client/account/lang.d.ts +1 -0
  7. package/dist/admin/client/account/lang.js +4 -2
  8. package/dist/admin/client/account/profile-section.svelte +2 -2
  9. package/dist/admin/client/account/security-section.svelte +27 -4
  10. package/dist/admin/client/account/sessions-section.svelte +1 -1
  11. package/dist/admin/client/admin/admin-after-login-layout-content.svelte +1 -1
  12. package/dist/admin/client/admin/dashboard-page.svelte +34 -10
  13. package/dist/admin/client/collection/bulk-actions-bar.svelte +86 -44
  14. package/dist/admin/client/collection/bulk-actions-bar.svelte.d.ts +3 -1
  15. package/dist/admin/client/collection/collection-entries.svelte +52 -36
  16. package/dist/admin/client/collection/collection-entries.svelte.d.ts +3 -0
  17. package/dist/admin/client/collection/collection.svelte +28 -14
  18. package/dist/admin/client/collection/collection.svelte.d.ts +3 -0
  19. package/dist/admin/client/collection/data-table.svelte +279 -130
  20. package/dist/admin/client/collection/data-table.svelte.d.ts +11 -0
  21. package/dist/admin/client/collection/date-cell.svelte +4 -4
  22. package/dist/admin/client/collection/row-actions.svelte +2 -1
  23. package/dist/admin/client/collection/sortable-header.svelte +33 -9
  24. package/dist/admin/client/collection/state-display.svelte +102 -0
  25. package/dist/admin/client/collection/state-display.svelte.d.ts +12 -0
  26. package/dist/admin/client/collection/status-badge.svelte +99 -11
  27. package/dist/admin/client/collection/status-badge.svelte.d.ts +15 -1
  28. package/dist/admin/client/collection/table-pagination.svelte +21 -6
  29. package/dist/admin/client/collection/table-toolbar.svelte +105 -80
  30. package/dist/admin/client/collection/table-toolbar.svelte.d.ts +11 -8
  31. package/dist/admin/client/entry/entry-form.svelte +36 -11
  32. package/dist/admin/client/entry/entry-form.svelte.d.ts +1 -0
  33. package/dist/admin/client/entry/entry-header.svelte +22 -15
  34. package/dist/admin/client/entry/entry-header.svelte.d.ts +1 -0
  35. package/dist/admin/client/entry/entry.svelte +269 -165
  36. package/dist/admin/client/entry/header/a11y-header-badge.svelte +47 -0
  37. package/dist/admin/client/entry/header/a11y-header-badge.svelte.d.ts +8 -0
  38. package/dist/admin/client/entry/header/publish-panel.svelte +69 -13
  39. package/dist/admin/client/entry/header/save-indicator.svelte +57 -28
  40. package/dist/admin/client/entry/header/save-indicator.svelte.d.ts +1 -0
  41. package/dist/admin/client/entry/header/status-badge.svelte +60 -15
  42. package/dist/admin/client/entry/header/status-badge.svelte.d.ts +1 -2
  43. package/dist/admin/client/entry/header/version-history-sheet.svelte +1 -1
  44. package/dist/admin/client/entry/hybrid/hybrid-layout.svelte +74 -23
  45. package/dist/admin/client/entry/hybrid/hybrid-preview.svelte +1 -1
  46. package/dist/admin/client/entry/utils.d.ts +14 -0
  47. package/dist/admin/client/entry/utils.js +28 -0
  48. package/dist/admin/client/form/form-submission/form-submission.svelte +2 -2
  49. package/dist/admin/client/form/form-submissions.svelte +143 -194
  50. package/dist/admin/client/form/form-submissions.svelte.d.ts +2 -0
  51. package/dist/admin/client/login/lang.d.ts +3 -0
  52. package/dist/admin/client/login/lang.js +10 -4
  53. package/dist/admin/client/login/login-form.svelte +8 -1
  54. package/dist/admin/client/login/reset-password-page.svelte +24 -3
  55. package/dist/admin/client/login/schema.d.ts +14 -2
  56. package/dist/admin/client/login/schema.js +19 -8
  57. package/dist/admin/client/maintenance/maintenance-page.svelte +16 -17
  58. package/dist/admin/client/media/media-page.svelte +1 -1
  59. package/dist/admin/client/shop/coupon-edit-page.svelte +117 -13
  60. package/dist/admin/client/shop/coupon-form.svelte +282 -138
  61. package/dist/admin/client/shop/coupon-form.svelte.d.ts +1 -9
  62. package/dist/admin/client/shop/coupon-new-page.svelte +40 -10
  63. package/dist/admin/client/shop/coupon-new-page.svelte.d.ts +2 -17
  64. package/dist/admin/client/shop/coupon-schema.d.ts +28 -0
  65. package/dist/admin/client/shop/coupon-schema.js +53 -0
  66. package/dist/admin/client/shop/coupons-list-page.svelte +262 -118
  67. package/dist/admin/client/shop/coupons-list-page.svelte.d.ts +16 -1
  68. package/dist/admin/client/shop/shipping-method-edit-page.svelte +108 -59
  69. package/dist/admin/client/shop/shipping-method-form.svelte +36 -9
  70. package/dist/admin/client/shop/shipping-method-new-page.svelte +44 -13
  71. package/dist/admin/client/shop/shipping-methods-list-page.svelte +101 -59
  72. package/dist/admin/client/shop/shop-order-detail-page.svelte +113 -84
  73. package/dist/admin/client/shop/shop-orders-list-page.svelte +302 -152
  74. package/dist/admin/client/shop/shop-orders-list-page.svelte.d.ts +18 -1
  75. package/dist/admin/client/shop/shop-products-list-page.svelte +355 -118
  76. package/dist/admin/client/shop/shop-products-list-page.svelte.d.ts +19 -1
  77. package/dist/admin/client/users/accept-invite-page.svelte +24 -3
  78. package/dist/admin/client/users/create-user-dialog.svelte +3 -8
  79. package/dist/admin/client/users/lang.d.ts +2 -0
  80. package/dist/admin/client/users/lang.js +4 -0
  81. package/dist/admin/client/users/pending-invitations.svelte +2 -9
  82. package/dist/admin/client/users/user-name-cell.svelte +20 -0
  83. package/dist/admin/client/users/user-name-cell.svelte.d.ts +9 -0
  84. package/dist/admin/client/users/user-role-badge.svelte +16 -0
  85. package/dist/admin/client/users/user-role-badge.svelte.d.ts +7 -0
  86. package/dist/admin/client/users/user-row-actions.svelte +72 -0
  87. package/dist/admin/client/users/user-row-actions.svelte.d.ts +20 -0
  88. package/dist/admin/client/users/user-sessions-sheet.svelte +2 -11
  89. package/dist/admin/client/users/users-page.svelte +283 -497
  90. package/dist/admin/client/users/users-page.svelte.d.ts +12 -1
  91. package/dist/admin/components/dashboard/form-submissions-widget.svelte +59 -74
  92. package/dist/admin/components/dashboard/recent-activity.svelte +17 -5
  93. package/dist/admin/components/dashboard/recent-entries.svelte +19 -7
  94. package/dist/admin/components/dialogs/confirmation-dialog.svelte +105 -0
  95. package/dist/admin/components/dialogs/confirmation-dialog.svelte.d.ts +13 -0
  96. package/dist/admin/components/fields/block-picker-modal.svelte +6 -0
  97. package/dist/admin/components/fields/blocks-field.svelte +46 -1
  98. package/dist/admin/components/fields/boolean-field.svelte +1 -1
  99. package/dist/admin/components/fields/field-renderer.svelte +23 -21
  100. package/dist/admin/components/fields/file-field.svelte +344 -30
  101. package/dist/admin/components/fields/media-field.svelte +16 -2
  102. package/dist/admin/components/fields/radio-field.svelte +22 -0
  103. package/dist/admin/components/fields/relation-field.svelte +123 -97
  104. package/dist/admin/components/fields/relation-picker-dialog.svelte +2 -2
  105. package/dist/admin/components/fields/seo-field.svelte +60 -30
  106. package/dist/admin/components/fields/shop-field.svelte +9 -4
  107. package/dist/admin/components/fields/simple-array-field.svelte +321 -151
  108. package/dist/admin/components/fields/simple-array-field.svelte.d.ts +3 -0
  109. package/dist/admin/components/fields/slug-field.svelte +146 -21
  110. package/dist/admin/components/fields/text-field-wrapper.svelte +37 -20
  111. package/dist/admin/components/fields/text-field.svelte +7 -2
  112. package/dist/admin/components/fields/url-field-wrapper.svelte +10 -0
  113. package/dist/admin/components/fields/url-field.svelte +36 -23
  114. package/dist/admin/components/forms/form-error-summary.svelte +143 -0
  115. package/dist/admin/components/forms/form-error-summary.svelte.d.ts +27 -0
  116. package/dist/admin/components/layout/app-sidebar.svelte +7 -2
  117. package/dist/admin/components/layout/detail-page-shell.svelte +71 -0
  118. package/dist/admin/components/layout/detail-page-shell.svelte.d.ts +24 -0
  119. package/dist/admin/components/layout/lang.d.ts +5 -0
  120. package/dist/admin/components/layout/lang.js +10 -0
  121. package/dist/admin/components/layout/layout-renderer.svelte +71 -2
  122. package/dist/admin/components/layout/layout-renderer.svelte.d.ts +1 -0
  123. package/dist/admin/components/layout/layout-tabs.svelte +172 -0
  124. package/dist/admin/components/layout/layout-tabs.svelte.d.ts +24 -0
  125. package/dist/admin/components/layout/nav-breadcrumbs.svelte +25 -7
  126. package/dist/admin/components/layout/nav-collections.svelte +23 -36
  127. package/dist/admin/components/layout/nav-forms.svelte +19 -35
  128. package/dist/admin/components/layout/nav-main.svelte +3 -28
  129. package/dist/admin/components/layout/nav-search.svelte +70 -2
  130. package/dist/admin/components/layout/nav-section.svelte +77 -0
  131. package/dist/admin/components/layout/nav-section.svelte.d.ts +22 -0
  132. package/dist/admin/components/layout/nav-shop.svelte +3 -27
  133. package/dist/admin/components/layout/nav-singletons.svelte +16 -28
  134. package/dist/admin/components/layout/page-header.stories.svelte +93 -0
  135. package/dist/admin/components/layout/page-header.stories.svelte.d.ts +27 -0
  136. package/dist/admin/components/layout/page-header.svelte +68 -0
  137. package/dist/admin/components/layout/page-header.svelte.d.ts +17 -0
  138. package/dist/admin/components/layout/site-header.svelte +9 -0
  139. package/dist/admin/components/layout/site-header.svelte.d.ts +2 -17
  140. package/dist/admin/components/media/file/file-name-input.svelte +6 -2
  141. package/dist/admin/components/media/file/file-preview.svelte +130 -17
  142. package/dist/admin/components/media/file-upload.svelte +16 -7
  143. package/dist/admin/components/media/file-upload.svelte.d.ts +1 -0
  144. package/dist/admin/components/media/files-list.svelte +153 -53
  145. package/dist/admin/components/media/files-list.svelte.d.ts +1 -0
  146. package/dist/admin/components/media/media-library.svelte +577 -198
  147. package/dist/admin/components/media/media-library.svelte.d.ts +4 -0
  148. package/dist/admin/components/media/media-selector.svelte +4 -2
  149. package/dist/admin/components/media/media-selector.svelte.d.ts +1 -0
  150. package/dist/admin/components/media/tag-sidebar.svelte +4 -4
  151. package/dist/admin/components/tiptap/FigureNodeView.svelte +10 -0
  152. package/dist/admin/components/tiptap/bubble-menu.svelte +104 -0
  153. package/dist/admin/components/tiptap/bubble-menu.svelte.d.ts +19 -0
  154. package/dist/admin/components/tiptap/content-editor.svelte +28 -24
  155. package/dist/admin/components/tiptap/editor-toolbar.svelte +7 -7
  156. package/dist/admin/components/tiptap/extensions.js +5 -1
  157. package/dist/admin/components/tiptap/image-dialog.svelte +5 -1
  158. package/dist/admin/components/tiptap/link-dialog.svelte +2 -0
  159. package/dist/admin/components/tiptap/tiptap-editor.svelte +18 -20
  160. package/dist/admin/components/tiptap/video-dialog.svelte +1 -1
  161. package/dist/admin/i18n/errors.d.ts +140 -0
  162. package/dist/admin/i18n/errors.js +151 -0
  163. package/dist/admin/remote/entry.remote.d.ts +59 -4
  164. package/dist/admin/remote/entry.remote.js +239 -62
  165. package/dist/admin/remote/shop.remote.d.ts +37 -32
  166. package/dist/admin/remote/shop.remote.js +9 -2
  167. package/dist/admin/shared/password-generate.d.ts +6 -0
  168. package/dist/admin/shared/password-generate.js +40 -0
  169. package/dist/admin/shared/password-schema.d.ts +6 -0
  170. package/dist/admin/shared/password-schema.js +10 -3
  171. package/dist/admin/styles/admin.css +23 -6
  172. package/dist/admin/styles/tokens.md +244 -0
  173. package/dist/admin/utils/accordionActivation.d.ts +13 -0
  174. package/dist/admin/utils/accordionActivation.js +35 -0
  175. package/dist/admin/utils/entryLabel.d.ts +23 -0
  176. package/dist/admin/utils/entryLabel.js +51 -12
  177. package/dist/admin/utils/field-a11y.d.ts +29 -0
  178. package/dist/admin/utils/field-a11y.js +23 -0
  179. package/dist/admin/utils/fieldPathElement.d.ts +9 -0
  180. package/dist/admin/utils/fieldPathElement.js +18 -0
  181. package/dist/admin/utils/fileDisplay.d.ts +10 -0
  182. package/dist/admin/utils/fileDisplay.js +26 -0
  183. package/dist/admin/utils/flattenFormErrors.d.ts +19 -0
  184. package/dist/admin/utils/flattenFormErrors.js +102 -0
  185. package/dist/admin/utils/formatters.d.ts +12 -0
  186. package/dist/admin/utils/{formatDate.js → formatters.js} +23 -2
  187. package/dist/admin/utils/scrollWithin.d.ts +9 -0
  188. package/dist/admin/utils/scrollWithin.js +32 -0
  189. package/dist/admin/utils/tabActivation.d.ts +12 -0
  190. package/dist/admin/utils/tabActivation.js +24 -0
  191. package/dist/cms/runtime/schema.d.ts +1 -0
  192. package/dist/cms/runtime/schema.js +1 -0
  193. package/dist/cms/runtime/types.d.ts +80 -7
  194. package/dist/components/ui/accordion/accordion-content.svelte +17 -3
  195. package/dist/components/ui/accordion/accordion.stories.svelte +21 -1
  196. package/dist/components/ui/alert/alert.stories.svelte +14 -0
  197. package/dist/components/ui/alert-dialog/alert-dialog.stories.svelte +45 -0
  198. package/dist/components/ui/alert-dialog/alert-dialog.stories.svelte.d.ts +27 -0
  199. package/dist/components/ui/avatar/avatar.stories.svelte +27 -0
  200. package/dist/components/ui/badge/badge.stories.svelte +15 -0
  201. package/dist/components/ui/breadcrumb/breadcrumb.stories.svelte +47 -0
  202. package/dist/components/ui/breadcrumb/breadcrumb.svelte +1 -1
  203. package/dist/components/ui/button/button.stories.svelte +53 -6
  204. package/dist/components/ui/button/button.svelte +39 -5
  205. package/dist/components/ui/button/button.svelte.d.ts +4 -0
  206. package/dist/components/ui/button-group/button-group.stories.svelte +44 -0
  207. package/dist/components/ui/button-group/button-group.stories.svelte.d.ts +27 -0
  208. package/dist/components/ui/calendar/calendar.stories.svelte +36 -0
  209. package/dist/components/ui/calendar/calendar.stories.svelte.d.ts +27 -0
  210. package/dist/components/ui/card/card.stories.svelte +7 -0
  211. package/dist/components/ui/carousel/carousel.stories.svelte +43 -0
  212. package/dist/components/ui/carousel/carousel.stories.svelte.d.ts +27 -0
  213. package/dist/components/ui/checkbox/checkbox.stories.svelte +67 -0
  214. package/dist/components/ui/checkbox/checkbox.stories.svelte.d.ts +27 -0
  215. package/dist/components/ui/checkbox/checkbox.svelte +3 -3
  216. package/dist/components/ui/command/command.stories.svelte +18 -0
  217. package/dist/components/ui/data-table/data-table.stories.svelte +61 -0
  218. package/dist/components/ui/data-table/data-table.stories.svelte.d.ts +18 -0
  219. package/dist/components/ui/dialog/dialog-content.svelte +5 -0
  220. package/dist/components/ui/dialog/dialog-content.svelte.d.ts +2 -0
  221. package/dist/components/ui/dialog/dialog.stories.svelte +35 -0
  222. package/dist/components/ui/dropdown-menu/dropdown-menu.stories.svelte +74 -0
  223. package/dist/components/ui/dropdown-menu/dropdown-menu.stories.svelte.d.ts +27 -0
  224. package/dist/components/ui/field/field-context.svelte.d.ts +22 -0
  225. package/dist/components/ui/field/field-context.svelte.js +9 -0
  226. package/dist/components/ui/field/field-control.svelte +18 -0
  227. package/dist/components/ui/field/field-control.svelte.d.ts +8 -0
  228. package/dist/components/ui/field/field-description.svelte +12 -0
  229. package/dist/components/ui/field/field-error.svelte +14 -6
  230. package/dist/components/ui/field/field-label.svelte +10 -0
  231. package/dist/components/ui/field/field.stories.svelte +95 -9
  232. package/dist/components/ui/field/field.svelte +57 -0
  233. package/dist/components/ui/field/field.svelte.d.ts +2 -0
  234. package/dist/components/ui/field/index.d.ts +3 -1
  235. package/dist/components/ui/field/index.js +4 -2
  236. package/dist/components/ui/form/form-field-errors.svelte +1 -1
  237. package/dist/components/ui/form/form.stories.svelte +25 -0
  238. package/dist/components/ui/form/form.stories.svelte.d.ts +26 -0
  239. package/dist/components/ui/input/input.stories.svelte +26 -0
  240. package/dist/components/ui/input-group/input-group-input.svelte.d.ts +1 -1
  241. package/dist/components/ui/input-group/input-group.stories.svelte +43 -0
  242. package/dist/components/ui/input-group/input-group.stories.svelte.d.ts +27 -0
  243. package/dist/components/ui/item/item.stories.svelte +61 -0
  244. package/dist/components/ui/item/item.stories.svelte.d.ts +27 -0
  245. package/dist/components/ui/label/label.stories.svelte +7 -0
  246. package/dist/components/ui/live-region/index.d.ts +1 -0
  247. package/dist/components/ui/live-region/index.js +1 -0
  248. package/dist/components/ui/live-region/live-region-demo.svelte +32 -0
  249. package/dist/components/ui/live-region/live-region-demo.svelte.d.ts +7 -0
  250. package/dist/components/ui/live-region/live-region.stories.svelte +23 -0
  251. package/dist/components/ui/live-region/live-region.stories.svelte.d.ts +26 -0
  252. package/dist/components/ui/live-region/live-region.svelte +12 -0
  253. package/dist/components/ui/live-region/live-region.svelte.d.ts +8 -0
  254. package/dist/components/ui/popover/popover.stories.svelte +34 -0
  255. package/dist/components/ui/radio-group/radio-group.stories.svelte +58 -0
  256. package/dist/components/ui/radio-group/radio-group.stories.svelte.d.ts +27 -0
  257. package/dist/components/ui/resizable/resizable.stories.svelte +56 -0
  258. package/dist/components/ui/resizable/resizable.stories.svelte.d.ts +27 -0
  259. package/dist/components/ui/select/select.stories.svelte +49 -0
  260. package/dist/components/ui/separator/separator.stories.svelte +18 -0
  261. package/dist/components/ui/sheet/sheet.stories.svelte +34 -0
  262. package/dist/components/ui/sidebar/sidebar-input.svelte.d.ts +1 -1
  263. package/dist/components/ui/sidebar/sidebar-menu-button.svelte +1 -0
  264. package/dist/components/ui/sidebar/sidebar-trigger.svelte +1 -1
  265. package/dist/components/ui/sidebar/sidebar.stories.svelte +72 -0
  266. package/dist/components/ui/sidebar/sidebar.stories.svelte.d.ts +27 -0
  267. package/dist/components/ui/skeleton/skeleton.stories.svelte +39 -0
  268. package/dist/components/ui/skeleton/skeleton.stories.svelte.d.ts +27 -0
  269. package/dist/components/ui/skeleton/skeleton.svelte +6 -0
  270. package/dist/components/ui/sonner/index.d.ts +1 -1
  271. package/dist/components/ui/sonner/index.js +1 -1
  272. package/dist/components/ui/sonner/sonner.stories.svelte +7 -0
  273. package/dist/components/ui/sonner/sonner.svelte +17 -1
  274. package/dist/components/ui/sonner/sonner.svelte.d.ts +6 -0
  275. package/dist/components/ui/spinner/spinner.stories.svelte +30 -0
  276. package/dist/components/ui/spinner/spinner.stories.svelte.d.ts +27 -0
  277. package/dist/components/ui/switch/switch.stories.svelte +56 -0
  278. package/dist/components/ui/switch/switch.stories.svelte.d.ts +27 -0
  279. package/dist/components/ui/table/table-cell.svelte +1 -1
  280. package/dist/components/ui/table/table-head.svelte +1 -1
  281. package/dist/components/ui/table/table.stories.svelte +68 -0
  282. package/dist/components/ui/table/table.stories.svelte.d.ts +27 -0
  283. package/dist/components/ui/table/table.svelte +1 -1
  284. package/dist/components/ui/tabs/tabs.stories.svelte +48 -0
  285. package/dist/components/ui/tabs/tabs.stories.svelte.d.ts +27 -0
  286. package/dist/components/ui/textarea/textarea.stories.svelte +21 -0
  287. package/dist/components/ui/toggle/toggle.stories.svelte +23 -0
  288. package/dist/components/ui/toggle-group/toggle-group.stories.svelte +43 -0
  289. package/dist/components/ui/tooltip/tooltip.stories.svelte +46 -6
  290. package/dist/core/fields/fieldSchemaToTs.d.ts +7 -0
  291. package/dist/core/fields/fieldSchemaToTs.js +234 -90
  292. package/dist/core/fields/layoutUtils.d.ts +4 -1
  293. package/dist/core/fields/layoutUtils.js +41 -4
  294. package/dist/core/fields/resolveSeo.d.ts +70 -0
  295. package/dist/core/fields/resolveSeo.js +88 -0
  296. package/dist/core/fields/seoFieldDescriptor.d.ts +43 -0
  297. package/dist/core/fields/seoFieldDescriptor.js +74 -0
  298. package/dist/core/fields/slugPath.d.ts +13 -0
  299. package/dist/core/fields/slugPath.js +32 -0
  300. package/dist/core/fields/urlUtils.d.ts +8 -0
  301. package/dist/core/fields/urlUtils.js +27 -0
  302. package/dist/core/index.d.ts +1 -0
  303. package/dist/core/index.js +1 -0
  304. package/dist/core/server/entries/operations/create.js +13 -0
  305. package/dist/core/server/entries/operations/get.d.ts +7 -0
  306. package/dist/core/server/entries/operations/get.js +10 -6
  307. package/dist/core/server/entries/operations/slugUniqueness.d.ts +37 -0
  308. package/dist/core/server/entries/operations/slugUniqueness.js +116 -0
  309. package/dist/core/server/entries/operations/update.d.ts +6 -1
  310. package/dist/core/server/entries/operations/update.js +24 -1
  311. package/dist/core/server/fields/slugResolver.d.ts +3 -13
  312. package/dist/core/server/fields/slugResolver.js +8 -37
  313. package/dist/core/server/generator/fields.js +10 -17
  314. package/dist/core/server/generator/formFields.js +2 -1
  315. package/dist/core/server/generator/generator.js +4 -4
  316. package/dist/core/server/generator/utils.d.ts +1 -0
  317. package/dist/core/server/generator/utils.js +4 -0
  318. package/dist/paraglide/messages/_index.d.ts +3 -36
  319. package/dist/paraglide/messages/_index.js +3 -71
  320. package/dist/paraglide/messages/hello_world.d.ts +5 -0
  321. package/dist/paraglide/messages/hello_world.js +33 -0
  322. package/dist/paraglide/messages/login_hello.d.ts +16 -0
  323. package/dist/paraglide/messages/login_hello.js +34 -0
  324. package/dist/paraglide/messages/login_please_login.d.ts +16 -0
  325. package/dist/paraglide/messages/login_please_login.js +34 -0
  326. package/dist/shop/server/orders.d.ts +1 -0
  327. package/dist/shop/server/orders.js +14 -0
  328. package/dist/shop/server/shop-data.d.ts +2 -0
  329. package/dist/shop/server/shop-data.js +20 -5
  330. package/dist/sveltekit/server/handle.js +17 -0
  331. package/dist/types/cms.schema.js +4 -2
  332. package/dist/types/fields.d.ts +35 -0
  333. package/dist/types/index.d.ts +1 -1
  334. package/dist/types/layout.d.ts +35 -2
  335. package/dist/updates/0.26.0/index.d.ts +2 -0
  336. package/dist/updates/0.26.0/index.js +51 -0
  337. package/dist/updates/index.js +3 -1
  338. package/package.json +29 -7
  339. package/dist/admin/client/collection/empty-state.svelte +0 -28
  340. package/dist/admin/client/collection/empty-state.svelte.d.ts +0 -9
  341. package/dist/admin/client/form/submission-status-badge.svelte +0 -41
  342. package/dist/admin/client/form/submission-status-badge.svelte.d.ts +0 -7
  343. package/dist/admin/components/media/file-preview.svelte +0 -51
  344. package/dist/admin/components/media/file-preview.svelte.d.ts +0 -6
  345. package/dist/admin/utils/formatDate.d.ts +0 -5
  346. package/dist/paraglide/messages/en.d.ts +0 -5
  347. package/dist/paraglide/messages/en.js +0 -14
  348. package/dist/paraglide/messages/pl.d.ts +0 -5
  349. package/dist/paraglide/messages/pl.js +0 -14
@@ -0,0 +1,88 @@
1
+ const SEO_KEYS = [
2
+ 'slug',
3
+ 'canonicalUrl',
4
+ 'title',
5
+ 'description',
6
+ 'ogImage',
7
+ 'keywords',
8
+ 'customCode'
9
+ ];
10
+ // Compile-time exhaustiveness guard: fails the build if `SeoFieldData` gains or
11
+ // loses a key relative to `SEO_KEYS`, instead of a key silently dropping from
12
+ // the resolver. Must reference every `SeoFieldData` key and compile today.
13
+ const _SEO_KEYS_EXHAUSTIVE = {
14
+ slug: true,
15
+ canonicalUrl: true,
16
+ title: true,
17
+ description: true,
18
+ ogImage: true,
19
+ keywords: true,
20
+ customCode: true
21
+ };
22
+ void _SEO_KEYS_EXHAUSTIVE;
23
+ /**
24
+ * Resolve `entry.seo` into flat fields, flattening localized objects by
25
+ * `language`.
26
+ *
27
+ * For each of the six text keys: a `string` value passes through; a localized
28
+ * `{ lang: string }` object resolves to `obj[language]` (or `undefined` if the
29
+ * requested language is missing); anything else resolves to `undefined`.
30
+ *
31
+ * `ogImage` is exempt from flattening: it passes through unchanged as a string
32
+ * or a {@link MediaFile} object (only `null`/`undefined` → `undefined`).
33
+ *
34
+ * The name `resolveSeo` is a deliberate, v1.0-frozen choice — it aligns with
35
+ * the `resolveEntryUrl` / `resolveMedia*` resolver family.
36
+ *
37
+ * @param entry - The (possibly resolved/hydrated) entry, or any object with an
38
+ * optional `seo` property. A non-object or missing `seo` yields all-`undefined`.
39
+ * @param language - The active language code. Required to flatten localized
40
+ * `{ lang: string }` values; localized fields resolve to `undefined` without it.
41
+ * @returns A {@link ResolvedSeo} — all keys present, each `string`/`MediaFile`
42
+ * (only `ogImage`) or `undefined`.
43
+ * @public
44
+ * @example
45
+ * ```svelte
46
+ * <script lang="ts">
47
+ * import { resolveSeo } from 'includio-cms/core';
48
+ * import { Seo } from 'includio-cms';
49
+ * let { entry, language } = $props();
50
+ * const seo = $derived(resolveSeo(entry, language));
51
+ * </script>
52
+ *
53
+ * <svelte:head>
54
+ * {#if seo.title}<title>{seo.title}</title>{/if}
55
+ * {#if seo.canonicalUrl}<link rel="canonical" href={seo.canonicalUrl} />{/if}
56
+ * </svelte:head>
57
+ *
58
+ * <Seo
59
+ * title={seo.title}
60
+ * description={seo.description}
61
+ * keywords={seo.keywords}
62
+ * ogImage={seo.ogImage}
63
+ * canonicalUrl={seo.canonicalUrl}
64
+ * />
65
+ * ```
66
+ */
67
+ export function resolveSeo(entry, language) {
68
+ const rawSeo = entry.seo;
69
+ const seo = rawSeo != null && typeof rawSeo === 'object' ? rawSeo : {};
70
+ const out = {};
71
+ for (const k of SEO_KEYS) {
72
+ const v = seo[k];
73
+ if (k === 'ogImage') {
74
+ // Never localize/flatten: pass string OR MediaFile object through.
75
+ out[k] = v != null && (typeof v === 'string' || typeof v === 'object') ? v : undefined;
76
+ }
77
+ else if (typeof v === 'string') {
78
+ out[k] = v;
79
+ }
80
+ else if (language && v != null && typeof v === 'object') {
81
+ out[k] = v[language];
82
+ }
83
+ else {
84
+ out[k] = undefined;
85
+ }
86
+ }
87
+ return out;
88
+ }
@@ -0,0 +1,43 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * @internal v1.0-frozen SEO field shape. SSOT for Zod + TS gen. Mismatches
4
+ * (description/ogImage required-in-Zod vs optional-in-TS; ogImage flat-not-localized)
5
+ * are PRE-EXISTING and preserved 1:1. Fixing = behavior change, post-1.0.
6
+ *
7
+ * NOTE: the current Zod `z.object` key order and the current TS template-literal
8
+ * line order DIFFER (Zod: ...keywords, ogImage, customCode; TS: ...ogImage,
9
+ * keywords, customCode). This array uses the ZOD source order; `buildSeoTsType`
10
+ * keeps its own (TS) line order verbatim and only derives `?` per key from
11
+ * `tsOptional`. The Zod shape-keys snapshot sorts keys; the TS-type-string
12
+ * snapshot preserves line order verbatim — so both orderings are deliberately
13
+ * reproduced 1:1.
14
+ */
15
+ export interface SeoDescriptorEntry {
16
+ key: string;
17
+ /** non-localized Zod */
18
+ zod: 'string' | 'string-optional';
19
+ zodLocalized: 'perLang-string' | 'perLang-optional' | 'flat-string';
20
+ /** TS codegen */
21
+ tsOptional: boolean;
22
+ }
23
+ export declare const SEO_FIELD_DESCRIPTOR: readonly SeoDescriptorEntry[];
24
+ /**
25
+ * @internal Builds the exact object passed to `z.object(...)` for the SEO field,
26
+ * for both non-localized and localized modes — byte-identical to the previous
27
+ * hand-written blocks in `fieldSchemaToTs.ts`.
28
+ */
29
+ export declare function buildSeoZodShape(languages: string[], localized: boolean): Record<string, z.ZodType>;
30
+ /**
31
+ * @internal TS-codegen line order (verbatim from generator/fields.ts
32
+ * `case 'seo':`). Distinct from the Zod object key order above (see module
33
+ * note). Exported solely so drift-guard tests can assert it stays a
34
+ * permutation of the descriptor keys.
35
+ */
36
+ export declare const SEO_TS_LINE_ORDER: readonly ["slug", "canonicalUrl", "title", "description", "ogImage", "keywords", "customCode"];
37
+ /**
38
+ * @internal Returns the SEO TS type string with EXACTLY the same characters
39
+ * (tabs/braces/whitespace) as the previous hand-written template literal in
40
+ * `server/generator/fields.ts`. Only the per-key `?` is derived from the
41
+ * descriptor's `tsOptional`.
42
+ */
43
+ export declare function buildSeoTsType(): string;
@@ -0,0 +1,74 @@
1
+ import { z } from 'zod';
2
+ export const SEO_FIELD_DESCRIPTOR = [
3
+ { key: 'slug', zod: 'string', zodLocalized: 'perLang-string', tsOptional: false },
4
+ {
5
+ key: 'canonicalUrl',
6
+ zod: 'string-optional',
7
+ zodLocalized: 'perLang-optional',
8
+ tsOptional: true
9
+ },
10
+ { key: 'title', zod: 'string', zodLocalized: 'perLang-string', tsOptional: false },
11
+ { key: 'description', zod: 'string', zodLocalized: 'perLang-string', tsOptional: true },
12
+ { key: 'keywords', zod: 'string-optional', zodLocalized: 'perLang-optional', tsOptional: true },
13
+ { key: 'ogImage', zod: 'string', zodLocalized: 'flat-string', tsOptional: true },
14
+ { key: 'customCode', zod: 'string-optional', zodLocalized: 'perLang-optional', tsOptional: true }
15
+ ];
16
+ /**
17
+ * @internal Builds the exact object passed to `z.object(...)` for the SEO field,
18
+ * for both non-localized and localized modes — byte-identical to the previous
19
+ * hand-written blocks in `fieldSchemaToTs.ts`.
20
+ */
21
+ export function buildSeoZodShape(languages, localized) {
22
+ const shape = {};
23
+ for (const entry of SEO_FIELD_DESCRIPTOR) {
24
+ if (!localized) {
25
+ shape[entry.key] = entry.zod === 'string-optional' ? z.string().optional() : z.string();
26
+ continue;
27
+ }
28
+ switch (entry.zodLocalized) {
29
+ case 'flat-string':
30
+ shape[entry.key] = z.string();
31
+ break;
32
+ case 'perLang-optional':
33
+ shape[entry.key] = z.object(Object.fromEntries(languages.map((lang) => [lang, z.string().optional()])));
34
+ break;
35
+ case 'perLang-string':
36
+ shape[entry.key] = z.object(Object.fromEntries(languages.map((lang) => [lang, z.string()])));
37
+ break;
38
+ default: {
39
+ const _exhaustive = entry.zodLocalized;
40
+ throw new Error(`Unhandled zodLocalized: ${String(_exhaustive)}`);
41
+ }
42
+ }
43
+ }
44
+ return shape;
45
+ }
46
+ /**
47
+ * @internal TS-codegen line order (verbatim from generator/fields.ts
48
+ * `case 'seo':`). Distinct from the Zod object key order above (see module
49
+ * note). Exported solely so drift-guard tests can assert it stays a
50
+ * permutation of the descriptor keys.
51
+ */
52
+ export const SEO_TS_LINE_ORDER = [
53
+ 'slug',
54
+ 'canonicalUrl',
55
+ 'title',
56
+ 'description',
57
+ 'ogImage',
58
+ 'keywords',
59
+ 'customCode'
60
+ ];
61
+ /**
62
+ * @internal Returns the SEO TS type string with EXACTLY the same characters
63
+ * (tabs/braces/whitespace) as the previous hand-written template literal in
64
+ * `server/generator/fields.ts`. Only the per-key `?` is derived from the
65
+ * descriptor's `tsOptional`.
66
+ */
67
+ export function buildSeoTsType() {
68
+ const byKey = new Map(SEO_FIELD_DESCRIPTOR.map((e) => [e.key, e]));
69
+ const lines = SEO_TS_LINE_ORDER.map((key) => {
70
+ const entry = byKey.get(key);
71
+ return `\t\t\t\t${key}${entry.tsOptional ? '?' : ''}: string;`;
72
+ });
73
+ return `{\n${lines.join('\n')}\n\t\t\t}`;
74
+ }
@@ -0,0 +1,13 @@
1
+ /** @internal Pure: resolve slug dot-path, defaulting to 'seo.slug'. */
2
+ export declare function resolveSlugPath(slugField: string | undefined): string;
3
+ /** @internal Traverse dot-path; handles string & localized { lang: string }. */
4
+ export declare function getSlugFromEntryData(data: Record<string, unknown>, slugPath: string, language?: string): string | undefined;
5
+ /** @internal Pure: apply pathTemplate (no getCMS). */
6
+ export declare function applyPathTemplate(pathTemplate: string | undefined, slug: string): string;
7
+ /** @internal Compose full entry URL; undefined when no resolvable slug. */
8
+ export declare function resolveEntryUrl(opts: {
9
+ slugField?: string;
10
+ pathTemplate?: string;
11
+ data: Record<string, unknown>;
12
+ language?: string;
13
+ }): string | undefined;
@@ -0,0 +1,32 @@
1
+ const DEFAULT_SLUG_PATH = 'seo.slug';
2
+ /** @internal Pure: resolve slug dot-path, defaulting to 'seo.slug'. */
3
+ export function resolveSlugPath(slugField) {
4
+ return slugField || DEFAULT_SLUG_PATH;
5
+ }
6
+ /** @internal Traverse dot-path; handles string & localized { lang: string }. */
7
+ export function getSlugFromEntryData(data, slugPath, language) {
8
+ const parts = slugPath.split('.');
9
+ let current = data;
10
+ for (const part of parts) {
11
+ if (current == null || typeof current !== 'object')
12
+ return undefined;
13
+ current = current[part];
14
+ }
15
+ if (typeof current === 'string')
16
+ return current;
17
+ if (language && current != null && typeof current === 'object') {
18
+ return current[language];
19
+ }
20
+ return undefined;
21
+ }
22
+ /** @internal Pure: apply pathTemplate (no getCMS). */
23
+ export function applyPathTemplate(pathTemplate, slug) {
24
+ return pathTemplate ? pathTemplate.replace('{slug}', slug) : slug;
25
+ }
26
+ /** @internal Compose full entry URL; undefined when no resolvable slug. */
27
+ export function resolveEntryUrl(opts) {
28
+ const slug = getSlugFromEntryData(opts.data, resolveSlugPath(opts.slugField), opts.language);
29
+ if (!slug)
30
+ return undefined;
31
+ return applyPathTemplate(opts.pathTemplate, slug);
32
+ }
@@ -2,6 +2,14 @@
2
2
  * Check if URL is external (starts with http:// or https://).
3
3
  */
4
4
  export declare function isExternalUrl(url: string): boolean;
5
+ /**
6
+ * Validate a URL field string value. Empty is valid (presence is enforced by
7
+ * `required`). Accepts absolute http(s) URLs, `mailto:` / `tel:`, and any
8
+ * scheme-less value (root-relative `/path`, anchor `#id`, or a bare internal
9
+ * slug like `witaj` — many non-technical editors omit the leading slash).
10
+ * Rejects whitespace and disallowed schemes (e.g. `javascript:`).
11
+ */
12
+ export declare function isValidUrlValue(value: string): boolean;
5
13
  /**
6
14
  * Build rel attribute string from individual tokens.
7
15
  * Deduplicates and returns space-separated string or empty string.
@@ -4,6 +4,33 @@
4
4
  export function isExternalUrl(url) {
5
5
  return /^https?:\/\//i.test(url);
6
6
  }
7
+ /**
8
+ * Validate a URL field string value. Empty is valid (presence is enforced by
9
+ * `required`). Accepts absolute http(s) URLs, `mailto:` / `tel:`, and any
10
+ * scheme-less value (root-relative `/path`, anchor `#id`, or a bare internal
11
+ * slug like `witaj` — many non-technical editors omit the leading slash).
12
+ * Rejects whitespace and disallowed schemes (e.g. `javascript:`).
13
+ */
14
+ export function isValidUrlValue(value) {
15
+ const v = value.trim();
16
+ if (v === '')
17
+ return true;
18
+ if (/\s/.test(value))
19
+ return false;
20
+ const schemeMatch = v.match(/^([a-z][a-z0-9+.-]*):/i);
21
+ if (!schemeMatch)
22
+ return true; // scheme-less → internal path/slug/anchor
23
+ const scheme = schemeMatch[1].toLowerCase();
24
+ if (scheme === 'mailto' || scheme === 'tel')
25
+ return true;
26
+ try {
27
+ const u = new URL(v);
28
+ return u.protocol === 'http:' || u.protocol === 'https:';
29
+ }
30
+ catch {
31
+ return false;
32
+ }
33
+ }
7
34
  /**
8
35
  * Build rel attribute string from individual tokens.
9
36
  * Deduplicates and returns space-separated string or empty string.
@@ -1,4 +1,5 @@
1
1
  export { getCMS } from './cms.js';
2
2
  export { resolveMediaWithStyles, type ResolvedMedia } from './server/fields/utils/resolveMedia.js';
3
+ export { resolveSeo, type ResolvedSeo } from './fields/resolveSeo.js';
3
4
  export { createEntityAPI } from '../entity/index.js';
4
5
  export { getAuth } from '../server/auth.js';
@@ -1,4 +1,5 @@
1
1
  export { getCMS } from './cms.js';
2
2
  export { resolveMediaWithStyles } from './server/fields/utils/resolveMedia.js';
3
+ export { resolveSeo } from './fields/resolveSeo.js';
3
4
  export { createEntityAPI } from '../entity/index.js';
4
5
  export { getAuth } from '../server/auth.js';
@@ -6,6 +6,7 @@ import { _getDbEntryOrThrow as getDbEntryOrThrow } from './get.js';
6
6
  import { generateZodSchemaFromFields } from '../../../fields/fieldSchemaToTs.js';
7
7
  import { getFieldsFromConfig } from '../../../fields/layoutUtils.js';
8
8
  import { CmsError, formatZodDataIssues } from '../../../errors.js';
9
+ import { assertSlugUnique } from './slugUniqueness.js';
9
10
  export const createEntrySchema = z.object({
10
11
  slug: z.string(),
11
12
  type: z.enum(entryTypes)
@@ -51,6 +52,18 @@ export const createEntryVersion = async (data, options) => {
51
52
  }
52
53
  validatedData = parsedData.data;
53
54
  }
55
+ // Slug uniqueness: only enforced for published-or-scheduled writes (draft
56
+ // versions and skipValidation paths — e.g. seeds — are exempt). Each slug
57
+ // field is checked in its own namespace (see assertSlugUnique).
58
+ if (data.publishedAt != null && !options?.skipValidation) {
59
+ const entry = await getDbEntryOrThrow({ id: data.entryId });
60
+ await assertSlugUnique({
61
+ collection: entry.slug,
62
+ lang: data.lang,
63
+ data: validatedData,
64
+ excludeEntryId: data.entryId
65
+ });
66
+ }
54
67
  const newEntryVersion = await getCMS().databaseAdapter.createEntryVersion({
55
68
  ...data,
56
69
  createdBy: user.id,
@@ -17,6 +17,13 @@ export declare const getEntryLabels: (options: {
17
17
  search?: string;
18
18
  limit?: number;
19
19
  status?: "published" | "draft" | "all";
20
+ /**
21
+ * Preferred language for entry titles. Falls back to other languages when
22
+ * an entry has no version in the preferred one (links are language-
23
+ * agnostic — we resolve by id, but the human label should follow the
24
+ * editor's content language).
25
+ */
26
+ language?: string;
20
27
  }) => Promise<{
21
28
  id: string;
22
29
  label: string;
@@ -121,18 +121,22 @@ export const getEntryLabels = async (options) => {
121
121
  if (dbEntries.length === 0)
122
122
  return [];
123
123
  const entryIds = dbEntries.map((e) => e.id);
124
- const language = cms.languages[0];
125
- const allVersions = await cms.databaseAdapter.getEntryVersions({
126
- entryIds,
127
- lang: language
128
- });
124
+ // Scan the preferred language first, then the rest as fallback. Without
125
+ // this an entry that only has a `pl` version was invisible to a `pl`
126
+ // editor because cms.languages[0] = 'en' in demo.
127
+ const langs = [
128
+ ...(options.language ? [options.language] : []),
129
+ ...cms.languages.filter((l) => l !== options.language)
130
+ ];
131
+ const allVersions = (await Promise.all(langs.map((lang) => cms.databaseAdapter.getEntryVersions({ entryIds, lang }).then((vs) => vs.map((v) => ({ ...v, _langOrder: langs.indexOf(lang) })))))).flat();
129
132
  const now = new Date();
130
133
  const statusFilter = options.status ?? 'all';
131
134
  const entryAdminTitle = config.entryAdminTitle;
132
135
  let results = dbEntries
133
136
  .map((entry) => {
134
137
  const entryVersions = allVersions.filter((v) => v.entryId === entry.id);
135
- const sorted = entryVersions.sort((a, b) => b.versionNumber - a.versionNumber);
138
+ // Preferred language wins; within a language, the latest version.
139
+ const sorted = entryVersions.sort((a, b) => a._langOrder - b._langOrder || b.versionNumber - a.versionNumber);
136
140
  const publishedVersion = sorted.find((v) => v.publishedAt != null && v.publishedAt <= now) || null;
137
141
  const hasPublished = publishedVersion != null;
138
142
  if (statusFilter === 'published' && !hasPublished)
@@ -0,0 +1,37 @@
1
+ import type { Field } from '../../../../types/fields.js';
2
+ /**
3
+ * Collect every dot-path in a collection whose value should be unique across
4
+ * entries (per content language) when published.
5
+ *
6
+ * Rule (decision: per-path, not per-collection):
7
+ * - Each `SlugField` (root or nested inside an object) has its own namespace.
8
+ * Two entries' top-level `slug` must not collide, but `slug` (entry A) and
9
+ * `seo.slug` (entry B) MAY share a value — they identify different things.
10
+ * - A `SeoField` always contributes its own `<path>.slug` namespace.
11
+ *
12
+ * Recurses into `object` fields; skips `array`/`blocks` because identifiers
13
+ * inside repeatable items aren't entry-level slugs.
14
+ */
15
+ export declare function collectSlugFieldPaths(fields: Field[], prefix?: string): string[];
16
+ export interface AssertSlugUniqueArgs {
17
+ /** Collection slug (config slug, e.g. `'blog-post'`). */
18
+ collection: string;
19
+ /** Content language whose slugs must not collide. */
20
+ lang: string;
21
+ /** Validated entry data about to be persisted as a published version. */
22
+ data: Record<string, unknown>;
23
+ /** Entry being published — excluded from the conflict scan. */
24
+ excludeEntryId: string;
25
+ }
26
+ /**
27
+ * Reject publishing when any slug-typed field (or `seo.slug`) collides with
28
+ * another entry's currently-published-or-scheduled version in the same
29
+ * collection + language. Each slug path is checked in its own namespace.
30
+ * Drafts, archived entries, the entry's own history, and other languages
31
+ * are ignored. Empty slug values are skipped — uniqueness is only
32
+ * meaningful for non-empty strings.
33
+ *
34
+ * @throws CmsError SLUG_DUPLICATE on the first conflict (`details.path` says
35
+ * which slug-field collided).
36
+ */
37
+ export declare function assertSlugUnique({ collection, lang, data, excludeEntryId }: AssertSlugUniqueArgs): Promise<void>;
@@ -0,0 +1,116 @@
1
+ import { getCMS } from '../../../cms.js';
2
+ import { CmsError } from '../../../errors.js';
3
+ import { getFieldsFromConfig } from '../../../fields/layoutUtils.js';
4
+ /**
5
+ * Collect every dot-path in a collection whose value should be unique across
6
+ * entries (per content language) when published.
7
+ *
8
+ * Rule (decision: per-path, not per-collection):
9
+ * - Each `SlugField` (root or nested inside an object) has its own namespace.
10
+ * Two entries' top-level `slug` must not collide, but `slug` (entry A) and
11
+ * `seo.slug` (entry B) MAY share a value — they identify different things.
12
+ * - A `SeoField` always contributes its own `<path>.slug` namespace.
13
+ *
14
+ * Recurses into `object` fields; skips `array`/`blocks` because identifiers
15
+ * inside repeatable items aren't entry-level slugs.
16
+ */
17
+ export function collectSlugFieldPaths(fields, prefix = '') {
18
+ const paths = [];
19
+ for (const f of fields) {
20
+ const full = prefix ? `${prefix}.${f.slug}` : f.slug;
21
+ if (f.type === 'slug') {
22
+ paths.push(full);
23
+ }
24
+ else if (f.type === 'seo') {
25
+ paths.push(`${full}.slug`);
26
+ }
27
+ else if (f.type === 'object' && 'fields' in f) {
28
+ paths.push(...collectSlugFieldPaths(f.fields, full));
29
+ }
30
+ }
31
+ return paths;
32
+ }
33
+ /** Traverse dot-path on a plain object; returns undefined if any segment missing. */
34
+ function readDotPath(data, path) {
35
+ const parts = path.split('.');
36
+ let current = data;
37
+ for (const part of parts) {
38
+ if (current == null || typeof current !== 'object')
39
+ return undefined;
40
+ current = current[part];
41
+ }
42
+ return current;
43
+ }
44
+ /** Resolve a slug value at dot-path; handles raw string or `{ lang: string }`. */
45
+ function readSlugAtPath(data, path, lang) {
46
+ const raw = readDotPath(data, path);
47
+ if (typeof raw === 'string')
48
+ return raw;
49
+ if (raw != null && typeof raw === 'object') {
50
+ return raw[lang];
51
+ }
52
+ return undefined;
53
+ }
54
+ /**
55
+ * Reject publishing when any slug-typed field (or `seo.slug`) collides with
56
+ * another entry's currently-published-or-scheduled version in the same
57
+ * collection + language. Each slug path is checked in its own namespace.
58
+ * Drafts, archived entries, the entry's own history, and other languages
59
+ * are ignored. Empty slug values are skipped — uniqueness is only
60
+ * meaningful for non-empty strings.
61
+ *
62
+ * @throws CmsError SLUG_DUPLICATE on the first conflict (`details.path` says
63
+ * which slug-field collided).
64
+ */
65
+ export async function assertSlugUnique({ collection, lang, data, excludeEntryId }) {
66
+ const cms = getCMS();
67
+ let config;
68
+ try {
69
+ config = cms.getBySlug(collection);
70
+ }
71
+ catch {
72
+ return; // unknown collection — nothing to check
73
+ }
74
+ const fields = getFieldsFromConfig(config);
75
+ const slugPaths = collectSlugFieldPaths(fields);
76
+ // Snapshot incoming values, dropping empty entries up-front so we don't
77
+ // fetch other entries' data when nothing could collide.
78
+ const incoming = slugPaths
79
+ .map((path) => ({ path, value: readSlugAtPath(data, path, lang) }))
80
+ .filter((p) => typeof p.value === 'string' && p.value.trim() !== '');
81
+ if (incoming.length === 0)
82
+ return;
83
+ const dbEntries = await cms.databaseAdapter.getEntries({ slug: collection });
84
+ const otherIds = dbEntries
85
+ .filter((e) => e.archivedAt == null && e.id !== excludeEntryId)
86
+ .map((e) => e.id);
87
+ if (otherIds.length === 0)
88
+ return;
89
+ const versions = await cms.databaseAdapter.getEntryVersions({
90
+ entryIds: otherIds,
91
+ lang
92
+ });
93
+ // Each other entry can have many historical versions — only its latest
94
+ // published-or-scheduled version is "live" for slug purposes, so collapse
95
+ // per entry before comparing.
96
+ const latestPerEntry = new Map();
97
+ for (const v of versions) {
98
+ if (v.publishedAt == null)
99
+ continue;
100
+ const cur = latestPerEntry.get(v.entryId);
101
+ if (!cur || v.versionNumber > cur.versionNumber) {
102
+ latestPerEntry.set(v.entryId, {
103
+ versionNumber: v.versionNumber,
104
+ data: v.data
105
+ });
106
+ }
107
+ }
108
+ for (const [entryId, version] of latestPerEntry) {
109
+ for (const { path, value } of incoming) {
110
+ const otherSlug = readSlugAtPath(version.data, path, lang);
111
+ if (otherSlug === value) {
112
+ throw new CmsError('SLUG_DUPLICATE', `Slug „${value}" jest już używany w innym wpisie tej kolekcji (pole „${path}", język: ${lang}).`, { collection, lang, path, slug: value, conflictEntryId: entryId });
113
+ }
114
+ }
115
+ }
116
+ }
@@ -11,7 +11,7 @@ export declare const updateEntryVersionSchema: z.ZodObject<{
11
11
  publishedBy: z.ZodOptional<z.ZodNullable<z.ZodString>>;
12
12
  }, z.z.core.$strip>;
13
13
  export declare const updateEntryVersion: (id: string, data: Partial<DbEntryVersion>) => Promise<DbEntryVersion>;
14
- export declare const updateEntryVersionCommandTypes: readonly ["draft", "published-now", "published-scheduled", "cancel-published"];
14
+ export declare const updateEntryVersionCommandTypes: readonly ["draft", "published-now", "published-scheduled", "cancel-published", "cancel-scheduled"];
15
15
  export type UpdateEntryVersionCommandType = (typeof updateEntryVersionCommandTypes)[number];
16
16
  /** Prune old draft versions scoped to (entryId, lang) */
17
17
  export declare const pruneOldDraftVersions: (entryId: string, lang: string) => Promise<void>;
@@ -21,3 +21,8 @@ export declare const upsertDraftVersion: (entryId: string, data: Record<string,
21
21
  }) => Promise<DbEntryVersion>;
22
22
  /** Unpublish a specific language version for an entry */
23
23
  export declare const unpublishEntryLang: (entryId: string, lang: string) => Promise<void>;
24
+ /**
25
+ * Cancel only the scheduled (future-dated) versions for a language, leaving the
26
+ * currently-live published version (publishedAt <= now) untouched.
27
+ */
28
+ export declare const cancelScheduledEntryLang: (entryId: string, lang: string) => Promise<void>;
@@ -80,7 +80,8 @@ export const updateEntryVersionCommandTypes = [
80
80
  'draft',
81
81
  'published-now',
82
82
  'published-scheduled',
83
- 'cancel-published'
83
+ 'cancel-published',
84
+ 'cancel-scheduled'
84
85
  ];
85
86
  const MAX_DRAFT_VERSIONS = 10;
86
87
  /** Prune old draft versions scoped to (entryId, lang) */
@@ -142,3 +143,25 @@ export const unpublishEntryLang = async (entryId, lang) => {
142
143
  });
143
144
  }));
144
145
  };
146
+ /**
147
+ * Cancel only the scheduled (future-dated) versions for a language, leaving the
148
+ * currently-live published version (publishedAt <= now) untouched.
149
+ */
150
+ export const cancelScheduledEntryLang = async (entryId, lang) => {
151
+ const now = new Date();
152
+ const versions = await getCMS().databaseAdapter.getEntryVersions({
153
+ entryIds: [entryId],
154
+ lang
155
+ });
156
+ await Promise.all(versions
157
+ .filter((v) => v.publishedAt !== null && new Date(v.publishedAt) > now)
158
+ .map((version) => {
159
+ return getCMS().databaseAdapter.updateEntryVersion({
160
+ id: version.id,
161
+ data: {
162
+ publishedAt: null,
163
+ publishedBy: null
164
+ }
165
+ });
166
+ }));
167
+ };
@@ -1,15 +1,5 @@
1
- /**
2
- * Get the dot-path to the slug field for a given collection/single slug.
3
- * Falls back to 'seo.slug' if no slugField is configured.
4
- */
1
+ export { getSlugFromEntryData } from '../../fields/slugPath.js';
2
+ /** Get dot-path to slug field for a collection. Falls back to 'seo.slug'. */
5
3
  export declare function getEntrySlugPath(configSlug: string): string;
6
- /**
7
- * Traverse a dot-path in entry data and return the slug value.
8
- * Handles both plain strings and localized objects { lang: string }.
9
- */
10
- export declare function getSlugFromEntryData(data: Record<string, unknown>, slugPath: string, language?: string): string | undefined;
11
- /**
12
- * Build full entry path using collection's pathTemplate.
13
- * If no template configured, returns the raw slug.
14
- */
4
+ /** Build full entry path using collection's pathTemplate. */
15
5
  export declare function getEntryPath(configSlug: string, slug: string): string;