includio-cms 0.1.4 → 0.5.1

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 (310) hide show
  1. package/CHANGELOG.md +83 -0
  2. package/ROADMAP.md +26 -13
  3. package/dist/admin/api/accept-invite.js +1 -5
  4. package/dist/admin/api/invite.js +7 -16
  5. package/dist/admin/client/account/account-page.svelte +20 -50
  6. package/dist/admin/client/account/lang.d.ts +15 -23
  7. package/dist/admin/client/account/lang.js +51 -67
  8. package/dist/admin/client/account/preferences-section.svelte +26 -84
  9. package/dist/admin/client/account/profile-section.svelte +60 -40
  10. package/dist/admin/client/account/schema.d.ts +11 -3
  11. package/dist/admin/client/account/schema.js +25 -16
  12. package/dist/admin/client/account/security-section.svelte +139 -105
  13. package/dist/admin/client/account/sessions-section.svelte +35 -34
  14. package/dist/admin/client/admin/admin-after-login-layout-content.svelte +3 -5
  15. package/dist/admin/client/admin/admin-layout.svelte +3 -2
  16. package/dist/admin/client/admin/admin-preloader.svelte +36 -0
  17. package/dist/admin/client/admin/admin-preloader.svelte.d.ts +18 -0
  18. package/dist/admin/client/admin/dashboard-page.svelte +35 -67
  19. package/dist/admin/client/collection/a11y-score-cell.svelte +45 -0
  20. package/dist/admin/client/collection/a11y-score-cell.svelte.d.ts +6 -0
  21. package/dist/admin/client/collection/bulk-actions-bar.svelte +100 -0
  22. package/dist/admin/client/collection/bulk-actions-bar.svelte.d.ts +10 -0
  23. package/dist/admin/client/collection/collection-entries.svelte +277 -258
  24. package/dist/admin/client/collection/collection-view.svelte.d.ts +4 -3
  25. package/dist/admin/client/collection/collection-view.svelte.js +9 -5
  26. package/dist/admin/client/collection/collection.svelte +22 -12
  27. package/dist/admin/client/collection/data-table.svelte +50 -39
  28. package/dist/admin/client/collection/data-table.svelte.d.ts +1 -0
  29. package/dist/admin/client/collection/date-cell.svelte +7 -5
  30. package/dist/admin/client/collection/date-cell.svelte.d.ts +1 -1
  31. package/dist/admin/client/collection/empty-state.svelte +28 -0
  32. package/dist/admin/client/collection/empty-state.svelte.d.ts +9 -0
  33. package/dist/admin/client/collection/entry-link.svelte +10 -4
  34. package/dist/admin/client/collection/entry-link.svelte.d.ts +1 -0
  35. package/dist/admin/client/collection/grid-view.svelte +21 -23
  36. package/dist/admin/client/collection/grid-view.svelte.d.ts +1 -2
  37. package/dist/admin/client/collection/row-actions.svelte +69 -0
  38. package/dist/admin/client/collection/row-actions.svelte.d.ts +10 -0
  39. package/dist/admin/client/collection/status-badge.svelte +7 -8
  40. package/dist/admin/client/collection/table-pagination.svelte +122 -79
  41. package/dist/admin/client/collection/table-pagination.svelte.d.ts +1 -0
  42. package/dist/admin/client/collection/table-toolbar.svelte +108 -88
  43. package/dist/admin/client/collection/table-toolbar.svelte.d.ts +8 -9
  44. package/dist/admin/client/entry/entry-form.svelte +109 -1
  45. package/dist/admin/client/entry/entry-header.svelte +143 -37
  46. package/dist/admin/client/entry/entry-header.svelte.d.ts +8 -0
  47. package/dist/admin/client/entry/entry.svelte +272 -61
  48. package/dist/admin/client/entry/header/a11y-validator.d.ts +47 -0
  49. package/dist/admin/client/entry/header/a11y-validator.js +352 -0
  50. package/dist/admin/client/entry/header/publish-panel.svelte +531 -129
  51. package/dist/admin/client/entry/header/publish-panel.svelte.d.ts +4 -0
  52. package/dist/admin/client/entry/header/save-indicator.svelte +33 -23
  53. package/dist/admin/client/entry/header/schedule-popover.svelte +1 -1
  54. package/dist/admin/client/entry/header/status-badge.svelte +25 -118
  55. package/dist/admin/client/entry/header/version-history-sheet.svelte +322 -98
  56. package/dist/admin/client/form/form-submission/form-submission.svelte +271 -83
  57. package/dist/admin/client/form/form-submission/submission-field.svelte +12 -12
  58. package/dist/admin/client/form/form-submissions.svelte +421 -139
  59. package/dist/admin/client/form/submission-link.svelte +8 -2
  60. package/dist/admin/client/form/submission-link.svelte.d.ts +1 -0
  61. package/dist/admin/client/form/submission-status-badge.svelte +18 -4
  62. package/dist/admin/client/form/submission-status-badge.svelte.d.ts +1 -0
  63. package/dist/admin/client/login/lang.d.ts +32 -0
  64. package/dist/admin/client/login/lang.js +66 -2
  65. package/dist/admin/client/login/login-form.svelte +237 -95
  66. package/dist/admin/client/login/login-form.svelte.d.ts +2 -17
  67. package/dist/admin/client/login/login-page.svelte +34 -98
  68. package/dist/admin/client/login/reset-password-page.svelte +235 -0
  69. package/dist/admin/client/login/reset-password-page.svelte.d.ts +4 -0
  70. package/dist/admin/client/login/schema.d.ts +15 -0
  71. package/dist/admin/client/login/schema.js +21 -0
  72. package/dist/admin/client/users/accept-invite-page.svelte +166 -37
  73. package/dist/admin/client/users/create-user-dialog.svelte +15 -7
  74. package/dist/admin/client/users/delete-user-dialog.svelte +81 -16
  75. package/dist/admin/client/users/delete-user-dialog.svelte.d.ts +4 -1
  76. package/dist/admin/client/users/edit-user-dialog.svelte +3 -0
  77. package/dist/admin/client/users/invite-user-dialog.svelte +16 -3
  78. package/dist/admin/client/users/lang.d.ts +27 -0
  79. package/dist/admin/client/users/lang.js +64 -10
  80. package/dist/admin/client/users/pending-invitations.svelte +59 -23
  81. package/dist/admin/client/users/users-page.svelte +471 -72
  82. package/dist/admin/components/accessibility/accessibility-overview.svelte +2 -7
  83. package/dist/admin/components/dashboard/a11y-gauge.svelte +90 -0
  84. package/dist/admin/components/dashboard/a11y-gauge.svelte.d.ts +18 -0
  85. package/dist/admin/components/dashboard/accessibility-hub.svelte +13 -12
  86. package/dist/admin/components/dashboard/changelog-dialog.svelte +157 -0
  87. package/dist/admin/components/dashboard/changelog-dialog.svelte.d.ts +6 -0
  88. package/dist/admin/components/dashboard/form-submissions-widget.svelte +71 -113
  89. package/dist/admin/components/dashboard/index.d.ts +4 -2
  90. package/dist/admin/components/dashboard/index.js +4 -2
  91. package/dist/admin/components/dashboard/orphaned-entries-notice.svelte +240 -0
  92. package/dist/admin/components/dashboard/orphaned-entries-notice.svelte.d.ts +13 -0
  93. package/dist/admin/components/dashboard/recent-activity.svelte +53 -75
  94. package/dist/admin/components/dashboard/recent-entries.svelte +94 -0
  95. package/dist/admin/components/dashboard/recent-entries.svelte.d.ts +18 -0
  96. package/dist/admin/components/dashboard/stat-card.svelte +2 -2
  97. package/dist/admin/components/dashboard/tip-of-the-day.svelte +109 -0
  98. package/dist/admin/components/dashboard/tip-of-the-day.svelte.d.ts +3 -0
  99. package/dist/admin/components/dashboard/welcome-header.svelte +45 -0
  100. package/dist/admin/components/dashboard/welcome-header.svelte.d.ts +3 -0
  101. package/dist/admin/components/fields/{array-field.svelte → blocks-field.svelte} +4 -4
  102. package/dist/admin/components/fields/{array-field.svelte.d.ts → blocks-field.svelte.d.ts} +5 -5
  103. package/dist/admin/components/fields/content-field.svelte +27 -0
  104. package/dist/admin/components/fields/content-field.svelte.d.ts +31 -0
  105. package/dist/admin/components/fields/field-renderer.svelte +9 -7
  106. package/dist/admin/components/fields/image-field.svelte +2 -2
  107. package/dist/admin/components/fields/media-field.svelte +2 -2
  108. package/dist/admin/components/fields/seo-field.svelte +205 -25
  109. package/dist/admin/components/fields/simple-array-field.svelte +289 -0
  110. package/dist/admin/components/fields/simple-array-field.svelte.d.ts +30 -0
  111. package/dist/admin/components/fields/slug-field.svelte +3 -2
  112. package/dist/admin/components/fields/standalone-field-renderer.svelte +148 -0
  113. package/dist/admin/components/fields/standalone-field-renderer.svelte.d.ts +9 -0
  114. package/dist/admin/components/fields/text-field-wrapper.svelte +147 -3
  115. package/dist/admin/components/fields/text-field-wrapper.svelte.d.ts +2 -2
  116. package/dist/admin/components/fields/url-field.svelte +5 -4
  117. package/dist/admin/components/layout/app-sidebar.svelte +27 -24
  118. package/dist/admin/components/layout/lang.d.ts +6 -0
  119. package/dist/admin/components/layout/lang.js +13 -1
  120. package/dist/admin/components/layout/layout-renderer.svelte +352 -0
  121. package/dist/admin/components/layout/layout-renderer.svelte.d.ts +14 -0
  122. package/dist/admin/components/layout/nav-breadcrumbs.svelte +4 -4
  123. package/dist/admin/components/layout/nav-collections.svelte +65 -36
  124. package/dist/admin/components/layout/nav-footer.svelte +38 -0
  125. package/dist/admin/components/layout/nav-footer.svelte.d.ts +3 -0
  126. package/dist/admin/components/layout/nav-forms.svelte +55 -30
  127. package/dist/admin/components/layout/nav-main.svelte +14 -52
  128. package/dist/admin/components/layout/nav-search.svelte +4 -3
  129. package/dist/admin/components/layout/nav-singletons.svelte +59 -17
  130. package/dist/admin/components/layout/nav-singletons.svelte.d.ts +17 -8
  131. package/dist/admin/components/layout/site-header.svelte +74 -13
  132. package/dist/admin/components/media/alt-input.svelte +32 -22
  133. package/dist/admin/components/media/bulk-action-bar.svelte +139 -150
  134. package/dist/admin/components/media/file/file-details.svelte +299 -217
  135. package/dist/admin/components/media/file/file-miniature.svelte +54 -41
  136. package/dist/admin/components/media/file/file-miniature.svelte.d.ts +1 -0
  137. package/dist/admin/components/media/file/file-preview.svelte +1 -1
  138. package/dist/admin/components/media/file-upload.svelte +24 -26
  139. package/dist/admin/components/media/files-list.svelte +112 -40
  140. package/dist/admin/components/media/files-list.svelte.d.ts +2 -0
  141. package/dist/admin/components/media/focal-point-input.svelte +122 -26
  142. package/dist/admin/components/media/media-library.svelte +127 -70
  143. package/dist/admin/components/media/media-search.svelte +6 -6
  144. package/dist/admin/components/media/media-sort.svelte +3 -1
  145. package/dist/admin/components/media/multi-file-summary.svelte +88 -68
  146. package/dist/admin/components/media/tag-combobox.svelte +141 -66
  147. package/dist/admin/components/media/tag-combobox.svelte.d.ts +1 -0
  148. package/dist/admin/components/media/tag-sidebar.svelte +139 -121
  149. package/dist/admin/components/tiptap/FigureNodeView.svelte +144 -15
  150. package/dist/admin/components/tiptap/InlineBlockNodeView.svelte +254 -0
  151. package/dist/admin/components/tiptap/InlineBlockNodeView.svelte.d.ts +4 -0
  152. package/dist/admin/components/tiptap/SlashCommandPopup.svelte +212 -0
  153. package/dist/admin/components/tiptap/SlashCommandPopup.svelte.d.ts +8 -0
  154. package/dist/admin/components/tiptap/content-editor.svelte +280 -0
  155. package/dist/admin/components/tiptap/content-editor.svelte.d.ts +9 -0
  156. package/dist/admin/components/tiptap/editor-toolbar.svelte +230 -0
  157. package/dist/admin/components/tiptap/editor-toolbar.svelte.d.ts +16 -0
  158. package/dist/admin/components/tiptap/heading-a11y-plugin.d.ts +2 -0
  159. package/dist/admin/components/tiptap/heading-a11y-plugin.js +67 -0
  160. package/dist/admin/components/tiptap/image-dialog.svelte +172 -11
  161. package/dist/admin/components/tiptap/inline-block-node.d.ts +19 -0
  162. package/dist/admin/components/tiptap/inline-block-node.js +98 -0
  163. package/dist/admin/components/tiptap/link-dialog.svelte +9 -4
  164. package/dist/admin/components/tiptap/slash-command.d.ts +17 -0
  165. package/dist/admin/components/tiptap/slash-command.js +181 -0
  166. package/dist/admin/components/tiptap/structured-content-utils.d.ts +21 -0
  167. package/dist/admin/components/tiptap/structured-content-utils.js +150 -0
  168. package/dist/admin/components/tiptap/tiptap-editor.svelte +18 -190
  169. package/dist/admin/email/invite-template.d.ts +8 -0
  170. package/dist/admin/email/invite-template.js +99 -0
  171. package/dist/admin/email/reset-password-template.d.ts +7 -0
  172. package/dist/admin/email/reset-password-template.js +96 -0
  173. package/dist/admin/remote/ai.remote.d.ts +1 -0
  174. package/dist/admin/remote/ai.remote.js +4 -1
  175. package/dist/admin/remote/entry.remote.d.ts +9 -0
  176. package/dist/admin/remote/entry.remote.js +58 -8
  177. package/dist/admin/remote/preview.remote.js +2 -1
  178. package/dist/admin/shared/password-schema.d.ts +5 -0
  179. package/dist/admin/shared/password-schema.js +10 -0
  180. package/dist/admin/state/content-language.svelte.d.ts +3 -0
  181. package/dist/admin/state/content-language.svelte.js +8 -0
  182. package/dist/admin/styles/admin.css +1530 -151
  183. package/dist/admin/utils/formatDate.d.ts +1 -0
  184. package/dist/admin/utils/formatDate.js +8 -0
  185. package/dist/admin/utils/roleLabel.d.ts +2 -0
  186. package/dist/admin/utils/roleLabel.js +13 -0
  187. package/dist/admin/utils/translationStatus.d.ts +17 -0
  188. package/dist/admin/utils/translationStatus.js +134 -0
  189. package/dist/ai-claude/index.d.ts +2 -0
  190. package/dist/ai-claude/index.js +56 -0
  191. package/dist/cms/runtime/api.d.ts +6 -1
  192. package/dist/cms/runtime/api.js +3 -0
  193. package/dist/cms/runtime/schemas.d.ts +9 -1
  194. package/dist/cms/runtime/schemas.js +8 -0
  195. package/dist/cms/runtime/types.d.ts +82 -10
  196. package/dist/cms/runtime/types.js +4 -0
  197. package/dist/components/ui/accordion/accordion.stories.svelte +39 -0
  198. package/dist/components/ui/accordion/accordion.stories.svelte.d.ts +27 -0
  199. package/dist/components/ui/alert/alert.stories.svelte +53 -0
  200. package/dist/components/ui/alert/alert.stories.svelte.d.ts +27 -0
  201. package/dist/components/ui/alert/alert.svelte +5 -0
  202. package/dist/components/ui/alert/alert.svelte.d.ts +9 -0
  203. package/dist/components/ui/avatar/avatar.stories.svelte +16 -0
  204. package/dist/components/ui/avatar/avatar.stories.svelte.d.ts +27 -0
  205. package/dist/components/ui/badge/badge.stories.svelte +33 -0
  206. package/dist/components/ui/badge/badge.stories.svelte.d.ts +27 -0
  207. package/dist/components/ui/breadcrumb/breadcrumb.stories.svelte +33 -0
  208. package/dist/components/ui/breadcrumb/breadcrumb.stories.svelte.d.ts +27 -0
  209. package/dist/components/ui/button/button.stories.svelte +43 -0
  210. package/dist/components/ui/button/button.stories.svelte.d.ts +27 -0
  211. package/dist/components/ui/button/button.svelte +1 -2
  212. package/dist/components/ui/button/button.svelte.d.ts +0 -3
  213. package/dist/components/ui/button-group/button-group-separator.svelte.d.ts +1 -1
  214. package/dist/components/ui/card/card.stories.svelte +42 -0
  215. package/dist/components/ui/card/card.stories.svelte.d.ts +27 -0
  216. package/dist/components/ui/command/command.stories.svelte +51 -0
  217. package/dist/components/ui/command/command.stories.svelte.d.ts +27 -0
  218. package/dist/components/ui/dialog/dialog.stories.svelte +29 -0
  219. package/dist/components/ui/dialog/dialog.stories.svelte.d.ts +27 -0
  220. package/dist/components/ui/field/field-label.svelte.d.ts +1 -1
  221. package/dist/components/ui/field/field.stories.svelte +21 -0
  222. package/dist/components/ui/field/field.stories.svelte.d.ts +27 -0
  223. package/dist/components/ui/input/input.stories.svelte +40 -0
  224. package/dist/components/ui/input/input.stories.svelte.d.ts +27 -0
  225. package/dist/components/ui/input/input.svelte +2 -4
  226. package/dist/components/ui/item/item-separator.svelte.d.ts +1 -1
  227. package/dist/components/ui/label/label.stories.svelte +20 -0
  228. package/dist/components/ui/label/label.stories.svelte.d.ts +27 -0
  229. package/dist/components/ui/popover/popover.stories.svelte +29 -0
  230. package/dist/components/ui/popover/popover.stories.svelte.d.ts +27 -0
  231. package/dist/components/ui/select/select-group-heading.svelte.d.ts +1 -1
  232. package/dist/components/ui/select/select.stories.svelte +23 -0
  233. package/dist/components/ui/select/select.stories.svelte.d.ts +27 -0
  234. package/dist/components/ui/separator/separator.stories.svelte +24 -0
  235. package/dist/components/ui/separator/separator.stories.svelte.d.ts +27 -0
  236. package/dist/components/ui/sheet/sheet.stories.svelte +29 -0
  237. package/dist/components/ui/sheet/sheet.stories.svelte.d.ts +27 -0
  238. package/dist/components/ui/sidebar/sidebar-group.svelte +3 -3
  239. package/dist/components/ui/sidebar/sidebar-group.svelte.d.ts +2 -2
  240. package/dist/components/ui/sidebar/sidebar-menu-button.svelte +28 -30
  241. package/dist/components/ui/sidebar/sidebar-menu-button.svelte.d.ts +7 -7
  242. package/dist/components/ui/sidebar/sidebar-separator.svelte.d.ts +1 -1
  243. package/dist/components/ui/sidebar/sidebar-trigger.svelte +4 -4
  244. package/dist/components/ui/sonner/sonner.stories.svelte +22 -0
  245. package/dist/components/ui/sonner/sonner.stories.svelte.d.ts +26 -0
  246. package/dist/components/ui/sonner/sonner.svelte +8 -2
  247. package/dist/components/ui/sonner/toast-demo.svelte +29 -0
  248. package/dist/components/ui/sonner/toast-demo.svelte.d.ts +6 -0
  249. package/dist/components/ui/textarea/textarea.stories.svelte +22 -0
  250. package/dist/components/ui/textarea/textarea.stories.svelte.d.ts +27 -0
  251. package/dist/components/ui/textarea/textarea.svelte +0 -2
  252. package/dist/components/ui/toggle/toggle.stories.svelte +22 -0
  253. package/dist/components/ui/toggle/toggle.stories.svelte.d.ts +27 -0
  254. package/dist/components/ui/toggle-group/toggle-group.stories.svelte +17 -0
  255. package/dist/components/ui/toggle-group/toggle-group.stories.svelte.d.ts +27 -0
  256. package/dist/components/ui/tooltip/tooltip.stories.svelte +26 -0
  257. package/dist/components/ui/tooltip/tooltip.stories.svelte.d.ts +27 -0
  258. package/dist/core/fields/fieldSchemaToTs.d.ts +1 -0
  259. package/dist/core/fields/fieldSchemaToTs.js +133 -1
  260. package/dist/core/fields/layoutUtils.d.ts +17 -0
  261. package/dist/core/fields/layoutUtils.js +149 -0
  262. package/dist/core/fields/structuredToHtml.d.ts +9 -0
  263. package/dist/core/fields/structuredToHtml.js +161 -0
  264. package/dist/core/server/entries/operations/create.js +2 -1
  265. package/dist/core/server/entries/operations/get.js +10 -7
  266. package/dist/core/server/entries/operations/update.d.ts +3 -0
  267. package/dist/core/server/entries/operations/update.js +30 -2
  268. package/dist/core/server/fields/queryStructuredContent.d.ts +15 -0
  269. package/dist/core/server/fields/queryStructuredContent.js +65 -0
  270. package/dist/core/server/fields/resolveImageFields.js +51 -2
  271. package/dist/core/server/fields/resolveRelationFields.js +2 -2
  272. package/dist/core/server/fields/resolveRichtextLinks.js +80 -13
  273. package/dist/core/server/fields/resolveUrlFields.js +57 -6
  274. package/dist/core/server/fields/slugResolver.d.ts +10 -0
  275. package/dist/core/server/fields/slugResolver.js +34 -0
  276. package/dist/core/server/generator/fields.js +15 -4
  277. package/dist/core/server/generator/generator.js +3 -2
  278. package/dist/db-postgres/index.js +10 -6
  279. package/dist/files-local/index.js +126 -64
  280. package/dist/server/auth.d.ts +5 -0
  281. package/dist/server/auth.js +12 -1
  282. package/dist/sveltekit/components/structured-content.svelte +204 -0
  283. package/dist/sveltekit/components/structured-content.svelte.d.ts +21 -0
  284. package/dist/sveltekit/config.d.ts +13 -3
  285. package/dist/sveltekit/index.d.ts +3 -0
  286. package/dist/sveltekit/index.js +3 -0
  287. package/dist/sveltekit/server/handle.js +1 -0
  288. package/dist/types/config.d.ts +3 -0
  289. package/dist/types/entries.d.ts +3 -0
  290. package/dist/types/fields.d.ts +19 -2
  291. package/dist/types/index.d.ts +2 -0
  292. package/dist/types/index.js +2 -0
  293. package/dist/types/layout.d.ts +54 -0
  294. package/dist/types/layout.js +6 -0
  295. package/dist/types/structured-content.d.ts +63 -0
  296. package/dist/types/structured-content.js +1 -0
  297. package/dist/updates/0.1.5/index.d.ts +2 -0
  298. package/dist/updates/0.1.5/index.js +18 -0
  299. package/dist/updates/0.2.0/index.d.ts +2 -0
  300. package/dist/updates/0.2.0/index.js +11 -0
  301. package/dist/updates/0.2.2/index.d.ts +2 -0
  302. package/dist/updates/0.2.2/index.js +13 -0
  303. package/dist/updates/0.5.0/index.d.ts +2 -0
  304. package/dist/updates/0.5.0/index.js +14 -0
  305. package/dist/updates/0.5.1/index.d.ts +2 -0
  306. package/dist/updates/0.5.1/index.js +17 -0
  307. package/dist/updates/index.js +6 -1
  308. package/package.json +16 -9
  309. package/dist/admin/components/dashboard/updates-banner.svelte +0 -170
  310. package/dist/admin/components/dashboard/updates-banner.svelte.d.ts +0 -3
@@ -9,6 +9,10 @@
9
9
  import { getLocalizedLabel } from '../../utils/collectionLabel.js';
10
10
  import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
11
11
  import type { InterfaceLanguage } from '../../../types/languages.js';
12
+ import AlertCircle from '@tabler/icons-svelte/icons/alert-circle';
13
+ import ArchiveIcon from '@tabler/icons-svelte/icons/archive';
14
+ import XIcon from '@tabler/icons-svelte/icons/x';
15
+ import Button from '../../../components/ui/button/button.svelte';
12
16
  import { ElementSize, useDebounce } from 'runed';
13
17
  import { defaults, superForm, type SuperForm } from 'sveltekit-superforms';
14
18
  import { zod4, zod4Client } from 'sveltekit-superforms/adapters';
@@ -18,10 +22,12 @@
18
22
  import type { UpdateEntryVersionCommandType } from '../../../core/server/entries/operations/update.js';
19
23
  import { getRawCollectionEntryLabel } from '../../utils/entryLabel.js';
20
24
  import type { Field } from '../../../types/fields.js';
25
+ import { getFieldsFromConfig, hasLayout } from '../../../core/fields/layoutUtils.js';
21
26
  import type { ValidationErrors } from 'sveltekit-superforms';
22
27
  import { createHybridContext } from './hybrid/hybrid-context.svelte.js';
23
28
  import { onMount } from 'svelte';
24
29
  import { get } from 'svelte/store';
30
+ import { computeTranslationStatus } from '../../utils/translationStatus.js';
25
31
 
26
32
  const contentLanguage = getContentLanguage();
27
33
  const remotes = getRemotes();
@@ -56,7 +62,11 @@
56
62
  } else if (typeof value === 'object' && value !== null) {
57
63
  // Nested errors - recurse
58
64
  const nestedFields = field && 'fields' in field ? (field.fields as Field[]) : [];
59
- const nested = flattenErrors(value as ValidationErrors<Record<string, unknown>>, nestedFields, path);
65
+ const nested = flattenErrors(
66
+ value as ValidationErrors<Record<string, unknown>>,
67
+ nestedFields,
68
+ path
69
+ );
60
70
  if (nested.length > 0) {
61
71
  result.push(...nested.map((e) => `${label} > ${e}`));
62
72
  }
@@ -70,31 +80,55 @@
70
80
  InterfaceLanguage,
71
81
  {
72
82
  entryArchived: string;
83
+ entryRestored: string;
84
+ archivedBanner: string;
85
+ restore: string;
73
86
  saveToast: string;
74
- saveDraftToast: string;
75
87
  publishToast: string;
76
88
  scheduledToast: string;
89
+ unpublishToast: string;
77
90
  saveFailed: string;
78
91
  cannotPublish: string;
92
+ validationHint: string;
93
+ newerDraft: string;
94
+ switchToDraft: string;
95
+ editingDraft: string;
96
+ switchToPublished: string;
79
97
  }
80
98
  > = {
81
99
  en: {
82
100
  entryArchived: 'Entry archived successfully',
101
+ entryRestored: 'Entry restored successfully',
102
+ archivedBanner: 'This entry is archived. Restore it to edit.',
103
+ restore: 'Restore',
83
104
  saveToast: 'Entry saved successfully',
84
- saveDraftToast: 'Draft saved successfully',
85
105
  publishToast: 'Entry published successfully',
86
106
  scheduledToast: 'Entry scheduled successfully',
107
+ unpublishToast: 'Publication withdrawn',
87
108
  saveFailed: 'Save failed',
88
- cannotPublish: 'Cannot publish'
109
+ cannotPublish: 'Cannot publish',
110
+ validationHint: 'Fix the highlighted fields below to publish',
111
+ newerDraft: 'A newer unpublished draft exists',
112
+ switchToDraft: 'Switch to draft',
113
+ editingDraft: 'You are editing an unpublished draft',
114
+ switchToPublished: 'View published'
89
115
  },
90
116
  pl: {
91
117
  entryArchived: 'Wpis został zarchiwizowany pomyślnie',
118
+ entryRestored: 'Wpis został przywrócony',
119
+ archivedBanner: 'Ten wpis jest zarchiwizowany. Przywróć go, żeby edytować.',
120
+ restore: 'Przywróć',
92
121
  saveToast: 'Wpis został pomyślnie zapisany',
93
- saveDraftToast: 'Wersja robocza została pomyślnie zapisana',
94
122
  publishToast: 'Wpis został pomyślnie opublikowany',
95
123
  scheduledToast: 'Wpis został zaplanowany pomyślnie',
124
+ unpublishToast: 'Wycofano publikację',
96
125
  saveFailed: 'Błąd zapisu',
97
- cannotPublish: 'Nie można opublikować'
126
+ cannotPublish: 'Nie można opublikować',
127
+ validationHint: 'Popraw wyróżnione pola, żeby opublikować',
128
+ newerDraft: 'Istnieje nowszy nieopublikowany szkic',
129
+ switchToDraft: 'Przejdź do szkicu',
130
+ editingDraft: 'Edytujesz nieopublikowany szkic',
131
+ switchToPublished: 'Zobacz opublikowaną'
98
132
  }
99
133
  };
100
134
 
@@ -105,9 +139,13 @@
105
139
 
106
140
  let { entry, editingEntry }: Props = $props();
107
141
  let { collection } = entry;
142
+ const isArchived = $derived(!!entry.archivedAt);
108
143
 
109
144
  // Create form once at component level
110
- const collectionSchema = generateZodSchemaFromFields(collection.fields, contentLanguage.all);
145
+ const collectionSchema = generateZodSchemaFromFields(
146
+ getFieldsFromConfig(collection),
147
+ contentLanguage.all
148
+ );
111
149
  const form = superForm(defaults(editingEntry.data, zod4(collectionSchema)), {
112
150
  validators: zod4Client(collectionSchema),
113
151
  SPA: true,
@@ -115,15 +153,29 @@
115
153
  resetForm: false
116
154
  });
117
155
 
156
+ let validationErrors = $state<string[]>([]);
157
+
118
158
  // Autosave state
119
159
  type SaveStatus = 'idle' | 'saving' | 'saved' | 'unsaved' | 'error';
120
160
  let saveStatus = $state<SaveStatus>('idle');
121
- let lastSavedData = $state<string>(JSON.stringify(editingEntry.data));
161
+ let lastSavedData = $state<string>(JSON.stringify(get(form.form)));
122
162
  let autosaveTimer: ReturnType<typeof setTimeout> | null = null;
163
+ // Tracks draft version created via autosave while editing a published version
164
+ let savedDraftVersionId = $state<string | null>(null);
123
165
 
124
166
  const AUTOSAVE_DELAY = 30000; // 30 seconds
125
167
 
168
+ // Reactive translation status — must live here where we can subscribe to form.form store
169
+ const formStore = form.form;
170
+ const collectionFields = getFieldsFromConfig(collection);
171
+ const translationStatus = $derived.by(() => {
172
+ if (contentLanguage.all.length <= 1) return null;
173
+ const data = $formStore;
174
+ return computeTranslationStatus(data, collectionFields, contentLanguage.all);
175
+ });
176
+
126
177
  function scheduleAutosave() {
178
+ if (isArchived) return;
127
179
  if (autosaveTimer) {
128
180
  clearTimeout(autosaveTimer);
129
181
  }
@@ -133,22 +185,26 @@
133
185
  }
134
186
 
135
187
  async function performAutosave() {
188
+ if (isArchived) return;
136
189
  const currentFormData = get(form.form);
137
190
  const currentData = JSON.stringify(currentFormData);
138
191
  if (currentData === lastSavedData) {
139
- saveStatus = 'saved';
140
192
  return;
141
193
  }
142
194
 
143
195
  saveStatus = 'saving';
144
196
  try {
145
- await remotes.updateEntryVersionCommand({
197
+ const result = await remotes.updateEntryVersionCommand({
146
198
  entryId: entry.id,
147
199
  data: currentFormData,
148
200
  type: 'draft'
149
201
  });
150
202
  lastSavedData = currentData;
151
203
  saveStatus = 'saved';
204
+ // If we're editing the published version and saved a draft, track it for the banner
205
+ if (entry.publishedVersion && editingEntry.id === entry.publishedVersion.id && result?.id) {
206
+ savedDraftVersionId = result.id;
207
+ }
152
208
  // Reset to idle after 3s
153
209
  setTimeout(() => {
154
210
  if (saveStatus === 'saved') saveStatus = 'idle';
@@ -158,7 +214,7 @@
158
214
  }
159
215
  }
160
216
 
161
- // Track form changes for autosave
217
+ // Track form changes for autosave + Ctrl/Cmd+S shortcut
162
218
  onMount(() => {
163
219
  const unsub = form.form.subscribe((data) => {
164
220
  const currentData = JSON.stringify(data);
@@ -167,8 +223,23 @@
167
223
  scheduleAutosave();
168
224
  }
169
225
  });
226
+
227
+ function handleKeydown(e: KeyboardEvent) {
228
+ if ((e.ctrlKey || e.metaKey) && e.key === 's') {
229
+ e.preventDefault();
230
+ if (isArchived) return;
231
+ if (autosaveTimer) {
232
+ clearTimeout(autosaveTimer);
233
+ autosaveTimer = null;
234
+ }
235
+ performAutosave();
236
+ }
237
+ }
238
+ window.addEventListener('keydown', handleKeydown);
239
+
170
240
  return () => {
171
241
  unsub();
242
+ window.removeEventListener('keydown', handleKeydown);
172
243
  if (autosaveTimer) clearTimeout(autosaveTimer);
173
244
  };
174
245
  });
@@ -179,6 +250,12 @@
179
250
  goto(`/admin/collections/${collection.slug}`);
180
251
  }
181
252
 
253
+ async function onRestore() {
254
+ await remotes.unarchiveEntryCommand(entry.id);
255
+ toast.success(lang[interfaceLanguage.current].entryRestored);
256
+ goto(window.location.pathname, { invalidateAll: true });
257
+ }
258
+
182
259
  async function onSave(type: UpdateEntryVersionCommandType, scheduledAt?: Date) {
183
260
  // Cancel pending autosave
184
261
  if (autosaveTimer) {
@@ -186,24 +263,23 @@
186
263
  autosaveTimer = null;
187
264
  }
188
265
 
189
- if (type === 'draft') {
190
- // Drafts save without validation
191
- const currentFormData = get(form.form);
266
+ // Unpublish skip validation, call remote directly
267
+ if (type === 'cancel-published') {
192
268
  saveStatus = 'saving';
193
269
  try {
194
270
  await remotes.updateEntryVersionCommand({
195
271
  entryId: entry.id,
196
- data: currentFormData,
197
- type: 'draft'
272
+ data: get(form.form),
273
+ type
198
274
  });
199
- lastSavedData = JSON.stringify(currentFormData);
200
275
  saveStatus = 'saved';
201
- toast.success(lang[interfaceLanguage.current].saveDraftToast);
276
+ toast.success(lang[interfaceLanguage.current].unpublishToast);
277
+
202
278
  setTimeout(() => {
203
279
  if (saveStatus === 'saved') saveStatus = 'idle';
204
280
  }, 3000);
205
281
  } catch (e) {
206
- console.error('Save error:', e);
282
+ console.error('Unpublish error:', e);
207
283
  saveStatus = 'error';
208
284
  toast.error(lang[interfaceLanguage.current].saveFailed, {
209
285
  description: e instanceof Error ? e.message : undefined
@@ -213,9 +289,10 @@
213
289
  }
214
290
 
215
291
  // Publish requires validation
216
- const validatedForm = await form.validateForm();
292
+ const validatedForm = await form.validateForm({ update: true });
217
293
 
218
294
  if (validatedForm.valid) {
295
+ validationErrors = [];
219
296
  saveStatus = 'saving';
220
297
  try {
221
298
  await remotes.updateEntryVersionCommand({
@@ -245,14 +322,16 @@
245
322
  });
246
323
  }
247
324
  } else {
248
- const errors = flattenErrors(validatedForm.errors, collection.fields);
249
- toast.error(
250
- lang[interfaceLanguage.current].cannotPublish,
251
- {
252
- description: errors.slice(0, 3).join('\n') + (errors.length > 3 ? '\n...' : ''),
253
- duration: 5000
254
- }
325
+ const errors = flattenErrors(validatedForm.errors, getFieldsFromConfig(collection));
326
+ validationErrors = errors;
327
+
328
+ // Scroll to first errored field
329
+ const firstErrorKey = Object.keys(validatedForm.errors).find(
330
+ (k) => k !== '_errors'
255
331
  );
332
+ if (firstErrorKey) {
333
+ scrollToIssue(firstErrorKey);
334
+ }
256
335
  }
257
336
  }
258
337
 
@@ -292,7 +371,7 @@
292
371
 
293
372
  type SizePreset = keyof typeof sizePresets;
294
373
 
295
- let sizePreset: SizePreset = $state('responsive');
374
+ let sizePreset: SizePreset = $state('desktop');
296
375
 
297
376
  function getOriginFromUrl(url: string): string {
298
377
  try {
@@ -302,7 +381,9 @@
302
381
  }
303
382
  }
304
383
 
305
- const previewOrigin = collection.previewUrl ? getOriginFromUrl(collection.previewUrl) : window.location.origin;
384
+ const previewOrigin = collection.previewUrl
385
+ ? getOriginFromUrl(collection.previewUrl)
386
+ : window.location.origin;
306
387
 
307
388
  const updatePreview = useDebounce(
308
389
  async (window: Window, form: SuperForm<Record<string, unknown>>) => {
@@ -420,46 +501,176 @@
420
501
  }
421
502
  }
422
503
  });
504
+
505
+ // Banner: viewing published version + a separate draft exists
506
+ const showDraftBanner = $derived(
507
+ entry.publishedVersion != null &&
508
+ editingEntry.id === entry.publishedVersion.id &&
509
+ entry.draftVersion != null &&
510
+ entry.draftVersion.id !== entry.publishedVersion.id &&
511
+ entry.draftVersion.createdAt > entry.publishedVersion.createdAt
512
+ );
513
+
514
+ // Banner: viewing draft + a published version exists
515
+ const showPublishedBanner = $derived(
516
+ entry.publishedVersion != null && editingEntry.id !== entry.publishedVersion.id
517
+ );
518
+
519
+ const draftVersionId = $derived(entry.draftVersion?.id ?? savedDraftVersionId);
520
+
521
+ const scrollToIssue = (fieldSlug: string) => {
522
+ const fieldEl = document.querySelector(`[data-field-path="${fieldSlug}"]`);
523
+ if (fieldEl) {
524
+ fieldEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
525
+ const proseMirror = fieldEl.querySelector('.ProseMirror') as HTMLElement | null;
526
+ if (proseMirror) {
527
+ proseMirror.focus();
528
+ }
529
+ }
530
+ };
531
+
532
+ // Auto-dismiss validation banner when user starts editing
533
+ onMount(() => {
534
+ const unsub = form.tainted.subscribe((tainted) => {
535
+ if (tainted && validationErrors.length > 0) {
536
+ validationErrors = [];
537
+ }
538
+ });
539
+ return () => unsub();
540
+ });
541
+
542
+ const t = $derived(lang[interfaceLanguage.current]);
423
543
  </script>
424
544
 
425
- <EntryHeader {entry} version={editingEntry} {onSave} {onArchive} {saveStatus} />
545
+ <EntryHeader
546
+ {entry}
547
+ version={editingEntry}
548
+ {onSave}
549
+ onSaveDraft={performAutosave}
550
+ {onArchive}
551
+ {saveStatus}
552
+ {isArchived}
553
+ fields={getFieldsFromConfig(collection)}
554
+ getFormData={() => get(form.form)}
555
+ onScrollToIssue={scrollToIssue}
556
+ {translationStatus}
557
+ />
558
+
559
+ {#if validationErrors.length > 0}
560
+ <div
561
+ role="alert"
562
+ aria-live="assertive"
563
+ class="flex items-start gap-3 border-b border-[var(--error)]/20 bg-[#FDF0F0] px-6 py-3 text-sm text-[var(--error)]"
564
+ >
565
+ <AlertCircle class="mt-0.5 size-4 shrink-0" />
566
+ <div class="min-w-0 flex-1">
567
+ <p class="font-semibold">{t.cannotPublish}</p>
568
+ <p class="text-xs opacity-80">{t.validationHint}</p>
569
+ <ul class="mt-1 text-xs">
570
+ {#each validationErrors.slice(0, 5) as error}
571
+ <li>— {error}</li>
572
+ {/each}
573
+ </ul>
574
+ </div>
575
+ <button
576
+ type="button"
577
+ onclick={() => (validationErrors = [])}
578
+ class="shrink-0 rounded p-0.5 opacity-60 hover:opacity-100"
579
+ aria-label="Zamknij"
580
+ >
581
+ <XIcon class="size-4" />
582
+ </button>
583
+ </div>
584
+ {/if}
585
+
586
+ {#if isArchived}
587
+ <div
588
+ role="alert"
589
+ class="flex items-center justify-between gap-3 border-b border-[var(--warning)]/20 bg-[#FDF6EC] px-6 py-3 text-sm"
590
+ >
591
+ <div class="flex items-center gap-2 text-[var(--warning)]">
592
+ <ArchiveIcon class="size-4 shrink-0" />
593
+ <span>{t.archivedBanner}</span>
594
+ </div>
595
+ <Button size="sm" onclick={onRestore}>{t.restore}</Button>
596
+ </div>
597
+ {/if}
598
+
599
+ {#if showDraftBanner}
600
+ <div
601
+ class="flex items-center justify-between border-b bg-[var(--lavender-lighter)] px-6 py-2 text-sm"
602
+ >
603
+ <span class="text-[var(--text-secondary)]">{lang[interfaceLanguage.current].newerDraft}</span>
604
+ <button
605
+ type="button"
606
+ class="font-semibold text-[var(--primary)] hover:underline"
607
+ onclick={() => goto(`?version=${draftVersionId}`)}
608
+ >
609
+ {lang[interfaceLanguage.current].switchToDraft}
610
+ </button>
611
+ </div>
612
+ {:else if showPublishedBanner}
613
+ <div
614
+ class="flex items-center justify-between border-b bg-[var(--lavender-lighter)] px-6 py-2 text-sm"
615
+ >
616
+ <span class="text-[var(--text-secondary)]">{lang[interfaceLanguage.current].editingDraft}</span>
617
+ <button
618
+ type="button"
619
+ class="font-semibold text-[var(--primary)] hover:underline"
620
+ onclick={() => goto(`?version=${entry.publishedVersion!.id}`)}
621
+ >
622
+ {lang[interfaceLanguage.current].switchToPublished}
623
+ </button>
624
+ </div>
625
+ {/if}
426
626
 
627
+ <div class={isArchived ? 'pointer-events-none opacity-60' : ''}>
427
628
  {#if hybridContext.mode === 'hybrid' && collection.previewUrl}
428
- <div class="flex min-h-0 flex-1 overflow-hidden">
429
- {#await import('./hybrid/hybrid-layout.svelte')}
430
- <div class="h-full animate-pulse rounded-md bg-accent"></div>
431
- {:then { default: HybridLayout }}
432
- <HybridLayout>
433
- {#snippet preview()}
434
- {#await import('./hybrid/hybrid-preview.svelte')}
435
- <div class="h-full animate-pulse rounded-md bg-accent"></div>
436
- {:then { default: HybridPreview }}
437
- <HybridPreview {collection} {editingEntry} bind:previewIframe bind:sizePreset {size} bind:el />
438
- {:catch}
439
- <p class="p-4 text-sm text-destructive">Failed to load preview</p>
440
- {/await}
441
- {/snippet}
442
- {#snippet formPanel()}
443
- <EntryForm
444
- {form}
445
- {entry}
446
- focusedPath={hybridContext.focusedPath}
447
- onPathSelect={(path) => hybridContext.focusedPath = path}
448
- />
449
- {/snippet}
450
- </HybridLayout>
451
- {:catch}
452
- <p class="p-4 text-sm text-destructive">Failed to load layout</p>
453
- {/await}
454
- </div>
629
+ <div class="flex min-h-0 flex-1 overflow-hidden">
630
+ {#await import('./hybrid/hybrid-layout.svelte')}
631
+ <div class="bg-accent h-full animate-pulse rounded-md"></div>
632
+ {:then { default: HybridLayout }}
633
+ <HybridLayout>
634
+ {#snippet preview()}
635
+ {#await import('./hybrid/hybrid-preview.svelte')}
636
+ <div class="bg-accent h-full animate-pulse rounded-md"></div>
637
+ {:then { default: HybridPreview }}
638
+ <HybridPreview
639
+ {collection}
640
+ {editingEntry}
641
+ bind:previewIframe
642
+ bind:sizePreset
643
+ {size}
644
+ bind:el
645
+ />
646
+ {:catch}
647
+ <p class="text-destructive p-4 text-sm">Failed to load preview</p>
648
+ {/await}
649
+ {/snippet}
650
+ {#snippet formPanel()}
651
+ <EntryForm
652
+ {form}
653
+ {entry}
654
+ focusedPath={hybridContext.focusedPath}
655
+ onPathSelect={(path) => (hybridContext.focusedPath = path)}
656
+ />
657
+ {/snippet}
658
+ </HybridLayout>
659
+ {:catch}
660
+ <p class="text-destructive p-4 text-sm">Failed to load layout</p>
661
+ {/await}
662
+ </div>
663
+ {:else if hasLayout(collection)}
664
+ <div class="overflow-y-auto" style="scroll-padding-top: 48px;">
665
+ <EntryForm {form} {entry} />
666
+ </div>
455
667
  {:else}
456
- <div class="flex items-stretch justify-center">
668
+ <div class="flex items-stretch justify-center" style="scroll-padding-top: 48px;">
457
669
  <div class="max-w-2xl grow p-4 lg:p-6">
458
- <div
459
- class="glass-panel rounded-2xl border-slate-200/50 bg-white/80 p-4 backdrop-blur-xl dark:border-white/10 dark:bg-slate-900/60 lg:p-6"
460
- >
670
+ <div class="bg-card rounded-2xl border p-4 shadow-sm lg:p-6">
461
671
  <EntryForm {form} {entry} />
462
672
  </div>
463
673
  </div>
464
674
  </div>
465
675
  {/if}
676
+ </div>
@@ -0,0 +1,47 @@
1
+ import type { Field } from '../../../../types/fields.js';
2
+ export type A11yIssueType = 'warning' | 'success';
3
+ export interface A11yIssue {
4
+ type: A11yIssueType;
5
+ message: string;
6
+ fieldSlug?: string;
7
+ firstNodePos?: number;
8
+ }
9
+ /** Get heading issue positions for inline decoration in the editor.
10
+ * Works with TipTap JSON doc format (with `type`, `attrs.level`, `content`).
11
+ * Returns issues for: first heading not H2, H3 appearing before any H2. */
12
+ export interface HeadingIssue {
13
+ pos: number;
14
+ message: string;
15
+ }
16
+ export declare function getHeadingIssuePositions(doc: {
17
+ type: string;
18
+ content?: any[];
19
+ }): HeadingIssue[];
20
+ /** Find position of first image without alt in TipTap JSON doc. */
21
+ export declare function findFirstImageWithoutAlt(doc: {
22
+ type: string;
23
+ content?: any[];
24
+ }): number | null;
25
+ /** Find position of first heading hierarchy issue in TipTap JSON doc. */
26
+ export declare function findFirstHeadingIssue(doc: {
27
+ type: string;
28
+ content?: any[];
29
+ }): number | null;
30
+ /** Find position of first generic link in TipTap JSON doc. */
31
+ export declare function findFirstGenericLink(doc: {
32
+ type: string;
33
+ content?: any[];
34
+ }): number | null;
35
+ export interface A11yLang {
36
+ imagesWithoutAlt: (count: number) => string;
37
+ headingsOk: string;
38
+ headingsSkipped: string;
39
+ genericLinks: (count: number) => string;
40
+ allImagesHaveAlt: string;
41
+ noGenericLinks: string;
42
+ }
43
+ export declare const a11yLangPl: A11yLang;
44
+ export declare const a11yLangEn: A11yLang;
45
+ /** Run a11y validation on entry data and return issues list.
46
+ * When `languages` is provided, validates each language separately with prefix tags. */
47
+ export declare function validateA11y(data: Record<string, unknown>, fields: Field[], lang?: A11yLang, languages?: string[]): A11yIssue[];