@riducms/admin 0.2.2 → 0.3.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 (352) hide show
  1. package/THIRD_PARTY_NOTICES.md +60 -37
  2. package/package.json +27 -15
  3. package/src/app/admin-core-view-route.svelte +38 -53
  4. package/src/app/admin-extension-route.svelte +19 -0
  5. package/src/app/admin-layout.scss +175 -0
  6. package/src/app/admin-layout.svelte +144 -410
  7. package/src/app/admin-navigation-progress.svelte +11 -77
  8. package/src/app/admin-page-title.svelte +17 -85
  9. package/src/app/admin-page-title.ts +136 -0
  10. package/src/app/admin-route-error.svelte +24 -0
  11. package/src/app/admin-router-provider.svelte +34 -0
  12. package/src/app/admin-router-root.svelte +41 -0
  13. package/src/app/admin-routes.svelte +56 -19
  14. package/src/app/loading-boundary.svelte +3 -1
  15. package/src/app-meta.ts +13 -0
  16. package/src/app.css +138 -496
  17. package/src/app.svelte +60 -26
  18. package/src/components/brand/ridu-logo.svelte +19 -5
  19. package/src/components/data/list-table.scss +63 -0
  20. package/src/components/icons/LICENSE.md +24 -0
  21. package/src/components/icons/chevron.svelte +8 -0
  22. package/src/components/icons/plus.svelte +12 -0
  23. package/src/components/icons/search.svelte +12 -0
  24. package/src/components/icons/x.svelte +8 -0
  25. package/src/components/json-tree/json-tree-node.svelte +61 -67
  26. package/src/components/json-tree/json-viewer.scss +153 -0
  27. package/src/components/json-tree/json-viewer.svelte +57 -96
  28. package/src/components/ui/banner/banner.scss +28 -0
  29. package/src/components/ui/banner/banner.svelte +9 -8
  30. package/src/components/ui/banner/index.ts +1 -5
  31. package/src/components/ui/code-editor/code-editor.scss +20 -0
  32. package/src/components/ui/code-editor/code-editor.svelte +71 -0
  33. package/src/components/ui/code-editor/code-editor.ts +151 -0
  34. package/src/components/ui/code-editor/code-languages.ts +29 -0
  35. package/src/components/ui/collapsible-panel/collapsible-panel.scss +38 -0
  36. package/src/components/ui/collapsible-panel/collapsible-panel.svelte +13 -0
  37. package/src/components/ui/collapsible-panel/index.ts +1 -0
  38. package/src/components/ui/combobox/combobox.scss +163 -0
  39. package/src/components/ui/combobox/index.ts +22 -0
  40. package/src/components/ui/date-value-control/date-value-calendar.svelte +128 -16
  41. package/src/components/ui/date-value-control/date-value-control-loader.scss +26 -0
  42. package/src/components/ui/date-value-control/date-value-control-loader.svelte +35 -13
  43. package/src/components/ui/date-value-control/date-value-control.scss +223 -0
  44. package/src/components/ui/date-value-control/date-value-control.svelte +62 -44
  45. package/src/components/ui/drawer/drawer-depth.ts +8 -0
  46. package/src/components/ui/dropdown-menu/dropdown-menu-content.svelte +2 -5
  47. package/src/components/ui/dropdown-menu/dropdown-menu-item.svelte +2 -5
  48. package/src/components/ui/dropdown-menu/dropdown-menu-separator.svelte +2 -2
  49. package/src/components/ui/dropdown-menu/dropdown-menu.scss +62 -0
  50. package/src/components/ui/kbd/kbd.scss +17 -0
  51. package/src/components/ui/kbd/kbd.svelte +3 -10
  52. package/src/components/ui/navigation-drawer/navigation-drawer.scss +40 -0
  53. package/src/components/ui/navigation-drawer/navigation-drawer.svelte +38 -0
  54. package/src/components/ui/popover/popover-content.svelte +3 -5
  55. package/src/components/ui/popover/popover.scss +18 -0
  56. package/src/components/ui/radio-group/index.ts +0 -1
  57. package/src/components/ui/radio-group/radio-card-item.svelte +7 -12
  58. package/src/components/ui/radio-group/radio-group.svelte +2 -2
  59. package/src/components/ui/radio-group/radio.scss +45 -0
  60. package/src/components/ui/select/select-content.svelte +5 -6
  61. package/src/components/ui/select/select-item.svelte +8 -8
  62. package/src/components/ui/select/select-scroll-down-button.svelte +3 -5
  63. package/src/components/ui/select/select-scroll-up-button.svelte +3 -5
  64. package/src/components/ui/select/select-trigger.svelte +6 -7
  65. package/src/components/ui/select/select.scss +126 -0
  66. package/src/components/ui/sheet/sheet-content.svelte +6 -34
  67. package/src/components/ui/sheet/sheet-description.svelte +2 -2
  68. package/src/components/ui/sheet/sheet-overlay.svelte +2 -21
  69. package/src/components/ui/sheet/sheet-title.svelte +2 -2
  70. package/src/components/ui/sheet/sheet.scss +221 -0
  71. package/src/components/ui/skeleton/skeleton.scss +20 -0
  72. package/src/components/ui/skeleton/skeleton.svelte +4 -16
  73. package/src/components/ui/sonner/sonner.scss +121 -0
  74. package/src/components/ui/sonner/sonner.svelte +11 -10
  75. package/src/components/ui/status-indicator/status-indicator.scss +48 -0
  76. package/src/components/ui/status-indicator/status-indicator.svelte +14 -14
  77. package/src/components/ui/tabs/tabs-content.svelte +2 -2
  78. package/src/components/ui/tabs/tabs-list.svelte +7 -7
  79. package/src/components/ui/tabs/tabs-trigger.svelte +2 -8
  80. package/src/components/ui/tabs/tabs.scss +86 -0
  81. package/src/components/ui/tabs/tabs.svelte +2 -2
  82. package/src/components/ui/timezone-picker/timezone-picker.svelte +152 -0
  83. package/src/core/api/admin-client.ts +2 -2
  84. package/src/core/bootstrap/admin-bootstrap.ts +396 -0
  85. package/src/core/bootstrap/admin-prepared-state.ts +213 -0
  86. package/src/core/bootstrap/admin-route-modules.ts +66 -0
  87. package/src/core/clipboard/copy-feedback.svelte.ts +43 -0
  88. package/src/core/forms/embedded-fields.ts +4 -8
  89. package/src/core/forms/field-editor-authoring.ts +8 -0
  90. package/src/core/forms/field-issue-focus.ts +4 -4
  91. package/src/core/forms/field-occurrence.ts +4 -7
  92. package/src/core/forms/form-controller.svelte.ts +14 -21
  93. package/src/core/forms/form-draft-recovery.ts +17 -4
  94. package/src/core/forms/form-issue-correlation.ts +13 -13
  95. package/src/core/forms/form-path.ts +17 -0
  96. package/src/core/forms/form-schema.ts +12 -43
  97. package/src/core/forms/form-validation.ts +7 -46
  98. package/src/core/forms/plugin-field-authoring.ts +6 -2
  99. package/src/core/forms/plugin-field-write.ts +2 -6
  100. package/src/core/i18n/admin-i18n.svelte.ts +18 -0
  101. package/src/core/i18n/format-admin-date-time.ts +19 -0
  102. package/src/core/i18n/humanize-identifier.ts +5 -0
  103. package/src/core/i18n/time-zone-label.ts +20 -0
  104. package/src/core/loaders/admin-view-host.svelte +72 -0
  105. package/src/core/loaders/admin-view-loader.svelte.ts +90 -0
  106. package/src/core/notifications/validation-toast.svelte +2 -2
  107. package/src/core/plugins/field-registry.ts +7 -8
  108. package/src/core/routing/admin-paths.ts +53 -2
  109. package/src/core/routing/admin-scroll.svelte.ts +74 -0
  110. package/src/core/routing/admin-view-selection.ts +78 -0
  111. package/src/core/runtime/admin-runtime.svelte.ts +121 -124
  112. package/src/core/schema/field-default.ts +33 -0
  113. package/src/core/schema/field-localization.ts +15 -0
  114. package/src/core/schema/read-document-path.ts +11 -0
  115. package/src/features/account/account-menu.scss +37 -0
  116. package/src/features/account/account-menu.svelte +115 -0
  117. package/src/features/{auth → account}/account-navigation.svelte +16 -19
  118. package/src/features/{auth → account}/account-route.svelte +206 -49
  119. package/src/features/account/account-security-controller.svelte.ts +157 -0
  120. package/src/features/{auth → account}/account-security-route.svelte +140 -42
  121. package/src/features/api-reference/api-reference-code.svelte +64 -0
  122. package/src/features/api-reference/api-reference-content.svelte +258 -0
  123. package/src/features/api-reference/api-reference-drawer.scss +74 -0
  124. package/src/features/api-reference/api-reference-examples.ts +381 -0
  125. package/src/features/api-reference/api-reference-schema.ts +332 -0
  126. package/src/features/api-reference/api-reference.scss +190 -0
  127. package/src/features/api-reference/api-reference.svelte +74 -0
  128. package/src/features/auth/auth-brand.svelte +5 -3
  129. package/src/features/auth/auth-field.svelte +92 -0
  130. package/src/features/auth/auth-frame.svelte +5 -8
  131. package/src/features/auth/auth.scss +122 -0
  132. package/src/features/auth/create-first-user-controller.svelte.ts +20 -51
  133. package/src/features/auth/create-first-user-route.svelte +83 -122
  134. package/src/features/auth/forgot-password-route.svelte +31 -23
  135. package/src/features/auth/login-route.svelte +68 -116
  136. package/src/features/auth/logout-button.scss +34 -0
  137. package/src/features/auth/logout-button.svelte +52 -0
  138. package/src/features/auth/request-verification-route.svelte +27 -22
  139. package/src/features/auth/reset-password-route.svelte +108 -41
  140. package/src/features/auth/verify-email-route.svelte +49 -15
  141. package/src/features/bulk-edit/bulk-edit-fields.ts +23 -0
  142. package/src/features/bulk-edit/bulk-edit.scss +152 -0
  143. package/src/features/bulk-edit/field-picker.svelte +176 -0
  144. package/src/features/collections/bulk/bulk-actions.svelte +148 -0
  145. package/src/features/collections/bulk/bulk-confirmation.svelte +153 -0
  146. package/src/features/collections/bulk/bulk-editor-loader.svelte +95 -0
  147. package/src/features/collections/bulk/bulk-editor.svelte +186 -0
  148. package/src/features/collections/collection-list-cell-values.ts +144 -0
  149. package/src/features/collections/collection-list-controller.svelte.ts +268 -379
  150. package/src/features/collections/collection-list-preferences-controller.svelte.ts +54 -40
  151. package/src/features/collections/collection-list-query.ts +223 -0
  152. package/src/features/collections/collection-list-results.svelte +205 -329
  153. package/src/features/collections/collection-list-route.svelte +143 -884
  154. package/src/features/collections/collection-list.scss +261 -0
  155. package/src/features/collections/collection-list.svelte.ts +362 -0
  156. package/src/features/collections/controls/column-picker.svelte +110 -0
  157. package/src/features/collections/controls/column-pill.svelte +62 -0
  158. package/src/features/collections/controls/filter-builder.svelte +193 -0
  159. package/src/features/collections/controls/filter-row.svelte +142 -0
  160. package/src/features/collections/controls/list-controls.scss +196 -0
  161. package/src/features/collections/controls/list-options.svelte +203 -0
  162. package/src/features/collections/controls/list-pagination.scss +78 -0
  163. package/src/features/collections/controls/list-pagination.svelte +115 -0
  164. package/src/features/collections/controls/list-toolbar.svelte +132 -0
  165. package/src/features/collections/controls/sort-heading.scss +52 -0
  166. package/src/features/collections/controls/sort-heading.svelte +78 -0
  167. package/src/features/collections/list-columns.ts +43 -0
  168. package/src/features/collections/list-workspace.ts +155 -54
  169. package/src/features/dashboard/dashboard-route.svelte +82 -86
  170. package/src/features/dashboard/dashboard.scss +131 -0
  171. package/src/features/development/development-tools.svelte +20 -36
  172. package/src/features/documents/document-action-bar.svelte +438 -0
  173. package/src/features/documents/document-api-view-loader.svelte +17 -6
  174. package/src/features/documents/document-api-view.scss +120 -0
  175. package/src/features/documents/document-api-view.svelte +169 -454
  176. package/src/features/documents/document-controller.svelte.ts +526 -613
  177. package/src/features/documents/document-credentials.scss +37 -0
  178. package/src/features/documents/document-credentials.svelte +72 -0
  179. package/src/features/documents/{document-route-dirty.ts → document-credentials.ts} +25 -0
  180. package/src/features/documents/document-field-sections.scss +39 -0
  181. package/src/features/documents/document-field-sections.svelte +4 -13
  182. package/src/features/documents/document-loading.scss +24 -0
  183. package/src/features/documents/document-lock-controller.svelte.ts +187 -0
  184. package/src/features/documents/document-operation-dialogs.svelte +110 -0
  185. package/src/features/documents/document-route-loader.svelte +17 -5
  186. package/src/features/documents/document-route.svelte +305 -811
  187. package/src/features/documents/document-schedule.scss +127 -0
  188. package/src/features/documents/document-schedule.svelte +323 -0
  189. package/src/features/documents/document-title.ts +25 -0
  190. package/src/features/documents/document.scss +274 -0
  191. package/src/features/documents/live-preview-panel.scss +117 -0
  192. package/src/features/documents/live-preview-panel.svelte +167 -168
  193. package/src/features/documents/live-preview.ts +2 -10
  194. package/src/features/documents/upload-document-preview-loader.svelte +26 -23
  195. package/src/features/localization/content-locale-switcher.scss +43 -0
  196. package/src/features/localization/content-locale-switcher.svelte +24 -17
  197. package/src/features/navigation/admin-breadcrumbs.scss +56 -0
  198. package/src/features/navigation/admin-breadcrumbs.svelte +102 -37
  199. package/src/features/navigation/admin-command-menu.svelte +38 -15
  200. package/src/features/navigation/admin-navigation.scss +133 -0
  201. package/src/features/navigation/admin-navigation.svelte +169 -0
  202. package/src/features/navigation/breadcrumb-context.svelte.ts +15 -0
  203. package/src/features/navigation/navigation-preference.svelte.ts +70 -0
  204. package/src/features/reference-browser/reference-browser-editor.svelte +180 -0
  205. package/src/features/reference-browser/reference-browser-list.scss +113 -0
  206. package/src/features/reference-browser/reference-browser-list.svelte +370 -0
  207. package/src/features/reference-browser/reference-browser-loader.svelte +26 -8
  208. package/src/features/reference-browser/reference-browser-workflow.svelte.ts +335 -236
  209. package/src/features/reference-browser/reference-browser.scss +217 -0
  210. package/src/features/reference-browser/reference-browser.svelte +154 -797
  211. package/src/features/reference-browser/reference-collection-picker.svelte +116 -0
  212. package/src/features/reference-browser/reference-list-query.ts +35 -0
  213. package/src/features/uploads/bulk-upload-controller.svelte.ts +604 -0
  214. package/src/features/uploads/bulk-upload-edit-all.svelte +92 -0
  215. package/src/features/uploads/bulk-upload-editor.svelte +191 -0
  216. package/src/features/uploads/bulk-upload-file-picker.svelte +70 -0
  217. package/src/features/uploads/bulk-upload-remote.svelte +100 -0
  218. package/src/features/uploads/bulk-upload-route-loader.svelte +54 -0
  219. package/src/features/uploads/bulk-upload-route.svelte +172 -592
  220. package/src/features/uploads/bulk-upload-sidebar.svelte +125 -0
  221. package/src/features/uploads/bulk-upload.scss +526 -0
  222. package/src/features/uploads/bulk-upload.ts +105 -14
  223. package/src/features/uploads/crop-surface.ts +150 -0
  224. package/src/features/uploads/image-editor.svelte +231 -0
  225. package/src/features/uploads/rendition-preview.svelte +64 -0
  226. package/src/features/uploads/upload-control.svelte +340 -0
  227. package/src/features/uploads/upload-document-contracts.ts +30 -0
  228. package/src/features/uploads/upload-draft.svelte.ts +294 -0
  229. package/src/features/uploads/upload-metadata-fields.scss +32 -0
  230. package/src/features/uploads/upload-metadata-fields.svelte +8 -7
  231. package/src/features/uploads/upload-thumbnail.svelte +47 -0
  232. package/src/features/uploads/upload.scss +361 -0
  233. package/src/features/versions/version-comparison.svelte +361 -0
  234. package/src/features/versions/version-diff-fields.svelte +136 -0
  235. package/src/features/versions/version-diff.ts +296 -41
  236. package/src/features/versions/version-history-controller.svelte.ts +311 -0
  237. package/src/features/versions/version-history-route-loader.svelte +32 -0
  238. package/src/features/versions/version-history-route.svelte +157 -522
  239. package/src/features/versions/version-history.scss +444 -0
  240. package/src/features/versions/version-history.ts +45 -0
  241. package/src/features/versions/version-table.svelte +91 -0
  242. package/src/fields/embedded-schema-draft-editor.scss +48 -0
  243. package/src/fields/embedded-schema-draft-editor.svelte +24 -16
  244. package/src/fields/field-collapsible.svelte +14 -8
  245. package/src/fields/field-layout.scss +171 -0
  246. package/src/fields/field-layout.svelte +3 -5
  247. package/src/fields/field-renderer.svelte +17 -29
  248. package/src/fields/field-shell.svelte +1 -1
  249. package/src/fields/field-tab-layout.svelte +25 -19
  250. package/src/fields/join/join-field-controller.svelte.ts +513 -0
  251. package/src/fields/join/join-field-values.ts +111 -0
  252. package/src/fields/join/join-field.scss +189 -0
  253. package/src/fields/join/join-field.svelte +164 -332
  254. package/src/fields/live-validation-feedback.svelte +4 -3
  255. package/src/fields/local-field-editor.svelte +2 -2
  256. package/src/fields/nested/block-name-input.svelte +11 -12
  257. package/src/fields/nested/block-picker.scss +128 -0
  258. package/src/fields/nested/block-picker.svelte +81 -0
  259. package/src/fields/nested/block-placeholder.svg +2 -0
  260. package/src/fields/nested/nested-field.scss +331 -0
  261. package/src/fields/nested/nested-field.svelte +265 -245
  262. package/src/fields/nested/sortable-row.svelte +9 -7
  263. package/src/fields/plugin-field.svelte +4 -6
  264. package/src/fields/point/point-field.svelte +51 -38
  265. package/src/fields/primitive-list/primitive-list-field.svelte +63 -19
  266. package/src/fields/primitive-list/primitive-list.scss +109 -0
  267. package/src/fields/radio/radio-field.svelte +2 -1
  268. package/src/fields/relationship/relationship-field-controller.svelte.ts +81 -41
  269. package/src/fields/relationship/relationship-field.svelte +144 -175
  270. package/src/fields/relationship/relationship-lookup-controller.svelte.ts +58 -30
  271. package/src/fields/relationship/relationship-query.ts +2 -10
  272. package/src/fields/relationship/relationship-quick-picker-controller.svelte.ts +36 -62
  273. package/src/fields/relationship/relationship-quick-picker.svelte +178 -227
  274. package/src/fields/relationship/relationship-selection.svelte +120 -0
  275. package/src/fields/relationship/relationship-value.ts +46 -7
  276. package/src/fields/relationship/relationship.scss +182 -0
  277. package/src/fields/scalar/code-field.svelte +101 -0
  278. package/src/fields/scalar/date-control-value.ts +13 -0
  279. package/src/fields/scalar/scalar-field.svelte +25 -67
  280. package/src/fields/select/select-field.svelte +132 -94
  281. package/src/fields/text/slug-field.scss +28 -0
  282. package/src/fields/text/slug-field.svelte +19 -15
  283. package/src/fields/text/text-field.svelte +1 -2
  284. package/src/fields/ui/ui-field.svelte +7 -4
  285. package/src/fields/virtual/virtual-field.svelte +2 -5
  286. package/src/index.ts +1 -2
  287. package/src/routing.ts +2 -0
  288. package/src/app/deferred-redirect.svelte +0 -23
  289. package/src/components/code-block/code-block.svelte +0 -107
  290. package/src/components/ui/bulk-action-bar/bulk-action-bar.svelte +0 -50
  291. package/src/components/ui/bulk-action-bar/index.ts +0 -1
  292. package/src/components/ui/button/index.ts +0 -8
  293. package/src/components/ui/calendar/calendar-caption.svelte +0 -72
  294. package/src/components/ui/calendar/calendar-cell.svelte +0 -20
  295. package/src/components/ui/calendar/calendar-day.svelte +0 -27
  296. package/src/components/ui/calendar/calendar-grid-row.svelte +0 -13
  297. package/src/components/ui/calendar/calendar-grid.svelte +0 -17
  298. package/src/components/ui/calendar/calendar-head-cell.svelte +0 -20
  299. package/src/components/ui/calendar/calendar-header.svelte +0 -20
  300. package/src/components/ui/calendar/calendar-heading.svelte +0 -17
  301. package/src/components/ui/calendar/calendar-month-select.svelte +0 -45
  302. package/src/components/ui/calendar/calendar-month.svelte +0 -16
  303. package/src/components/ui/calendar/calendar-months.svelte +0 -20
  304. package/src/components/ui/calendar/calendar-nav.svelte +0 -20
  305. package/src/components/ui/calendar/calendar-next-button.svelte +0 -30
  306. package/src/components/ui/calendar/calendar-prev-button.svelte +0 -30
  307. package/src/components/ui/calendar/calendar-year-select.svelte +0 -44
  308. package/src/components/ui/calendar/calendar.svelte +0 -118
  309. package/src/components/ui/calendar/index.ts +0 -1
  310. package/src/components/ui/checkbox/checkbox.svelte +0 -35
  311. package/src/components/ui/checkbox/index.ts +0 -1
  312. package/src/components/ui/dialog/dialog-close.svelte +0 -11
  313. package/src/components/ui/dialog/dialog-content.svelte +0 -52
  314. package/src/components/ui/dialog/dialog-description.svelte +0 -20
  315. package/src/components/ui/dialog/dialog-footer.svelte +0 -20
  316. package/src/components/ui/dialog/dialog-header.svelte +0 -20
  317. package/src/components/ui/dialog/dialog-overlay.svelte +0 -20
  318. package/src/components/ui/dialog/dialog-title.svelte +0 -17
  319. package/src/components/ui/dialog/dialog-trigger.svelte +0 -11
  320. package/src/components/ui/dialog/dialog.svelte +0 -7
  321. package/src/components/ui/dialog/index.ts +0 -8
  322. package/src/components/ui/empty-state/empty-state.svelte +0 -34
  323. package/src/components/ui/empty-state/index.ts +0 -1
  324. package/src/components/ui/input/index.ts +0 -1
  325. package/src/components/ui/pagination/index.ts +0 -1
  326. package/src/components/ui/pagination/pagination.svelte +0 -88
  327. package/src/components/ui/radio-group/radio-group-item.svelte +0 -17
  328. package/src/components/ui/slider/index.ts +0 -3
  329. package/src/components/ui/slider/slider-content.svelte +0 -59
  330. package/src/components/ui/slider/slider.svelte +0 -35
  331. package/src/components/ui/table/index.ts +0 -6
  332. package/src/components/ui/table/table-body.svelte +0 -20
  333. package/src/components/ui/table/table-cell.svelte +0 -23
  334. package/src/components/ui/table/table-head.svelte +0 -23
  335. package/src/components/ui/table/table-header.svelte +0 -20
  336. package/src/components/ui/table/table-row.svelte +0 -23
  337. package/src/components/ui/table/table.svelte +0 -22
  338. package/src/components/ui/textarea/index.ts +0 -1
  339. package/src/components/ui/tooltip/index.ts +0 -4
  340. package/src/components/ui/tooltip/tooltip-content.svelte +0 -50
  341. package/src/components/ui/tooltip/tooltip-provider.svelte +0 -7
  342. package/src/components/ui/tooltip/tooltip-trigger.svelte +0 -7
  343. package/src/components/ui/tooltip/tooltip.svelte +0 -7
  344. package/src/features/auth/account-menu.svelte +0 -159
  345. package/src/features/auth/account-security-controller.svelte.ts +0 -119
  346. package/src/features/collections/collection-list-bulk-actions.svelte +0 -143
  347. package/src/features/collections/collection-list-bulk-editor.svelte +0 -157
  348. package/src/features/collections/collection-list-filter-popover.svelte +0 -238
  349. package/src/features/collections/collection-list-workspace-toolbar.svelte +0 -409
  350. package/src/features/collections/collection-table-skeleton.svelte +0 -44
  351. package/src/features/documents/document-heading.ts +0 -20
  352. package/src/features/documents/upload-document-preview.svelte +0 -525
@@ -1,4 +1,6 @@
1
+ import { documentLabel, documentTitleField } from "@admin/features/documents/document-title";
1
2
  import { resolveBlockTypes } from "@riducms/protocol";
3
+ import type { FieldReferenceBrowserProps } from "@riducms/plugin";
2
4
  import { connectDocumentLiveValidation } from "@admin/core/forms/live-validation.svelte";
3
5
  import type { SchemaCollection, SchemaField } from "@riducms/protocol";
4
6
  import { RiduError } from "@riducms/sdk";
@@ -9,142 +11,235 @@ import { documentFormValues, initialFormValues } from "@admin/core/forms/form-sc
9
11
  import { invalidFieldLabels } from "@admin/core/forms/form-validation";
10
12
  import type { NotificationCenter } from "@admin/core/notifications/notification-center.svelte";
11
13
  import type { AdminRuntime } from "@admin/core/runtime/admin-runtime.svelte";
14
+ import { UploadDraft } from "@admin/features/uploads/upload-draft.svelte";
15
+ import {
16
+ defaultListColumns,
17
+ listColumnFields,
18
+ listMetadataFields,
19
+ filterableFields,
20
+ type ListColumnSelection,
21
+ type ListFilterGroup,
22
+ type ListPageSize,
23
+ } from "@admin/features/collections/list-workspace";
24
+ import { referenceListWhere } from "@admin/features/reference-browser/reference-list-query";
25
+ import { isUploadMetadataField } from "@admin/features/uploads/upload-document-contracts";
12
26
 
13
27
  import { RelationshipLookupController } from "@admin/fields/relationship/relationship-lookup-controller.svelte";
14
- import {
15
- combineRelationshipFilters,
16
- type RelationshipFilter,
17
- } from "@admin/fields/relationship/relationship-query";
18
-
19
- interface FilterOption {
20
- key: string;
21
- label: string;
22
- filter: RelationshipFilter;
23
- }
24
28
 
25
29
  interface ReferenceBrowserWorkflowOptions {
26
30
  runtime: AdminRuntime;
27
31
  notifications: NotificationCenter;
28
32
  field: SchemaField;
29
33
  collection: SchemaCollection;
34
+ collections?: readonly SchemaCollection[];
30
35
  hasMany: boolean;
31
36
  selectedIDs: readonly string[];
32
37
  readOnly: boolean;
33
38
  initialDocument?: AdminDocument;
34
39
  initialDocumentID?: string;
35
- optionFilter?: RelationshipFilter;
40
+ initialCreate?: boolean;
41
+ initialFile?: File;
42
+ optionFilter?: FieldReferenceBrowserProps["optionFilter"];
36
43
  defaultValues?: Readonly<Record<string, unknown>>;
37
44
  allowCreate?: boolean;
38
45
  locale?: string;
39
- onCommit: (ids: string[]) => void | boolean | Promise<void | boolean>;
46
+ onCommit: (ids: string[], collectionSlug: string) => void | boolean | Promise<void | boolean>;
40
47
  onClose: () => void;
41
48
  get open(): boolean;
42
49
  setOpen(open: boolean): void;
43
50
  }
44
51
 
45
- const uploadMetadataFields = new Set([
46
- "filename",
47
- "mimeType",
48
- "filesize",
49
- "url",
50
- "objectKey",
51
- "width",
52
- "height",
53
- "sizes",
54
- "focalX",
55
- "focalY",
56
- "cropX",
57
- "cropY",
58
- "cropWidth",
59
- "cropHeight",
60
- ]);
61
-
62
52
  export class ReferenceBrowserWorkflow {
63
53
  readonly form: FormController;
64
- readonly pageSize: number;
65
- readonly scopedFields: SchemaField[];
66
- readonly displayField: SchemaField | undefined;
67
- readonly headingField: SchemaField | undefined;
68
- readonly searchField: SchemaField | undefined;
69
- readonly editorFields: SchemaField[];
70
- readonly validationFields: SchemaField[];
71
- readonly filters: FilterOption[];
54
+ readonly upload: UploadDraft;
55
+ readonly availableCollections: readonly SchemaCollection[];
56
+ pageSize = $state<ListPageSize>(10);
57
+ columns = $state.raw<ListColumnSelection[]>([]);
58
+ listFilters = $state.raw<ListFilterGroup[]>([]);
59
+ sort = $state("");
60
+ #collection: SchemaCollection;
61
+ readonly scopedFields = $derived(
62
+ this.collection.fields.map((field) => scopeField(field, `${this.options.field.id}-drawer`))
63
+ );
64
+ readonly displayField = $derived(
65
+ this.scopedFields.find((field) => field.path === documentTitleField(this.collection)?.path)
66
+ );
67
+ readonly searchField = $derived.by(() => {
68
+ const field = this.collection.capabilities.upload
69
+ ? this.collection.fields.find((field) => field.name === "filename")
70
+ : documentTitleField(this.collection);
71
+ return field?.queryRestricted ? undefined : field;
72
+ });
73
+ readonly editorFields = $derived(
74
+ this.scopedFields.filter(
75
+ (field) => !(this.collection.capabilities.upload && isUploadMetadataField(field.name))
76
+ )
77
+ );
78
+ readonly validationFields = $derived(
79
+ this.collection.fields.filter(
80
+ (field) => !(this.collection.capabilities.upload && isUploadMetadataField(field.name))
81
+ )
82
+ );
72
83
  screen = $state<"list" | "document">("list");
73
84
  workingSelection = $state.raw<string[]>([]);
74
85
  query = $state("");
75
86
  page = $state(1);
76
- filterKey = $state("");
77
87
  editorDocument = $state.raw<AdminDocument>();
78
88
  editorLoading = $state(false);
79
89
  editorError = $state<string>();
80
90
  editorView = $state<"edit" | "api">("edit");
81
- selectedFiles = $state<FileList>();
82
91
  newUserPassword = $state("");
83
92
  newUserPasswordConfirmation = $state("");
84
93
  credentialIssue = $state<string>();
85
94
  confirmDiscard = $state(false);
86
95
  committing = $state(false);
87
- #pendingDismiss: "back" | "close" = "back";
96
+ #pendingDismiss: "back" | "close" | "navigate" = "back";
97
+ #pendingNavigation?: () => void;
98
+ #directEntry = $state(false);
88
99
  readonly #lookup: RelationshipLookupController;
89
- readonly #editorLoad = new AbortController();
100
+ #editorLoad?: AbortController;
90
101
  #editorAccess?: AbortController;
91
102
  #saveRequest?: AbortController;
103
+ #disposed = false;
92
104
 
93
105
  constructor(readonly options: ReferenceBrowserWorkflowOptions) {
106
+ this.#directEntry =
107
+ options.initialCreate === true ||
108
+ options.initialFile !== undefined ||
109
+ options.initialDocument !== undefined ||
110
+ options.initialDocumentID !== undefined;
111
+ this.availableCollections =
112
+ options.collections?.filter(
113
+ (collection) => options.runtime.collectionOperations[collection.slug]?.read === true
114
+ ) ?? [];
115
+ this.#collection = $state.raw(
116
+ this.#directEntry
117
+ ? options.collection
118
+ : (this.availableCollections.find(
119
+ (collection) => collection.slug === options.collection.slug
120
+ ) ??
121
+ this.availableCollections[0] ??
122
+ options.collection)
123
+ );
94
124
  this.form = new FormController({}, options.runtime.i18n);
95
125
  connectDocumentLiveValidation(this.form, options.runtime.client);
96
- this.#lookup = new RelationshipLookupController(options.runtime.client);
97
- this.form.setLocalization(options.locale);
98
- this.pageSize = options.collection.capabilities.upload ? 8 : 10;
99
- this.scopedFields = options.collection.fields.map((field) =>
100
- scopeField(field, `${options.field.id}-drawer`)
101
- );
102
- this.displayField = findDisplayField(this.scopedFields);
103
- this.headingField = this.displayField?.admin.row === undefined ? this.displayField : undefined;
104
- this.searchField = findDisplayField(options.collection.fields);
105
- this.editorFields = this.scopedFields.filter(
106
- (field) => field.id !== this.headingField?.id && !uploadMetadataFields.has(field.name)
126
+ this.#lookup = new RelationshipLookupController(options.runtime.client, options.runtime.i18n);
127
+ this.upload = new UploadDraft(
128
+ options.runtime.client,
129
+ () => this.collection.slug,
130
+ options.runtime.i18n
107
131
  );
108
- this.validationFields = options.collection.fields.filter(
109
- (field) => !(options.collection.capabilities.upload && uploadMetadataFields.has(field.name))
110
- );
111
- this.filters = createCollectionFilters(options.collection, options.runtime.i18n);
112
- this.workingSelection = [...options.selectedIDs];
113
-
132
+ this.form.setLocalization(options.locale);
133
+ this.columns = this.#defaultColumns();
134
+ this.workingSelection = [...this.selectedIDs];
114
135
  if (options.initialDocument !== undefined) {
115
136
  this.#lookup.remember(options.initialDocument);
116
137
  this.#openEditor(options.initialDocument);
117
138
  } else if (options.initialDocumentID !== undefined) {
118
139
  this.screen = "document";
119
- this.#loadEditor(options.initialDocumentID, this.#editorLoad.signal);
140
+ this.#loadEditor(options.initialDocumentID);
141
+ } else if (options.initialCreate || options.initialFile) {
142
+ this.openNewDocument();
143
+ if (this.canCreateDocument && options.initialFile && options.collection.uploadSettings) {
144
+ this.upload.select(options.initialFile, options.collection.uploadSettings);
145
+ }
120
146
  }
121
147
 
122
148
  $effect(() => {
123
- if (!this.options.open || this.screen !== "list") return;
149
+ if (!this.options.open || this.screen !== "list" || this.browsingUnavailable) return;
124
150
  this.options.runtime.documentRevision;
125
151
  const request = {
126
- slug: this.options.collection.slug,
152
+ slug: this.collection.slug,
127
153
  page: this.page,
128
154
  limit: this.pageSize,
129
155
  search: this.query,
130
- searchField: this.searchField?.name,
131
- filter: this.currentFilter,
156
+ searchField: this.searchField?.path,
157
+ sort: this.sort,
158
+ where: referenceListWhere(this.listFilters, [
159
+ ...filterableFields(this.collection.fields),
160
+ ...listMetadataFields(this.collection, this.options.runtime.i18n),
161
+ ]),
162
+ includeAccess: true,
163
+ depth: 1,
164
+ filter:
165
+ typeof this.options.optionFilter === "function"
166
+ ? this.options.optionFilter(this.collection)
167
+ : this.options.optionFilter,
132
168
  locale: this.options.locale,
133
169
  };
134
170
  const delay = request.search.trim() === "" ? 0 : 220;
135
171
  const timer = window.setTimeout(() => this.#lookup.search(request), delay);
136
- return () => window.clearTimeout(timer);
172
+ return () => {
173
+ window.clearTimeout(timer);
174
+ this.#lookup.cancelSearch();
175
+ };
137
176
  });
138
177
 
139
178
  $effect(() => () => {
179
+ this.#disposed = true;
140
180
  this.form.disposeBindings();
141
- this.#editorLoad.abort();
181
+ this.#cancelEditorLoad();
142
182
  this.#editorAccess?.abort();
143
183
  this.#saveRequest?.abort();
144
184
  this.#lookup.dispose();
185
+ this.upload.dispose();
145
186
  });
146
187
  }
147
188
 
189
+ get collection() {
190
+ return this.#collection;
191
+ }
192
+
193
+ get selectedIDs() {
194
+ return this.collection.slug === this.options.collection.slug ? this.options.selectedIDs : [];
195
+ }
196
+
197
+ get browsingUnavailable() {
198
+ return (
199
+ !this.directEntry &&
200
+ this.options.collections !== undefined &&
201
+ this.availableCollections.length === 0
202
+ );
203
+ }
204
+
205
+ selectCollection = (slug: string) => {
206
+ if (this.screen !== "list" || this.committing || this.form.submitting) return;
207
+ const collection = this.availableCollections.find((collection) => collection.slug === slug);
208
+ if (collection === undefined || collection.slug === this.collection.slug) return;
209
+
210
+ this.#returnToList();
211
+ this.#lookup.reset();
212
+ this.#collection = collection;
213
+ this.query = "";
214
+ this.page = 1;
215
+ this.sort = "";
216
+ this.listFilters = [];
217
+ this.columns = this.#defaultColumns();
218
+ this.workingSelection = [...this.selectedIDs];
219
+ };
220
+
221
+ #defaultColumns() {
222
+ const columns = [
223
+ ...listColumnFields(this.collection),
224
+ ...listMetadataFields(this.collection, this.options.runtime.i18n),
225
+ ];
226
+ const defaults = new Set([
227
+ ...(this.collection.capabilities.upload ? ["filename"] : []),
228
+ ...defaultListColumns(
229
+ this.collection,
230
+ columns.map((field) => ({
231
+ path: field.path,
232
+ label: field.admin.label,
233
+ field,
234
+ }))
235
+ ),
236
+ ]);
237
+ return [...new Set([...defaults, ...columns.map((field) => field.path)])].map((path) => ({
238
+ path,
239
+ active: defaults.has(path),
240
+ }));
241
+ }
242
+
148
243
  get status() {
149
244
  return this.#lookup.status;
150
245
  }
@@ -162,7 +257,7 @@ export class ReferenceBrowserWorkflow {
162
257
  }
163
258
 
164
259
  get selectedFile() {
165
- return this.selectedFiles?.item(0) ?? undefined;
260
+ return this.upload.file;
166
261
  }
167
262
 
168
263
  get creating() {
@@ -170,101 +265,113 @@ export class ReferenceBrowserWorkflow {
170
265
  }
171
266
 
172
267
  get creatingAuthUser() {
173
- return this.creating && this.options.collection.capabilities.auth;
268
+ return this.creating && this.collection.capabilities.auth;
174
269
  }
175
270
 
176
271
  get editorDirty() {
177
272
  return (
178
273
  this.form.dirty ||
179
- this.selectedFile !== undefined ||
274
+ this.upload.dirty ||
180
275
  this.newUserPassword !== "" ||
181
276
  this.newUserPasswordConfirmation !== ""
182
277
  );
183
278
  }
184
279
 
185
- get currentFilter() {
186
- return combineRelationshipFilters(
187
- this.options.optionFilter,
188
- this.filters.find((option) => option.key === this.filterKey)?.filter
189
- );
190
- }
191
-
192
280
  get editorTitle() {
281
+ if (this.collection.capabilities.upload)
282
+ return this.upload.filename || this.options.runtime.i18n.t("documents:untitled");
193
283
  const value =
194
284
  this.displayField === undefined ? undefined : this.form.get(this.displayField.path);
195
285
  const title = typeof value === "string" ? value.trim() : "";
196
286
  return title === ""
197
287
  ? this.options.runtime.i18n.t("reference:untitledLabel", {
198
288
  label: this.options.runtime.i18n
199
- .text(
200
- this.options.collection.labels.singular,
201
- this.options.collection.labels.singularTranslations
202
- )
289
+ .text(this.collection.labels.singular, this.collection.labels.singularTranslations)
203
290
  .toLocaleLowerCase(this.options.runtime.i18n.language),
204
291
  })
205
292
  : title;
206
293
  }
207
294
 
208
- get headingIssues() {
209
- return this.headingField === undefined ? [] : this.form.issuesFor(this.headingField.path);
295
+ get editableUpload() {
296
+ return (
297
+ !this.options.readOnly &&
298
+ !this.form.submitting &&
299
+ this.form.access?.operations[this.creating ? "create" : "update"] === true
300
+ );
301
+ }
302
+
303
+ get directEntry() {
304
+ return this.#directEntry;
210
305
  }
211
306
 
307
+ canReadField = (path: string, id?: string) => this.#lookup.canReadField(path, id);
308
+
309
+ setColumns = (columns: ListColumnSelection[]) => {
310
+ this.columns = columns;
311
+ };
312
+
313
+ setListFilters = (filters: ListFilterGroup[]) => {
314
+ this.listFilters = filters;
315
+ this.page = 1;
316
+ };
317
+
318
+ setSort = (path: string, descending: boolean) => {
319
+ this.sort = descending ? `-${path}` : path;
320
+ this.page = 1;
321
+ };
322
+
323
+ setPageSize = (size: ListPageSize) => {
324
+ this.pageSize = size;
325
+ this.page = 1;
326
+ };
327
+
328
+ selectDocument = (id: string) => this.commitSelection([id]);
329
+
212
330
  get canSave() {
213
331
  const operationAllowed = this.creating
214
332
  ? this.form.access?.operations.create === true
215
333
  : this.form.access?.operations.update === true;
216
334
  const inputReady = this.creating
217
- ? !this.options.collection.capabilities.upload || this.selectedFile !== undefined
218
- : this.form.dirty;
219
- return !this.options.readOnly && operationAllowed && !this.form.submitting && inputReady;
335
+ ? !this.collection.capabilities.upload || this.selectedFile !== undefined
336
+ : this.editorDirty;
337
+ return (
338
+ !this.options.readOnly &&
339
+ operationAllowed &&
340
+ !this.editorLoading &&
341
+ !this.form.submitting &&
342
+ !this.upload.busy &&
343
+ !this.upload.editingImage &&
344
+ inputReady &&
345
+ (!this.collection.capabilities.upload || this.upload.present)
346
+ );
220
347
  }
221
348
 
222
349
  get canCreateDocument() {
223
350
  return (
224
351
  !this.options.readOnly &&
352
+ !this.browsingUnavailable &&
225
353
  this.options.allowCreate !== false &&
226
- this.options.runtime.collectionOperations[this.options.collection.slug]?.create === true
354
+ this.options.runtime.collectionOperations[this.collection.slug]?.create === true
227
355
  );
228
356
  }
229
357
 
230
358
  get canCommitSelection() {
231
359
  return (
232
360
  !this.options.readOnly &&
233
- (this.workingSelection.length !== this.options.selectedIDs.length ||
234
- this.workingSelection.some((id) => !this.options.selectedIDs.includes(id)))
361
+ !this.browsingUnavailable &&
362
+ (this.workingSelection.length !== this.selectedIDs.length ||
363
+ this.workingSelection.some((id) => !this.selectedIDs.includes(id)))
235
364
  );
236
365
  }
237
366
 
238
- get rangeStart() {
239
- return this.pagination.totalDocs === 0
240
- ? 0
241
- : (this.pagination.page - 1) * this.pagination.limit + 1;
242
- }
243
-
244
- get rangeEnd() {
245
- return Math.min(this.pagination.page * this.pagination.limit, this.pagination.totalDocs);
246
- }
247
-
248
- documentLabel = (document: AdminDocument) => {
249
- const value = this.options.collection.capabilities.upload
250
- ? document.filename
251
- : this.searchField === undefined
252
- ? undefined
253
- : document[this.searchField.name];
254
- return String(value ?? document.id);
367
+ setPage = (page: number) => {
368
+ this.page = page;
255
369
  };
256
370
 
257
- documentSubtitle = (document: AdminDocument) => {
258
- if (this.options.collection.capabilities.upload) {
259
- return [document.mimeType, this.formatBytes(document.filesize)].filter(Boolean).join(" · ");
260
- }
261
- const email = typeof document.email === "string" ? document.email : undefined;
262
- if (email !== undefined && email !== this.documentLabel(document)) return email;
263
- const status = typeof document.status === "string" ? document.status : document._status;
264
- return typeof status === "string"
265
- ? humanize(status, this.options.runtime.i18n.language)
266
- : document.id;
267
- };
371
+ documentLabel = (document: AdminDocument) =>
372
+ this.collection.capabilities.upload
373
+ ? String(document.filename ?? document.id)
374
+ : documentLabel(this.collection, document);
268
375
 
269
376
  toggleSelection = (id: string) => {
270
377
  if (this.options.readOnly || this.committing) return;
@@ -275,13 +382,17 @@ export class ReferenceBrowserWorkflow {
275
382
  : [id];
276
383
  };
277
384
 
278
- commitSelection = async () => {
279
- if (this.committing || this.options.readOnly) return;
385
+ commitSelection = async (selection: readonly string[] = this.workingSelection) => {
386
+ if (this.#disposed || this.committing || this.options.readOnly || this.browsingUnavailable)
387
+ return false;
280
388
  this.committing = true;
281
389
  try {
282
- const committed = await this.options.onCommit([...this.workingSelection]);
283
- if (committed !== false && !this.options.readOnly) this.#closeBrowser();
390
+ const committed = await this.options.onCommit([...selection], this.collection.slug);
391
+ if (this.#disposed || committed === false || this.options.readOnly) return false;
392
+ this.#closeBrowser();
393
+ return true;
284
394
  } catch (cause) {
395
+ if (this.#disposed) return false;
285
396
  this.options.notifications.error({
286
397
  title: this.options.runtime.i18n.t("reference:updateNotConfirmed"),
287
398
  message:
@@ -289,17 +400,12 @@ export class ReferenceBrowserWorkflow {
289
400
  ? cause.message
290
401
  : this.options.runtime.i18n.t("reference:selectedNotSaved"),
291
402
  });
403
+ return false;
292
404
  } finally {
293
- this.committing = false;
405
+ if (!this.#disposed) this.committing = false;
294
406
  }
295
407
  };
296
408
 
297
- chooseFilter = (key: string) => {
298
- if (this.committing) return;
299
- this.filterKey = key;
300
- this.page = 1;
301
- };
302
-
303
409
  handleSearch = (event: Event) => {
304
410
  if (this.committing) return;
305
411
  this.query = (event.currentTarget as HTMLInputElement).value;
@@ -308,15 +414,16 @@ export class ReferenceBrowserWorkflow {
308
414
 
309
415
  openNewDocument = () => {
310
416
  if (this.form.submitting || !this.canCreateDocument) return;
417
+ this.#cancelEditorLoad();
311
418
  this.editorDocument = undefined;
312
- this.form.setResource({ collection: this.options.collection.slug });
419
+ this.form.setResource({ collection: this.collection.slug });
313
420
  const values = {
314
- ...initialFormValues(this.options.collection.fields),
421
+ ...initialFormValues(this.collection.fields),
315
422
  ...this.options.defaultValues,
316
423
  };
317
- this.form.reset(values, this.options.collection.fields);
424
+ this.form.reset(values, this.collection.fields);
318
425
  this.form.setAccess(undefined, "create");
319
- this.selectedFiles = undefined;
426
+ this.upload.reset();
320
427
  this.newUserPassword = "";
321
428
  this.newUserPasswordConfirmation = "";
322
429
  this.credentialIssue = undefined;
@@ -327,16 +434,13 @@ export class ReferenceBrowserWorkflow {
327
434
  };
328
435
 
329
436
  openKnownDocument = (document: AdminDocument) => {
330
- this.#lookup.remember(document);
331
- this.#openEditor(document);
437
+ // List rows use populated relationships for display. Fetch editable wire values.
438
+ this.screen = "document";
439
+ this.#loadEditor(document.id);
332
440
  };
333
441
 
334
442
  saveEditor = async () => {
335
443
  if (this.form.submitting || !this.canSave) return false;
336
- const creatingUpload =
337
- this.creating &&
338
- this.options.collection.capabilities.upload &&
339
- this.selectedFile !== undefined;
340
444
  const request = new AbortController();
341
445
  this.#saveRequest?.abort();
342
446
  this.#saveRequest = request;
@@ -346,8 +450,28 @@ export class ReferenceBrowserWorkflow {
346
450
  const saved = await this.form.submit(this.validationFields, this.creating, async (values) => {
347
451
  if (this.options.readOnly) throw new Error("This field is read-only.");
348
452
  if (this.editorDocument !== undefined) {
453
+ if (this.collection.capabilities.upload) {
454
+ return this.options.runtime.client.updateUpload(
455
+ this.collection.slug,
456
+ this.editorDocument.id,
457
+ {
458
+ data: values,
459
+ file: this.upload.file,
460
+ filename: this.upload.filename,
461
+ image: this.upload.image,
462
+ },
463
+ {
464
+ revision:
465
+ typeof this.editorDocument._revision === "number"
466
+ ? this.editorDocument._revision
467
+ : undefined,
468
+ signal: request.signal,
469
+ locale: this.options.locale,
470
+ }
471
+ );
472
+ }
349
473
  return this.options.runtime.client.update(
350
- this.options.collection.slug,
474
+ this.collection.slug,
351
475
  this.editorDocument.id,
352
476
  values,
353
477
  {
@@ -360,16 +484,16 @@ export class ReferenceBrowserWorkflow {
360
484
  }
361
485
  );
362
486
  }
363
- if (this.options.collection.capabilities.auth) {
487
+ if (this.collection.capabilities.auth) {
364
488
  return this.options.runtime.client.createAuthUser(
365
- this.options.collection.slug,
489
+ this.collection.slug,
366
490
  values,
367
491
  this.newUserPassword,
368
492
  { signal: request.signal, locale: this.options.locale }
369
493
  );
370
494
  }
371
- if (!this.options.collection.capabilities.upload) {
372
- return this.options.runtime.client.create(this.options.collection.slug, values, {
495
+ if (!this.collection.capabilities.upload) {
496
+ return this.options.runtime.client.create(this.collection.slug, values, {
373
497
  signal: request.signal,
374
498
  locale: this.options.locale,
375
499
  });
@@ -377,7 +501,9 @@ export class ReferenceBrowserWorkflow {
377
501
  if (this.selectedFile === undefined) {
378
502
  throw new Error(this.options.runtime.i18n.t("uploads:chooseFileBeforeCreating"));
379
503
  }
380
- return this.options.runtime.client.upload(this.options.collection.slug, this.selectedFile, {
504
+ return this.options.runtime.client.upload(this.collection.slug, this.selectedFile, {
505
+ filename: this.upload.filename,
506
+ image: this.upload.image,
381
507
  data: values,
382
508
  signal: request.signal,
383
509
  locale: this.options.locale,
@@ -386,35 +512,36 @@ export class ReferenceBrowserWorkflow {
386
512
  if (request.signal.aborted || this.options.readOnly) return false;
387
513
  const wasCreating = this.editorDocument === undefined;
388
514
  this.editorDocument = saved;
515
+ this.upload.reset(saved);
516
+ this.form.setResource({ collection: this.collection.slug, id: saved.id });
389
517
  this.#lookup.remember(saved);
390
518
  this.workingSelection = this.options.hasMany
391
519
  ? [...new Set([...this.workingSelection, saved.id])]
392
520
  : [saved.id];
393
521
  this.options.runtime.documentsChanged();
394
- this.form.reset(
395
- documentFormValues(this.options.collection.fields, saved),
396
- this.options.collection.fields
397
- );
522
+ this.form.reset(documentFormValues(this.collection.fields, saved), this.collection.fields);
398
523
  this.form.setLocalization(this.options.locale, saved._localization?.sources);
399
524
  this.options.notifications.success({
400
525
  title: wasCreating
401
526
  ? this.options.runtime.i18n.t("reference:createdLabel", {
402
527
  label: this.options.runtime.i18n.text(
403
- this.options.collection.labels.singular,
404
- this.options.collection.labels.singularTranslations
528
+ this.collection.labels.singular,
529
+ this.collection.labels.singularTranslations
405
530
  ),
406
531
  })
407
532
  : this.options.runtime.i18n.t("documents:updated"),
408
533
  });
409
- if (wasCreating) this.screen = "list";
534
+ if (wasCreating && !this.directEntry) this.screen = "list";
410
535
  this.newUserPassword = "";
411
536
  this.newUserPasswordConfirmation = "";
412
537
  this.credentialIssue = undefined;
538
+ if (!wasCreating) this.#loadEditorAccess(saved.id);
539
+ if (wasCreating && this.directEntry && !(await this.commitSelection())) this.#returnToList();
413
540
  return true;
414
541
  } catch (cause) {
415
542
  if (request.signal.aborted || this.options.readOnly) return false;
416
543
  if (
417
- creatingUpload &&
544
+ this.collection.capabilities.upload &&
418
545
  !(cause instanceof FormValidationError) &&
419
546
  (!(cause instanceof RiduError) || cause.status >= 500)
420
547
  ) {
@@ -454,28 +581,25 @@ export class ReferenceBrowserWorkflow {
454
581
  };
455
582
 
456
583
  #validateNewUserPassword() {
457
- if (!this.creatingAuthUser || this.options.collection.authSettings === undefined) {
584
+ if (!this.creatingAuthUser || this.collection.authSettings === undefined) {
458
585
  return undefined;
459
586
  }
460
587
  if (this.newUserPassword === "")
461
588
  return this.options.runtime.i18n.t("reference:enterNewAccountPassword");
462
- if (
463
- Array.from(this.newUserPassword).length <
464
- this.options.collection.authSettings.passwordMinLength
465
- ) {
589
+ if (Array.from(this.newUserPassword).length < this.collection.authSettings.passwordMinLength) {
466
590
  return this.options.runtime.i18n.t("reference:passwordTooShort", {
467
591
  minimum: this.options.runtime.i18n.formatNumber(
468
- this.options.collection.authSettings.passwordMinLength
592
+ this.collection.authSettings.passwordMinLength
469
593
  ),
470
594
  });
471
595
  }
472
596
  if (
473
597
  new TextEncoder().encode(this.newUserPassword).length >
474
- this.options.collection.authSettings.passwordMaxBytes
598
+ this.collection.authSettings.passwordMaxBytes
475
599
  ) {
476
600
  return this.options.runtime.i18n.t("reference:passwordTooLong", {
477
601
  maximum: this.options.runtime.i18n.formatNumber(
478
- this.options.collection.authSettings.passwordMaxBytes
602
+ this.collection.authSettings.passwordMaxBytes
479
603
  ),
480
604
  });
481
605
  }
@@ -485,6 +609,17 @@ export class ReferenceBrowserWorkflow {
485
609
  return undefined;
486
610
  }
487
611
 
612
+ requestNavigation = (navigate: () => void) => {
613
+ if (this.committing || this.form.submitting) return;
614
+ if (this.editorDirty) {
615
+ this.#pendingDismiss = "navigate";
616
+ this.#pendingNavigation = navigate;
617
+ this.confirmDiscard = true;
618
+ return;
619
+ }
620
+ navigate();
621
+ };
622
+
488
623
  requestBack = () => {
489
624
  if (this.form.submitting) return;
490
625
  if (this.editorDirty) {
@@ -512,6 +647,12 @@ export class ReferenceBrowserWorkflow {
512
647
  discardChanges = () => {
513
648
  if (this.form.submitting) return;
514
649
  this.confirmDiscard = false;
650
+ if (this.#pendingDismiss === "navigate") {
651
+ const navigate = this.#pendingNavigation;
652
+ this.#pendingNavigation = undefined;
653
+ navigate?.();
654
+ return;
655
+ }
515
656
  if (this.#pendingDismiss === "close") {
516
657
  this.#closeBrowser();
517
658
  return;
@@ -525,50 +666,47 @@ export class ReferenceBrowserWorkflow {
525
666
  isImage = (document: AdminDocument | undefined) =>
526
667
  typeof document?.mimeType === "string" && document.mimeType.startsWith("image/");
527
668
 
528
- formatBytes = (value: unknown) => {
529
- if (typeof value !== "number" || !Number.isFinite(value)) return "";
530
- if (value < 1_024)
531
- return this.options.runtime.i18n.formatNumber(value, { style: "unit", unit: "byte" });
532
- if (value < 1_048_576)
533
- return this.options.runtime.i18n.formatNumber(value / 1_024, {
534
- maximumFractionDigits: 1,
535
- style: "unit",
536
- unit: "kilobyte",
537
- });
538
- return this.options.runtime.i18n.formatNumber(value / 1_048_576, {
539
- maximumFractionDigits: 1,
540
- style: "unit",
541
- unit: "megabyte",
542
- });
543
- };
669
+ #cancelEditorLoad() {
670
+ this.#editorLoad?.abort();
671
+ this.#editorLoad = undefined;
672
+ }
544
673
 
545
- #loadEditor(id: string, signal?: AbortSignal) {
674
+ #loadEditor(id: string) {
675
+ this.#cancelEditorLoad();
676
+ const request = new AbortController();
677
+ this.#editorLoad = request;
546
678
  this.editorLoading = true;
547
679
  this.editorError = undefined;
548
- this.#lookup
549
- .loadDocument(this.options.collection.slug, id, this.options.locale, signal)
680
+ this.options.runtime.client
681
+ .find(this.collection.slug, id, {
682
+ depth: 0,
683
+ locale: this.options.locale,
684
+ signal: request.signal,
685
+ })
550
686
  .then((document) => {
551
- if (signal?.aborted) return;
552
- if (document === undefined) {
553
- this.editorError =
554
- this.#lookup.error ?? this.options.runtime.i18n.t("reference:relatedLoadFailed");
555
- this.editorLoading = false;
556
- return;
557
- }
687
+ if (request.signal.aborted) return;
688
+ this.#editorLoad = undefined;
558
689
  this.#openEditor(document);
690
+ })
691
+ .catch((cause: unknown) => {
692
+ if (request.signal.aborted) return;
693
+ this.#editorLoad = undefined;
694
+ this.editorLoading = false;
695
+ this.editorError =
696
+ cause instanceof Error
697
+ ? cause.message
698
+ : this.options.runtime.i18n.t("reference:relatedLoadFailed");
559
699
  });
560
700
  }
561
701
 
562
702
  #openEditor(document: AdminDocument) {
563
- this.form.setResource({ collection: this.options.collection.slug, id: document.id });
703
+ this.#cancelEditorLoad();
704
+ this.form.setResource({ collection: this.collection.slug, id: document.id });
564
705
  this.editorDocument = document;
565
- this.form.reset(
566
- documentFormValues(this.options.collection.fields, document),
567
- this.options.collection.fields
568
- );
706
+ this.form.reset(documentFormValues(this.collection.fields, document), this.collection.fields);
569
707
  this.form.setLocalization(this.options.locale, document._localization?.sources);
570
708
  this.form.setAccess(undefined, "update");
571
- this.selectedFiles = undefined;
709
+ this.upload.reset(document);
572
710
  this.newUserPassword = "";
573
711
  this.newUserPasswordConfirmation = "";
574
712
  this.credentialIssue = undefined;
@@ -580,12 +718,13 @@ export class ReferenceBrowserWorkflow {
580
718
 
581
719
  #loadEditorAccess(id: string | undefined, data?: Record<string, unknown>) {
582
720
  this.#editorAccess?.abort();
721
+ this.form.setAccess(undefined, id === undefined ? "create" : "update");
583
722
  const request = new AbortController();
584
723
  this.#editorAccess = request;
585
724
  this.editorLoading = true;
586
725
  this.editorError = undefined;
587
726
  this.options.runtime.client
588
- .collectionAccess(this.options.collection.slug, {
727
+ .collectionAccess(this.collection.slug, {
589
728
  ...(id === undefined ? { data } : { id }),
590
729
  signal: request.signal,
591
730
  locale: this.options.locale,
@@ -612,12 +751,15 @@ export class ReferenceBrowserWorkflow {
612
751
  }
613
752
 
614
753
  #returnToList() {
754
+ this.#cancelEditorLoad();
615
755
  this.#editorAccess?.abort();
616
756
  this.#editorAccess = undefined;
617
757
  this.screen = "list";
758
+ this.#directEntry = false;
618
759
  this.editorDocument = undefined;
760
+ this.editorLoading = false;
619
761
  this.editorError = undefined;
620
- this.selectedFiles = undefined;
762
+ this.upload.reset();
621
763
  this.newUserPassword = "";
622
764
  this.newUserPasswordConfirmation = "";
623
765
  this.credentialIssue = undefined;
@@ -626,50 +768,13 @@ export class ReferenceBrowserWorkflow {
626
768
  }
627
769
 
628
770
  #closeBrowser() {
771
+ this.#cancelEditorLoad();
629
772
  this.form.disposeBindings();
630
773
  this.options.setOpen(false);
631
774
  this.options.onClose();
632
775
  }
633
776
  }
634
777
 
635
- function findDisplayField(fields: readonly SchemaField[]) {
636
- return (
637
- fields.find((field) => field.name === "title" || field.name === "name") ??
638
- fields.find((field) => field.type === "text" && field.name !== "email") ??
639
- fields.find((field) => field.type === "text" || field.type === "email")
640
- );
641
- }
642
-
643
- function createCollectionFilters(collection: SchemaCollection, i18n: AdminRuntime["i18n"]) {
644
- const result: FilterOption[] = [{ key: "", label: i18n.t("reference:all"), filter: undefined }];
645
- if (collection.capabilities.upload) {
646
- result.push(
647
- {
648
- key: "images",
649
- label: i18n.t("reference:images"),
650
- filter: { field: "mimeType", operator: "like", value: "image/" },
651
- },
652
- {
653
- key: "documents",
654
- label: i18n.t("reference:documents"),
655
- filter: { field: "mimeType", operator: "like", value: "application/" },
656
- }
657
- );
658
- return result;
659
- }
660
- const select =
661
- collection.fields.find((field) => field.name === "status" && field.select !== undefined) ??
662
- collection.fields.find((field) => field.select !== undefined);
663
- for (const option of select?.select?.options ?? []) {
664
- result.push({
665
- key: option.value,
666
- label: i18n.text(option.label, option.labelTranslations),
667
- filter: { field: select?.name ?? "", operator: "equals", value: option.value },
668
- });
669
- }
670
- return result;
671
- }
672
-
673
778
  function scopeField(field: SchemaField, prefix: string): SchemaField {
674
779
  return {
675
780
  ...field,
@@ -698,9 +803,3 @@ function scopeField(field: SchemaField, prefix: string): SchemaField {
698
803
  }),
699
804
  };
700
805
  }
701
-
702
- function humanize(value: string, language: string) {
703
- return value
704
- .replaceAll(/[_-]+/g, " ")
705
- .replace(/\b\w/g, (letter) => letter.toLocaleUpperCase(language));
706
- }