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
@@ -1,17 +1,26 @@
1
1
  <script lang="ts">
2
- import { goto } from '$app/navigation';
2
+ import { goto, beforeNavigate } from '$app/navigation';
3
3
  import { isCollection } from '../../utils/is-collection.js';
4
4
  import { toast } from 'svelte-sonner';
5
5
  import { getRemotes } from '../../context/remotes.js';
6
6
  import { getBreadcrumbs } from '../../state/breadcrumbs.svelte.js';
7
7
  import { getContentLanguage } from '../../state/content-language.svelte.js';
8
8
  import EntryHeader from './entry-header.svelte';
9
+ import ConfirmationDialog from '../../components/dialogs/confirmation-dialog.svelte';
10
+ import FormErrorSummary, {
11
+ type FormErrorEntry
12
+ } from '../../components/forms/form-error-summary.svelte';
9
13
  import { getLocalizedLabel } from '../../utils/collectionLabel.js';
14
+ import { scrollIntoViewWithin } from '../../utils/scrollWithin.js';
15
+ import { activateContainingTabs } from '../../utils/tabActivation.js';
16
+ import { activateContainingAccordions } from '../../utils/accordionActivation.js';
17
+ import { findFieldPathElement } from '../../utils/fieldPathElement.js';
18
+ import { flattenFormErrors } from '../../utils/flattenFormErrors.js';
10
19
  import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
20
+ import { sidebarLang } from '../../components/layout/lang.js';
21
+ import { errorMessages } from '../../i18n/errors.js';
11
22
  import type { InterfaceLanguage } from '../../../types/languages.js';
12
- import AlertCircle from '@tabler/icons-svelte/icons/alert-circle';
13
23
  import ArchiveIcon from '@tabler/icons-svelte/icons/archive';
14
- import XIcon from '@tabler/icons-svelte/icons/x';
15
24
  import Button from '../../../components/ui/button/button.svelte';
16
25
  import { cn } from '../../../utils.js';
17
26
  import { ElementSize, useDebounce } from 'runed';
@@ -21,60 +30,16 @@
21
30
  import type { DbEntryVersion, RawEntry } from '../../../types/entries.js';
22
31
  import EntryForm from './entry-form.svelte';
23
32
  import type { UpdateEntryVersionCommandType } from '../../../core/server/entries/operations/update.js';
24
- import { getRawCollectionEntryLabel } from '../../utils/entryLabel.js';
25
- import type { Field } from '../../../types/fields.js';
33
+ import { getCollectionEntryDisplayLabel } from '../../utils/entryLabel.js';
26
34
  import { getFieldsFromConfig, hasLayout } from '../../../core/fields/layoutUtils.js';
27
- import type { ValidationErrors } from 'sveltekit-superforms';
28
35
  import { createHybridContext } from './hybrid/hybrid-context.svelte.js';
29
- import { onMount, setContext } from 'svelte';
36
+ import { onMount, setContext, tick } from 'svelte';
30
37
  import { get } from 'svelte/store';
31
38
  const contentLanguage = getContentLanguage();
32
39
  const remotes = getRemotes();
33
40
  const interfaceLanguage = useInterfaceLanguage();
34
41
  const hybridContext = createHybridContext();
35
42
 
36
- function flattenErrors(
37
- errors: ValidationErrors<Record<string, unknown>>,
38
- fields: Field[],
39
- prefix = ''
40
- ): string[] {
41
- const result: string[] = [];
42
-
43
- for (const [key, value] of Object.entries(errors)) {
44
- if (key === '_errors') {
45
- if (Array.isArray(value)) {
46
- result.push(...value);
47
- }
48
- continue;
49
- }
50
- const path = prefix ? `${prefix}.${key}` : key;
51
-
52
- // Find field label
53
- const field = fields.find((f) => f.slug === key);
54
- const label = field
55
- ? getLocalizedLabel(field.label, interfaceLanguage.current) || field.slug
56
- : key;
57
-
58
- if (Array.isArray(value)) {
59
- // Direct error messages
60
- result.push(`${label}: ${value.join(', ')}`);
61
- } else if (typeof value === 'object' && value !== null) {
62
- // Nested errors - recurse
63
- const nestedFields = field && 'fields' in field ? (field.fields as Field[]) : [];
64
- const nested = flattenErrors(
65
- value as ValidationErrors<Record<string, unknown>>,
66
- nestedFields,
67
- path
68
- );
69
- if (nested.length > 0) {
70
- result.push(...nested.map((e) => `${label} > ${e}`));
71
- }
72
- }
73
- }
74
-
75
- return result;
76
- }
77
-
78
43
  const lang: Record<
79
44
  InterfaceLanguage,
80
45
  {
@@ -86,6 +51,7 @@
86
51
  publishToast: string;
87
52
  scheduledToast: string;
88
53
  unpublishToast: string;
54
+ cancelScheduledToast: string;
89
55
  saveFailed: string;
90
56
  cannotPublish: string;
91
57
  validationHint: string;
@@ -93,6 +59,7 @@
93
59
  switchToDraft: string;
94
60
  editingDraft: string;
95
61
  switchToPublished: string;
62
+ unsavedConfirm: string;
96
63
  }
97
64
  > = {
98
65
  en: {
@@ -104,13 +71,15 @@
104
71
  publishToast: 'Entry published successfully',
105
72
  scheduledToast: 'Entry scheduled successfully',
106
73
  unpublishToast: 'Publication withdrawn',
107
- saveFailed: 'Save failed',
74
+ cancelScheduledToast: 'Scheduled update cancelled',
75
+ saveFailed: errorMessages.server.saveFailed.en,
108
76
  cannotPublish: 'Cannot publish',
109
77
  validationHint: 'Fix the highlighted fields below to publish',
110
78
  newerDraft: 'A newer unpublished draft exists',
111
79
  switchToDraft: 'Switch to draft',
112
80
  editingDraft: 'You are editing an unpublished draft',
113
- switchToPublished: 'View published'
81
+ switchToPublished: 'View published',
82
+ unsavedConfirm: 'You have unsaved changes. Leave without saving?'
114
83
  },
115
84
  pl: {
116
85
  entryArchived: 'Wpis został zarchiwizowany pomyślnie',
@@ -121,13 +90,15 @@
121
90
  publishToast: 'Wpis został pomyślnie opublikowany',
122
91
  scheduledToast: 'Wpis został zaplanowany pomyślnie',
123
92
  unpublishToast: 'Wycofano publikację',
124
- saveFailed: 'Błąd zapisu',
93
+ cancelScheduledToast: 'Anulowano zaplanowaną aktualizację',
94
+ saveFailed: errorMessages.server.saveFailed.pl,
125
95
  cannotPublish: 'Nie można opublikować',
126
96
  validationHint: 'Popraw wyróżnione pola, żeby opublikować',
127
97
  newerDraft: 'Istnieje nowszy nieopublikowany szkic',
128
98
  switchToDraft: 'Przejdź do szkicu',
129
99
  editingDraft: 'Edytujesz nieopublikowany szkic',
130
- switchToPublished: 'Zobacz opublikowaną'
100
+ switchToPublished: 'Zobacz opublikowaną',
101
+ unsavedConfirm: 'Masz niezapisane zmiany. Opuścić stronę bez zapisywania?'
131
102
  }
132
103
  };
133
104
 
@@ -148,7 +119,7 @@
148
119
  const collectionSchema = generateZodSchemaFromFields(
149
120
  getFieldsFromConfig(collection),
150
121
  contentLanguage.all,
151
- { localized: false }
122
+ { localized: false, messageLanguage: interfaceLanguage.current }
152
123
  );
153
124
  const form = superForm(defaults(editingEntry.data, zod4(collectionSchema)), {
154
125
  validators: zod4Client(collectionSchema),
@@ -157,35 +128,68 @@
157
128
  resetForm: false
158
129
  });
159
130
 
160
- let validationErrors = $state<string[]>([]);
131
+ let validationErrors = $state<FormErrorEntry[]>([]);
161
132
 
162
- // Autosave state
133
+ // Save state — manual save only (autosave removed in S8; re-introduce with
134
+ // proper draft-only guard tracked in ROADMAP backlog).
163
135
  type SaveStatus = 'idle' | 'saving' | 'saved' | 'unsaved' | 'error';
164
136
  let saveStatus = $state<SaveStatus>('idle');
165
- let lastSavedData = $state<string>(JSON.stringify(get(form.form)));
166
- let autosaveTimer: ReturnType<typeof setTimeout> | null = null;
167
- // Tracks draft version created via autosave while editing a published version
137
+ let lastSavedAt = $state<Date | null>(null);
138
+ // Baseline for the "unsaved changes" check. `null` until the form has settled
139
+ // after mount / after a save superForm + field components normalise values
140
+ // (e.g. dates, empty selects) asynchronously, and snapshotting before that
141
+ // would cause a spurious "unsaved" flag. While `settling`, form changes are
142
+ // treated as normalisation and ignored.
143
+ let lastSavedData = $state<string | null>(null);
144
+ let settling = true;
145
+ let settleQuietTimer: ReturnType<typeof setTimeout> | undefined;
146
+ let settleMaxTimer: ReturnType<typeof setTimeout> | undefined;
168
147
  let savedDraftVersionId = $state<string | null>(null);
169
148
 
170
- const AUTOSAVE_DELAY = 30000; // 30 seconds
149
+ // Field components (TipTap, SEO auto-fill, blocks hydration, …) normalise
150
+ // their values into `$formData` asynchronously over several ticks after
151
+ // mount/save. We can't tell those apart from real edits by value, so instead
152
+ // we wait for the form to go *quiet* (no changes for `SETTLE_QUIET_MS`) and
153
+ // only then snapshot the baseline. A hard cap guards against a field that
154
+ // never stops emitting.
155
+ const SETTLE_QUIET_MS = 300;
156
+ const SETTLE_MAX_MS = 3000;
157
+
158
+ function finishSettling() {
159
+ clearTimeout(settleQuietTimer);
160
+ clearTimeout(settleMaxTimer);
161
+ lastSavedData = JSON.stringify(get(form.form));
162
+ settling = false;
163
+ }
171
164
 
172
- function scheduleAutosave() {
173
- if (isArchived) return;
174
- if (autosaveTimer) {
175
- clearTimeout(autosaveTimer);
176
- }
177
- autosaveTimer = setTimeout(async () => {
178
- await performAutosave();
179
- }, AUTOSAVE_DELAY);
165
+ function beginSettling() {
166
+ settling = true;
167
+ clearTimeout(settleQuietTimer);
168
+ clearTimeout(settleMaxTimer);
169
+ settleQuietTimer = setTimeout(finishSettling, SETTLE_QUIET_MS);
170
+ settleMaxTimer = setTimeout(finishSettling, SETTLE_MAX_MS);
171
+ }
172
+
173
+ function pokeSettling() {
174
+ // a change arrived while still settling → treat as normalisation, push
175
+ // the quiet window out (but keep the hard cap running).
176
+ clearTimeout(settleQuietTimer);
177
+ settleQuietTimer = setTimeout(finishSettling, SETTLE_QUIET_MS);
178
+ }
179
+
180
+ // After a save the form is "clean" again — re-settle so post-save
181
+ // normalisation (validated/transformed data echoed back) isn't flagged.
182
+ function markSaved() {
183
+ lastSavedAt = new Date();
184
+ saveStatus = 'saved';
185
+ beginSettling();
180
186
  }
181
187
 
182
- async function performAutosave() {
188
+ async function saveDraft() {
183
189
  if (isArchived) return;
184
190
  const currentFormData = get(form.form);
185
191
  const currentData = JSON.stringify(currentFormData);
186
- if (currentData === lastSavedData) {
187
- return;
188
- }
192
+ if (currentData === lastSavedData && saveStatus !== 'error') return;
189
193
 
190
194
  saveStatus = 'saving';
191
195
  try {
@@ -196,29 +200,37 @@
196
200
  data: currentFormData,
197
201
  type: 'draft'
198
202
  });
199
- lastSavedData = currentData;
200
- saveStatus = 'saved';
201
- // If we're editing the published version and saved a draft, track it for the banner
203
+ markSaved();
202
204
  const publishedVersion = entry.publishedVersions[currentLang];
203
205
  if (publishedVersion && editingEntry.id === publishedVersion.id && result?.id) {
204
206
  savedDraftVersionId = result.id;
205
207
  }
206
- // Reset to idle after 3s
207
- setTimeout(() => {
208
- if (saveStatus === 'saved') saveStatus = 'idle';
209
- }, 3000);
210
208
  } catch {
211
209
  saveStatus = 'error';
212
210
  }
213
211
  }
214
212
 
215
- // Track form changes for autosave + Ctrl/Cmd+S shortcut
213
+ // Warn before leaving with unsaved changes (in-app navigations).
214
+ beforeNavigate((nav) => {
215
+ if (saveStatus !== 'unsaved' || isArchived) return;
216
+ if (nav.type === 'leave') return; // browser close/reload — handled by beforeunload
217
+ if (!confirm(lang[interfaceLanguage.current].unsavedConfirm)) {
218
+ nav.cancel();
219
+ }
220
+ });
221
+
222
+ // Mark unsaved when form content diverges from the last-saved snapshot.
216
223
  onMount(() => {
224
+ beginSettling();
225
+
217
226
  const unsub = form.form.subscribe((data) => {
218
- const currentData = JSON.stringify(data);
219
- if (currentData !== lastSavedData) {
227
+ if (settling) {
228
+ pokeSettling();
229
+ return;
230
+ }
231
+ if (lastSavedData === null || saveStatus === 'saving') return;
232
+ if (JSON.stringify(data) !== lastSavedData) {
220
233
  saveStatus = 'unsaved';
221
- scheduleAutosave();
222
234
  }
223
235
  });
224
236
 
@@ -226,19 +238,25 @@
226
238
  if ((e.ctrlKey || e.metaKey) && e.key === 's') {
227
239
  e.preventDefault();
228
240
  if (isArchived) return;
229
- if (autosaveTimer) {
230
- clearTimeout(autosaveTimer);
231
- autosaveTimer = null;
232
- }
233
- performAutosave();
241
+ saveDraft();
234
242
  }
235
243
  }
236
244
  window.addEventListener('keydown', handleKeydown);
237
245
 
246
+ function handleBeforeUnload(e: BeforeUnloadEvent) {
247
+ if (saveStatus === 'unsaved' && !isArchived) {
248
+ e.preventDefault();
249
+ e.returnValue = '';
250
+ }
251
+ }
252
+ window.addEventListener('beforeunload', handleBeforeUnload);
253
+
238
254
  return () => {
255
+ clearTimeout(settleQuietTimer);
256
+ clearTimeout(settleMaxTimer);
239
257
  unsub();
240
258
  window.removeEventListener('keydown', handleKeydown);
241
- if (autosaveTimer) clearTimeout(autosaveTimer);
259
+ window.removeEventListener('beforeunload', handleBeforeUnload);
242
260
  };
243
261
  });
244
262
 
@@ -255,14 +273,8 @@
255
273
  }
256
274
 
257
275
  async function onSave(type: UpdateEntryVersionCommandType, scheduledAt?: Date) {
258
- // Cancel pending autosave
259
- if (autosaveTimer) {
260
- clearTimeout(autosaveTimer);
261
- autosaveTimer = null;
262
- }
263
-
264
276
  // Unpublish — skip validation, call remote directly
265
- if (type === 'cancel-published') {
277
+ if (type === 'cancel-published' || type === 'cancel-scheduled') {
266
278
  saveStatus = 'saving';
267
279
  try {
268
280
  await remotes.updateEntryVersionCommand({
@@ -271,12 +283,12 @@
271
283
  data: get(form.form),
272
284
  type
273
285
  });
274
- saveStatus = 'saved';
275
- toast.success(lang[interfaceLanguage.current].unpublishToast);
276
-
277
- setTimeout(() => {
278
- if (saveStatus === 'saved') saveStatus = 'idle';
279
- }, 3000);
286
+ markSaved();
287
+ toast.success(
288
+ type === 'cancel-scheduled'
289
+ ? lang[interfaceLanguage.current].cancelScheduledToast
290
+ : lang[interfaceLanguage.current].unpublishToast
291
+ );
280
292
  } catch (e) {
281
293
  console.error('Unpublish error:', e);
282
294
  saveStatus = 'error';
@@ -302,8 +314,7 @@
302
314
  scheduledAt
303
315
  });
304
316
 
305
- lastSavedData = JSON.stringify(validatedForm.data);
306
- saveStatus = 'saved';
317
+ markSaved();
307
318
 
308
319
  const toastMsg =
309
320
  type === 'published-now'
@@ -311,25 +322,57 @@
311
322
  : lang[interfaceLanguage.current].scheduledToast;
312
323
  toast.success(toastMsg);
313
324
 
314
- setTimeout(() => {
315
- if (saveStatus === 'saved') saveStatus = 'idle';
316
- }, 3000);
325
+ // Publishing/scheduling from a pinned ?version (e.g. a newer draft)
326
+ // leaves the editor on that version, so the "editing an unpublished
327
+ // draft" banner stays. Drop the param so the editor resolves back to
328
+ // the canonical (published) version for the language.
329
+ const url = new URL(window.location.href);
330
+ if (url.searchParams.has('version')) {
331
+ url.searchParams.delete('version');
332
+ goto(url.pathname + url.search);
333
+ }
317
334
  } catch (e) {
318
335
  console.error('Publish error:', e);
319
336
  saveStatus = 'error';
320
- toast.error(lang[interfaceLanguage.current].saveFailed, {
321
- description: e instanceof Error ? e.message : undefined
322
- });
323
- }
324
- } else {
325
- const errors = flattenErrors(validatedForm.errors, getFieldsFromConfig(collection));
326
- validationErrors = errors;
337
+ const body = (e as { body?: { message?: string; code?: string; path?: string } })
338
+ ?.body;
339
+ const message =
340
+ body?.message ?? (e instanceof Error ? e.message : undefined);
341
+
342
+ // Server-side validation that pinpoints a field (e.g. SLUG_DUPLICATE)
343
+ // surface it both inline (form.errors → Form.FieldErrors) and in
344
+ // the error summary banner.
345
+ if (body?.path && body.message) {
346
+ const path = body.path;
347
+ const msg = body.message;
348
+ form.errors.update(($errors) => {
349
+ const parts = path.split('.');
350
+ const root = $errors as unknown as Record<string, unknown>;
351
+ let cursor = root;
352
+ for (let i = 0; i < parts.length - 1; i++) {
353
+ const key = parts[i];
354
+ if (typeof cursor[key] !== 'object' || cursor[key] === null) {
355
+ cursor[key] = {};
356
+ }
357
+ cursor = cursor[key] as Record<string, unknown>;
358
+ }
359
+ cursor[parts[parts.length - 1]] = [msg];
360
+ return $errors;
361
+ });
362
+ validationErrors = [{ path, message: msg }];
363
+ }
327
364
 
328
- // Scroll to first errored field
329
- const firstErrorKey = Object.keys(validatedForm.errors).find((k) => k !== '_errors');
330
- if (firstErrorKey) {
331
- scrollToIssue(firstErrorKey);
365
+ toast.error(message ?? lang[interfaceLanguage.current].saveFailed);
332
366
  }
367
+ } else {
368
+ // The error summary takes focus and scrolls itself into view; the user
369
+ // reviews the full list and clicks an entry to jump to a field.
370
+ validationErrors = flattenFormErrors(
371
+ validatedForm.errors as Record<string, unknown>,
372
+ getFieldsFromConfig(collection),
373
+ validatedForm.data,
374
+ interfaceLanguage.current
375
+ );
333
376
  }
334
377
  }
335
378
 
@@ -338,6 +381,9 @@
338
381
  $effect(() => {
339
382
  breadcrumbs.state = isCollection(collection)
340
383
  ? [
384
+ {
385
+ label: sidebarLang[interfaceLanguage.current].collections.title
386
+ },
341
387
  {
342
388
  label:
343
389
  getLocalizedLabel(collection.labels?.plural, interfaceLanguage.current) ||
@@ -345,7 +391,13 @@
345
391
  href: `/admin/collections/${collection.slug}`
346
392
  },
347
393
  {
348
- label: getRawCollectionEntryLabel(entry, collection, contentLanguage.current)
394
+ // breadcrumb title follows the UI language (with cross-language fallback);
395
+ // untitled entries show a placeholder instead of the raw id
396
+ label: getCollectionEntryDisplayLabel(
397
+ entry,
398
+ collection,
399
+ interfaceLanguage.current
400
+ )
349
401
  }
350
402
  ]
351
403
  : [
@@ -520,14 +572,25 @@
520
572
 
521
573
  const draftVersionId = $derived(currentDraftVersion?.id ?? savedDraftVersionId);
522
574
 
523
- const scrollToIssue = (fieldSlug: string) => {
524
- const fieldEl = document.querySelector(`[data-field-path="${fieldSlug}"]`);
575
+ const scrollToIssue = async (fieldPath: string) => {
576
+ // Resolve the deepest rendered ancestor of the (possibly nested) path
577
+ // a leaf like `highlights.0.title` lives inside block/layout accordions
578
+ // that must be expanded before it can be measured / scrolled to.
579
+ const fieldEl = findFieldPathElement(fieldPath);
525
580
  if (fieldEl) {
526
- fieldEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
581
+ // Reveal the tab / accordion the field lives on (no-op if not nested
582
+ // inside one) before measuring geometry / scrolling. Tabs first so
583
+ // nested accordion-in-tab works in one pass; both are idempotent.
584
+ activateContainingTabs(fieldEl);
585
+ const openedAccordion = activateContainingAccordions(fieldEl);
586
+ await tick();
587
+ // A just-opened accordion expands via a ~200ms grid-template-rows
588
+ // transition; wait it out so the field's final position is settled
589
+ // before scrolling (otherwise the scroll lands mid-animation).
590
+ if (openedAccordion) await new Promise((resolve) => setTimeout(resolve, 240));
591
+ scrollIntoViewWithin(fieldEl);
527
592
  const proseMirror = fieldEl.querySelector('.ProseMirror') as HTMLElement | null;
528
- if (proseMirror) {
529
- proseMirror.focus();
530
- }
593
+ proseMirror?.focus({ preventScroll: true });
531
594
  }
532
595
  };
533
596
 
@@ -544,30 +607,74 @@
544
607
  const t = $derived(lang[interfaceLanguage.current]);
545
608
  const isHybrid = $derived(hybridContext.mode === 'hybrid' && !!collection.previewUrl);
546
609
 
610
+ const errorPaths = $derived(new Set(validationErrors.map((e) => e.path)));
611
+
612
+ let copyFromLangOpen = $state(false);
613
+ let pendingCopyLang = $state<string | null>(null);
614
+
547
615
  function onCopyFromLang(sourceLang: string) {
548
616
  const sourceVersion =
549
617
  entry.draftVersions[sourceLang] ?? entry.publishedVersions[sourceLang];
550
618
  if (!sourceVersion?.data) return;
619
+ pendingCopyLang = sourceLang;
620
+ copyFromLangOpen = true;
621
+ }
551
622
 
552
- const confirmMsg =
553
- interfaceLanguage.current === 'pl'
554
- ? `Nadpisze aktualne dane danymi z wersji ${sourceLang.toUpperCase()}. Kontynuować?`
555
- : `This will overwrite current data with data from ${sourceLang.toUpperCase()} version. Continue?`;
556
-
557
- if (!confirm(confirmMsg)) return;
558
-
559
- form.form.set(sourceVersion.data);
623
+ function performCopyFromLang() {
624
+ if (!pendingCopyLang) return;
625
+ const sourceVersion =
626
+ entry.draftVersions[pendingCopyLang] ?? entry.publishedVersions[pendingCopyLang];
627
+ if (sourceVersion?.data) {
628
+ form.form.set(sourceVersion.data);
629
+ }
630
+ pendingCopyLang = null;
560
631
  }
632
+
633
+ const copyDialogText = $derived.by(() => {
634
+ const target = contentLanguage.current.toUpperCase();
635
+ const source = (pendingCopyLang ?? '').toUpperCase();
636
+ if (interfaceLanguage.current === 'pl') {
637
+ return {
638
+ title: `Skopiować treść z ${source}?`,
639
+ description: `Aktualne dane w wersji ${target} zostaną zastąpione kopią z wersji ${source}. Możesz cofnąć tę akcję, dopóki nie zapiszesz zmian.`,
640
+ confirmLabel: 'Skopiuj'
641
+ };
642
+ }
643
+ return {
644
+ title: `Copy content from ${source}?`,
645
+ description: `Current data in the ${target} version will be replaced with a copy from ${source}. You can revert as long as you don't save.`,
646
+ confirmLabel: 'Copy'
647
+ };
648
+ });
561
649
  </script>
562
650
 
651
+ <svelte:window
652
+ onbeforeunload={(e) => {
653
+ if (saveStatus === 'unsaved') {
654
+ e.preventDefault();
655
+ return '';
656
+ }
657
+ }}
658
+ />
659
+
660
+ <ConfirmationDialog
661
+ bind:open={copyFromLangOpen}
662
+ title={copyDialogText.title}
663
+ description={copyDialogText.description}
664
+ confirmLabel={copyDialogText.confirmLabel}
665
+ variant="default"
666
+ onConfirm={performCopyFromLang}
667
+ />
668
+
563
669
  <div class={isHybrid ? 'flex h-full flex-col overflow-hidden' : ''}>
564
670
  <EntryHeader
565
671
  {entry}
566
672
  version={editingEntry}
567
673
  {onSave}
568
- onSaveDraft={performAutosave}
674
+ onSaveDraft={saveDraft}
569
675
  {onArchive}
570
676
  {saveStatus}
677
+ {lastSavedAt}
571
678
  {isArchived}
572
679
  fields={getFieldsFromConfig(collection)}
573
680
  getFormData={() => get(form.form)}
@@ -576,29 +683,13 @@
576
683
  />
577
684
 
578
685
  {#if validationErrors.length > 0}
579
- <div
580
- role="alert"
581
- aria-live="assertive"
582
- class="flex shrink-0 items-start gap-3 border-b border-[var(--error)]/20 bg-[#FDF0F0] px-6 py-3 text-sm text-[var(--error)]"
583
- >
584
- <AlertCircle class="mt-0.5 size-4 shrink-0" />
585
- <div class="min-w-0 flex-1">
586
- <p class="font-semibold">{t.cannotPublish}</p>
587
- <p class="text-xs opacity-80">{t.validationHint}</p>
588
- <ul class="mt-1 text-xs">
589
- {#each validationErrors.slice(0, 5) as error}
590
- <li>— {error}</li>
591
- {/each}
592
- </ul>
593
- </div>
594
- <button
595
- type="button"
596
- onclick={() => (validationErrors = [])}
597
- class="shrink-0 rounded p-0.5 opacity-60 hover:opacity-100"
598
- aria-label="Zamknij"
599
- >
600
- <XIcon class="size-4" />
601
- </button>
686
+ <div class="border-b px-6 py-3">
687
+ <FormErrorSummary
688
+ errors={validationErrors}
689
+ title={t.cannotPublish}
690
+ onFocusField={(path) => scrollToIssue(path)}
691
+ onDismiss={() => (validationErrors = [])}
692
+ />
602
693
  </div>
603
694
  {/if}
604
695
 
@@ -673,12 +764,25 @@
673
764
  {/await}
674
765
  {/snippet}
675
766
  {#snippet formPanel()}
676
- <EntryForm
677
- {form}
678
- {entry}
679
- focusedPath={hybridContext.focusedPath}
680
- onPathSelect={(path) => (hybridContext.focusedPath = path)}
681
- />
767
+ {#if hasLayout(collection)}
768
+ <EntryForm
769
+ {form}
770
+ {entry}
771
+ {errorPaths}
772
+ focusedPath={hybridContext.focusedPath}
773
+ onPathSelect={(path) => (hybridContext.focusedPath = path)}
774
+ />
775
+ {:else}
776
+ <div class="p-3 lg:p-4">
777
+ <EntryForm
778
+ {form}
779
+ {entry}
780
+ {errorPaths}
781
+ focusedPath={hybridContext.focusedPath}
782
+ onPathSelect={(path) => (hybridContext.focusedPath = path)}
783
+ />
784
+ </div>
785
+ {/if}
682
786
  {/snippet}
683
787
  </HybridLayout>
684
788
  {:catch}
@@ -686,13 +790,13 @@
686
790
  {/await}
687
791
  {:else if hasLayout(collection)}
688
792
  <div class="overflow-y-auto" style="scroll-padding-top: 48px;">
689
- <EntryForm {form} {entry} />
793
+ <EntryForm {form} {entry} {errorPaths} />
690
794
  </div>
691
795
  {:else}
692
796
  <div class="flex items-stretch justify-center" style="scroll-padding-top: 48px;">
693
797
  <div class="max-w-2xl grow p-4 lg:p-6">
694
798
  <div class="bg-card rounded-2xl border p-4 shadow-sm lg:p-6">
695
- <EntryForm {form} {entry} />
799
+ <EntryForm {form} {entry} {errorPaths} />
696
800
  </div>
697
801
  </div>
698
802
  </div>