@riducms/admin 0.2.3 → 0.4.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 +29 -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 +131 -135
  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 +159 -0
  120. package/src/features/{auth → account}/account-security-route.svelte +140 -42
  121. package/src/features/api-reference/api-reference-code.svelte +114 -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 +30 -60
  133. package/src/features/auth/create-first-user-route.svelte +83 -122
  134. package/src/features/auth/forgot-password-route.svelte +32 -24
  135. package/src/features/auth/login-route.svelte +69 -117
  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 +28 -23
  139. package/src/features/auth/reset-password-route.svelte +111 -41
  140. package/src/features/auth/verify-email-route.svelte +52 -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 +531 -617
  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 +336 -239
  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 +148 -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,14 @@ export class ReferenceBrowserWorkflow {
360
484
  }
361
485
  );
362
486
  }
363
- if (this.options.collection.capabilities.auth) {
364
- return this.options.runtime.client.createAuthUser(
365
- this.options.collection.slug,
366
- values,
367
- this.newUserPassword,
487
+ if (this.collection.capabilities.auth) {
488
+ return this.options.runtime.client.auth.createUser(
489
+ { collection: this.collection.slug, data: values, password: this.newUserPassword },
368
490
  { signal: request.signal, locale: this.options.locale }
369
491
  );
370
492
  }
371
- if (!this.options.collection.capabilities.upload) {
372
- return this.options.runtime.client.create(this.options.collection.slug, values, {
493
+ if (!this.collection.capabilities.upload) {
494
+ return this.options.runtime.client.create(this.collection.slug, values, {
373
495
  signal: request.signal,
374
496
  locale: this.options.locale,
375
497
  });
@@ -377,7 +499,9 @@ export class ReferenceBrowserWorkflow {
377
499
  if (this.selectedFile === undefined) {
378
500
  throw new Error(this.options.runtime.i18n.t("uploads:chooseFileBeforeCreating"));
379
501
  }
380
- return this.options.runtime.client.upload(this.options.collection.slug, this.selectedFile, {
502
+ return this.options.runtime.client.upload(this.collection.slug, this.selectedFile, {
503
+ filename: this.upload.filename,
504
+ image: this.upload.image,
381
505
  data: values,
382
506
  signal: request.signal,
383
507
  locale: this.options.locale,
@@ -386,35 +510,36 @@ export class ReferenceBrowserWorkflow {
386
510
  if (request.signal.aborted || this.options.readOnly) return false;
387
511
  const wasCreating = this.editorDocument === undefined;
388
512
  this.editorDocument = saved;
513
+ this.upload.reset(saved);
514
+ this.form.setResource({ collection: this.collection.slug, id: saved.id });
389
515
  this.#lookup.remember(saved);
390
516
  this.workingSelection = this.options.hasMany
391
517
  ? [...new Set([...this.workingSelection, saved.id])]
392
518
  : [saved.id];
393
519
  this.options.runtime.documentsChanged();
394
- this.form.reset(
395
- documentFormValues(this.options.collection.fields, saved),
396
- this.options.collection.fields
397
- );
520
+ this.form.reset(documentFormValues(this.collection.fields, saved), this.collection.fields);
398
521
  this.form.setLocalization(this.options.locale, saved._localization?.sources);
399
522
  this.options.notifications.success({
400
523
  title: wasCreating
401
524
  ? this.options.runtime.i18n.t("reference:createdLabel", {
402
525
  label: this.options.runtime.i18n.text(
403
- this.options.collection.labels.singular,
404
- this.options.collection.labels.singularTranslations
526
+ this.collection.labels.singular,
527
+ this.collection.labels.singularTranslations
405
528
  ),
406
529
  })
407
530
  : this.options.runtime.i18n.t("documents:updated"),
408
531
  });
409
- if (wasCreating) this.screen = "list";
532
+ if (wasCreating && !this.directEntry) this.screen = "list";
410
533
  this.newUserPassword = "";
411
534
  this.newUserPasswordConfirmation = "";
412
535
  this.credentialIssue = undefined;
536
+ if (!wasCreating) this.#loadEditorAccess(saved.id);
537
+ if (wasCreating && this.directEntry && !(await this.commitSelection())) this.#returnToList();
413
538
  return true;
414
539
  } catch (cause) {
415
540
  if (request.signal.aborted || this.options.readOnly) return false;
416
541
  if (
417
- creatingUpload &&
542
+ this.collection.capabilities.upload &&
418
543
  !(cause instanceof FormValidationError) &&
419
544
  (!(cause instanceof RiduError) || cause.status >= 500)
420
545
  ) {
@@ -454,28 +579,25 @@ export class ReferenceBrowserWorkflow {
454
579
  };
455
580
 
456
581
  #validateNewUserPassword() {
457
- if (!this.creatingAuthUser || this.options.collection.authSettings === undefined) {
582
+ if (!this.creatingAuthUser || this.collection.authSettings === undefined) {
458
583
  return undefined;
459
584
  }
460
585
  if (this.newUserPassword === "")
461
586
  return this.options.runtime.i18n.t("reference:enterNewAccountPassword");
462
- if (
463
- Array.from(this.newUserPassword).length <
464
- this.options.collection.authSettings.passwordMinLength
465
- ) {
587
+ if (Array.from(this.newUserPassword).length < this.collection.authSettings.passwordMinLength) {
466
588
  return this.options.runtime.i18n.t("reference:passwordTooShort", {
467
589
  minimum: this.options.runtime.i18n.formatNumber(
468
- this.options.collection.authSettings.passwordMinLength
590
+ this.collection.authSettings.passwordMinLength
469
591
  ),
470
592
  });
471
593
  }
472
594
  if (
473
595
  new TextEncoder().encode(this.newUserPassword).length >
474
- this.options.collection.authSettings.passwordMaxBytes
596
+ this.collection.authSettings.passwordMaxBytes
475
597
  ) {
476
598
  return this.options.runtime.i18n.t("reference:passwordTooLong", {
477
599
  maximum: this.options.runtime.i18n.formatNumber(
478
- this.options.collection.authSettings.passwordMaxBytes
600
+ this.collection.authSettings.passwordMaxBytes
479
601
  ),
480
602
  });
481
603
  }
@@ -485,6 +607,17 @@ export class ReferenceBrowserWorkflow {
485
607
  return undefined;
486
608
  }
487
609
 
610
+ requestNavigation = (navigate: () => void) => {
611
+ if (this.committing || this.form.submitting) return;
612
+ if (this.editorDirty) {
613
+ this.#pendingDismiss = "navigate";
614
+ this.#pendingNavigation = navigate;
615
+ this.confirmDiscard = true;
616
+ return;
617
+ }
618
+ navigate();
619
+ };
620
+
488
621
  requestBack = () => {
489
622
  if (this.form.submitting) return;
490
623
  if (this.editorDirty) {
@@ -512,6 +645,12 @@ export class ReferenceBrowserWorkflow {
512
645
  discardChanges = () => {
513
646
  if (this.form.submitting) return;
514
647
  this.confirmDiscard = false;
648
+ if (this.#pendingDismiss === "navigate") {
649
+ const navigate = this.#pendingNavigation;
650
+ this.#pendingNavigation = undefined;
651
+ navigate?.();
652
+ return;
653
+ }
515
654
  if (this.#pendingDismiss === "close") {
516
655
  this.#closeBrowser();
517
656
  return;
@@ -525,50 +664,47 @@ export class ReferenceBrowserWorkflow {
525
664
  isImage = (document: AdminDocument | undefined) =>
526
665
  typeof document?.mimeType === "string" && document.mimeType.startsWith("image/");
527
666
 
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
- };
667
+ #cancelEditorLoad() {
668
+ this.#editorLoad?.abort();
669
+ this.#editorLoad = undefined;
670
+ }
544
671
 
545
- #loadEditor(id: string, signal?: AbortSignal) {
672
+ #loadEditor(id: string) {
673
+ this.#cancelEditorLoad();
674
+ const request = new AbortController();
675
+ this.#editorLoad = request;
546
676
  this.editorLoading = true;
547
677
  this.editorError = undefined;
548
- this.#lookup
549
- .loadDocument(this.options.collection.slug, id, this.options.locale, signal)
678
+ this.options.runtime.client
679
+ .find(this.collection.slug, id, {
680
+ depth: 0,
681
+ locale: this.options.locale,
682
+ signal: request.signal,
683
+ })
550
684
  .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
- }
685
+ if (request.signal.aborted) return;
686
+ this.#editorLoad = undefined;
558
687
  this.#openEditor(document);
688
+ })
689
+ .catch((cause: unknown) => {
690
+ if (request.signal.aborted) return;
691
+ this.#editorLoad = undefined;
692
+ this.editorLoading = false;
693
+ this.editorError =
694
+ cause instanceof Error
695
+ ? cause.message
696
+ : this.options.runtime.i18n.t("reference:relatedLoadFailed");
559
697
  });
560
698
  }
561
699
 
562
700
  #openEditor(document: AdminDocument) {
563
- this.form.setResource({ collection: this.options.collection.slug, id: document.id });
701
+ this.#cancelEditorLoad();
702
+ this.form.setResource({ collection: this.collection.slug, id: document.id });
564
703
  this.editorDocument = document;
565
- this.form.reset(
566
- documentFormValues(this.options.collection.fields, document),
567
- this.options.collection.fields
568
- );
704
+ this.form.reset(documentFormValues(this.collection.fields, document), this.collection.fields);
569
705
  this.form.setLocalization(this.options.locale, document._localization?.sources);
570
706
  this.form.setAccess(undefined, "update");
571
- this.selectedFiles = undefined;
707
+ this.upload.reset(document);
572
708
  this.newUserPassword = "";
573
709
  this.newUserPasswordConfirmation = "";
574
710
  this.credentialIssue = undefined;
@@ -580,12 +716,13 @@ export class ReferenceBrowserWorkflow {
580
716
 
581
717
  #loadEditorAccess(id: string | undefined, data?: Record<string, unknown>) {
582
718
  this.#editorAccess?.abort();
719
+ this.form.setAccess(undefined, id === undefined ? "create" : "update");
583
720
  const request = new AbortController();
584
721
  this.#editorAccess = request;
585
722
  this.editorLoading = true;
586
723
  this.editorError = undefined;
587
724
  this.options.runtime.client
588
- .collectionAccess(this.options.collection.slug, {
725
+ .collectionAccess(this.collection.slug, {
589
726
  ...(id === undefined ? { data } : { id }),
590
727
  signal: request.signal,
591
728
  locale: this.options.locale,
@@ -612,12 +749,15 @@ export class ReferenceBrowserWorkflow {
612
749
  }
613
750
 
614
751
  #returnToList() {
752
+ this.#cancelEditorLoad();
615
753
  this.#editorAccess?.abort();
616
754
  this.#editorAccess = undefined;
617
755
  this.screen = "list";
756
+ this.#directEntry = false;
618
757
  this.editorDocument = undefined;
758
+ this.editorLoading = false;
619
759
  this.editorError = undefined;
620
- this.selectedFiles = undefined;
760
+ this.upload.reset();
621
761
  this.newUserPassword = "";
622
762
  this.newUserPasswordConfirmation = "";
623
763
  this.credentialIssue = undefined;
@@ -626,50 +766,13 @@ export class ReferenceBrowserWorkflow {
626
766
  }
627
767
 
628
768
  #closeBrowser() {
769
+ this.#cancelEditorLoad();
629
770
  this.form.disposeBindings();
630
771
  this.options.setOpen(false);
631
772
  this.options.onClose();
632
773
  }
633
774
  }
634
775
 
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
776
  function scopeField(field: SchemaField, prefix: string): SchemaField {
674
777
  return {
675
778
  ...field,
@@ -698,9 +801,3 @@ function scopeField(field: SchemaField, prefix: string): SchemaField {
698
801
  }),
699
802
  };
700
803
  }
701
-
702
- function humanize(value: string, language: string) {
703
- return value
704
- .replaceAll(/[_-]+/g, " ")
705
- .replace(/\b\w/g, (letter) => letter.toLocaleUpperCase(language));
706
- }