@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,65 +1,77 @@
1
1
  <script lang="ts">
2
- import { untrack } from "svelte";
3
2
  import { connectDocumentLiveValidation } from "@admin/core/forms/live-validation.svelte";
4
- import { useNavigate } from "@hvniel/svelte-router";
5
3
  import PaletteIcon from "~icons/lucide/palette";
6
4
  import RotateCcwIcon from "~icons/lucide/rotate-ccw";
7
5
  import UserRoundIcon from "~icons/lucide/user-round";
8
6
 
9
7
  import { Banner } from "@admin/components/ui/banner";
10
- import { Button } from "@admin/components/ui/button";
8
+ import { Button } from "@riducms/ui";
11
9
  import { Select, SelectContent, SelectItem, SelectTrigger } from "@admin/components/ui/select";
12
10
  import { Skeleton } from "@admin/components/ui/skeleton";
13
11
  import { FormController, FormValidationError } from "@admin/core/forms/form-controller.svelte";
14
12
  import { getNotificationCenter } from "@admin/core/notifications/notification-center.svelte";
15
13
  import { adminRoutePatterns } from "@admin/core/routing/admin-paths";
16
14
  import { getAdminRuntime, type ThemePreference } from "@admin/core/runtime/admin-runtime.svelte";
15
+ import { getAdminBootstrapCoordinator } from "@admin/core/bootstrap/admin-bootstrap";
16
+ import type { SchemaField } from "@riducms/protocol";
17
17
  import FieldLayout from "@admin/fields/field-layout.svelte";
18
- import AccountNavigation from "@admin/features/auth/account-navigation.svelte";
18
+ import AccountNavigation from "@admin/features/account/account-navigation.svelte";
19
+ import { useLocation } from "@hvniel/svelte-router";
19
20
 
20
21
  const runtime = getAdminRuntime();
22
+ const bootstrap = getAdminBootstrapCoordinator();
21
23
  const notifications = getNotificationCenter();
22
- const navigate = useNavigate();
24
+ const location = useLocation();
25
+
23
26
  const form = new FormController({}, runtime.i18n);
24
27
  connectDocumentLiveValidation(form, runtime.client);
25
- $effect(() => () => form.disposeBindings());
28
+
29
+ let profileRequest: AbortController | undefined;
30
+ let activeProfileKey = "";
31
+
26
32
  let loading = $state(true);
27
33
  let error = $state<string>();
34
+
28
35
  let profilePending = $state(false);
36
+
29
37
  let pendingThemeWrites = $state(0);
30
38
  const themePending = $derived(pendingThemeWrites > 0);
31
39
  let languagePending = $state(false);
32
40
  let timeZonePending = $state(false);
33
41
  let resetPending = $state(false);
42
+
34
43
  const collection = $derived(runtime.authCollection);
35
44
  const user = $derived(runtime.session?.user);
36
- const contentLocale = $derived(runtime.contentLocale);
45
+ const sessionID = $derived(runtime.session?.id);
46
+ const userID = $derived(runtime.session?.user.id);
47
+ const requestedLocale = $derived(new URLSearchParams(location.current.search).get("locale"));
48
+ const contentLocale = $derived(runtime.resolveContentLocale(requestedLocale));
49
+
37
50
  const selectedLanguageLabel = $derived.by(() => {
38
51
  const language = runtime.i18n.languages.find((item) => item.code === runtime.i18n.language);
52
+
39
53
  return language === undefined
40
54
  ? runtime.i18n.language
41
55
  : runtime.i18n.text(language.label, language.labelTranslations);
42
56
  });
57
+
43
58
  const selectedTimeZoneLabel = $derived.by(() => {
44
59
  const timeZone = runtime.i18n.timeZones.find((item) => item.id === runtime.i18n.timeZone);
60
+
45
61
  return timeZone === undefined
46
62
  ? runtime.i18n.timeZone
47
63
  : runtime.i18n.text(timeZone.label, timeZone.labelTranslations);
48
64
  });
49
- const accountComponents = $derived(
50
- runtime.accountComponents.filter((component) => component.surface === "profile")
51
- );
52
- const replacement = $derived(
53
- accountComponents.find((component) => component.position === "replace")
54
- );
55
- const beforeComponents = $derived(
56
- accountComponents.filter((component) => component.position === "before")
65
+
66
+ const accountComponents = runtime.config.extensions.account.filter(
67
+ (component) => component.surface === "profile"
57
68
  );
58
- const afterComponents = $derived(
59
- accountComponents.filter(
60
- (component) => component.position === undefined || component.position === "after"
61
- )
69
+ const replacement = accountComponents.find((component) => component.position === "replace");
70
+ const beforeComponents = accountComponents.filter((component) => component.position === "before");
71
+ const afterComponents = accountComponents.filter(
72
+ (component) => component.position === undefined || component.position === "after"
62
73
  );
74
+
63
75
  const host = {
64
76
  refreshUser,
65
77
  logout,
@@ -67,69 +79,164 @@
67
79
  notifications[tone]({ title, message }),
68
80
  };
69
81
 
70
- $effect(() => {
82
+ function adoptPreparedProfile(
83
+ slug: string,
84
+ id: string,
85
+ locale: string | undefined,
86
+ fields: readonly SchemaField[]
87
+ ) {
88
+ // The coordinator binds this exact pathname and query to the current runtime context. The
89
+ // effect's owner key prevents a retained account route from reusing it after a session change.
90
+ const route = bootstrap.routeData(location.current.pathname, location.current.search);
91
+ if (route?.kind !== "account") return false;
92
+
93
+ const { document, access } = route.document;
94
+ profileRequest?.abort();
95
+ profileRequest = undefined;
96
+ profilePending = false;
97
+ loading = false;
98
+ error = undefined;
99
+ form.setResource({ collection: slug, id });
100
+ form.setLocalization(locale);
101
+ if (document.error !== undefined || access.error !== undefined) {
102
+ form.reset({}, fields);
103
+ form.setAccess(undefined, "update");
104
+ error = (document.error ?? access.error)?.message;
105
+
106
+ return true;
107
+ }
108
+
109
+ form.reset(document.value, fields);
110
+ form.setLocalization(locale, document.value._localization?.sources);
111
+ form.setAccess(access.value, "update");
112
+
113
+ return true;
114
+ }
115
+
116
+ $effect.pre(() => {
71
117
  const slug = collection?.slug;
72
- const id = user?.id;
118
+ const owner = sessionID;
119
+ const id = userID;
73
120
  const locale = contentLocale;
74
- if (slug === undefined || id === undefined) return;
75
- const abort = new AbortController();
76
- untrack(() => load(slug, id, locale, abort.signal));
77
- return () => abort.abort();
121
+ const fields = collection?.fields;
122
+ if (slug === undefined || owner === undefined || id === undefined || fields === undefined)
123
+ return;
124
+
125
+ const key = `${runtime.manifestRevision}:${slug}:${owner}:${id}:${locale ?? "default"}`;
126
+ if (key === activeProfileKey) return;
127
+
128
+ activeProfileKey = key;
129
+ if (adoptPreparedProfile(slug, id, locale, fields)) return;
130
+
131
+ const request = beginProfileRequest();
132
+ profilePending = false;
133
+ load(slug, owner, id, locale, request);
78
134
  });
79
135
 
80
- $effect(() => runtime.registerContentLocaleBlocker(() => form.dirty || form.submitting));
136
+ $effect(() => {
137
+ const unregisterBlocker = runtime.registerContentLocaleBlocker(
138
+ () => form.dirty || form.submitting
139
+ );
81
140
 
82
- async function load(slug: string, id: string, locale: string | undefined, signal: AbortSignal) {
141
+ return () => {
142
+ unregisterBlocker();
143
+ profileRequest?.abort();
144
+ form.disposeBindings();
145
+ };
146
+ });
147
+
148
+ function beginProfileRequest() {
149
+ profileRequest?.abort();
150
+ const request = new AbortController();
151
+ profileRequest = request;
152
+
153
+ return request;
154
+ }
155
+
156
+ function ownsProfileRequest(request: AbortController, owner: string) {
157
+ return profileRequest === request && !request.signal.aborted && runtime.session?.id === owner;
158
+ }
159
+
160
+ async function load(
161
+ slug: string,
162
+ owner: string,
163
+ id: string,
164
+ locale: string | undefined,
165
+ request: AbortController
166
+ ) {
83
167
  form.setResource({ collection: slug, id });
84
168
  form.setLocalization(locale);
85
169
  loading = true;
86
170
  error = undefined;
171
+
87
172
  try {
88
173
  const [document, access] = await Promise.all([
89
- runtime.client.find(slug, id, { signal, locale }),
90
- runtime.client.collectionAccess(slug, { id, signal, locale }),
174
+ runtime.client.find(slug, id, { signal: request.signal, locale }),
175
+ runtime.client.collectionAccess(slug, { id, signal: request.signal, locale }),
91
176
  ]);
92
- if (signal.aborted) return;
177
+ if (!ownsProfileRequest(request, owner)) return;
178
+
93
179
  form.reset(document, collection?.fields ?? []);
94
180
  form.setLocalization(locale, document._localization?.sources);
95
181
  form.setAccess(access, "update");
96
182
  } catch (cause) {
97
- if (!signal.aborted) {
183
+ if (ownsProfileRequest(request, owner)) {
98
184
  error =
99
185
  cause instanceof Error ? cause.message : runtime.i18n.t("account:profileLoadFailed");
100
186
  }
101
187
  } finally {
102
- if (!signal.aborted) loading = false;
188
+ if (ownsProfileRequest(request, owner)) loading = false;
189
+
190
+ if (profileRequest === request) profileRequest = undefined;
103
191
  }
104
192
  }
105
193
 
106
194
  async function saveProfile(event: SubmitEvent) {
107
195
  event.preventDefault();
108
196
  if (collection === undefined || user === undefined) return;
197
+
198
+ const slug = collection.slug;
199
+ const owner = runtime.session?.id;
200
+ const id = user.id;
201
+ if (owner === undefined) return;
202
+
203
+ const locale = contentLocale;
204
+ const fields = collection.fields;
205
+ const request = beginProfileRequest();
206
+ loading = false;
109
207
  profilePending = true;
110
208
  error = undefined;
209
+
111
210
  try {
112
- const updated = await form.submit(collection.fields, false, (values) =>
113
- runtime.client.update(collection.slug, user.id, values, { locale: contentLocale })
211
+ const updated = await form.submit(fields, false, (values) =>
212
+ runtime.client.update(slug, id, values, { locale, signal: request.signal })
114
213
  );
214
+ if (!ownsProfileRequest(request, owner)) return;
215
+
216
+ const activeSession = runtime.session;
217
+ if (activeSession === undefined) return;
218
+
115
219
  form.reset(updated);
116
- form.setLocalization(contentLocale, updated._localization?.sources);
117
- if (runtime.session !== undefined) runtime.session = { ...runtime.session, user: updated };
220
+ form.setLocalization(locale, updated._localization?.sources);
221
+ runtime.session = { ...activeSession, user: updated };
118
222
  runtime.documentsChanged();
119
223
  notifications.success({ title: runtime.i18n.t("account:profileUpdated") });
120
224
  } catch (cause) {
121
- if (!(cause instanceof FormValidationError)) {
225
+ if (ownsProfileRequest(request, owner) && !(cause instanceof FormValidationError)) {
122
226
  error =
123
227
  cause instanceof Error ? cause.message : runtime.i18n.t("account:profileSaveFailed");
124
228
  }
125
229
  } finally {
126
- profilePending = false;
230
+ if (ownsProfileRequest(request, owner)) profilePending = false;
231
+
232
+ if (profileRequest === request) profileRequest = undefined;
127
233
  }
128
234
  }
129
235
 
130
236
  async function updateTheme(preference: ThemePreference) {
131
237
  pendingThemeWrites += 1;
132
238
  error = undefined;
239
+
133
240
  try {
134
241
  await runtime.setTheme(preference);
135
242
  notifications.success({ title: runtime.i18n.t("account:themeSaved") });
@@ -142,8 +249,10 @@
142
249
 
143
250
  async function updateLanguage(language: string) {
144
251
  if (!runtime.i18n.languages.some((item) => item.code === language)) return;
252
+
145
253
  languagePending = true;
146
254
  error = undefined;
255
+
147
256
  try {
148
257
  await runtime.i18n.setLanguage(language);
149
258
  notifications.success({ title: runtime.i18n.t("account:languageSaved") });
@@ -156,8 +265,10 @@
156
265
 
157
266
  async function updateTimeZone(timeZone: string) {
158
267
  if (!runtime.i18n.timeZones.some((item) => item.id === timeZone)) return;
268
+
159
269
  timeZonePending = true;
160
270
  error = undefined;
271
+
161
272
  try {
162
273
  await runtime.i18n.setTimeZone(timeZone);
163
274
  notifications.success({ title: runtime.i18n.t("account:timeZoneSaved") });
@@ -171,6 +282,7 @@
171
282
  async function resetPreferences() {
172
283
  resetPending = true;
173
284
  error = undefined;
285
+
174
286
  try {
175
287
  await runtime.resetPreferences();
176
288
  notifications.success({ title: runtime.i18n.t("account:preferencesReset") });
@@ -183,18 +295,58 @@
183
295
  }
184
296
 
185
297
  async function refreshUser() {
186
- if (collection === undefined || user === undefined) return undefined;
187
- const document = await runtime.client.find(collection.slug, user.id, { locale: contentLocale });
188
- if (runtime.session !== undefined) runtime.session = { ...runtime.session, user: document };
189
- form.reset(document);
190
- form.setLocalization(contentLocale, document._localization?.sources);
191
- return document;
298
+ if (collection === undefined || user === undefined || form.submitting) return undefined;
299
+
300
+ const owner = runtime.session?.id;
301
+ const id = user.id;
302
+ if (owner === undefined) return undefined;
303
+
304
+ const locale = contentLocale;
305
+ const request = beginProfileRequest();
306
+
307
+ try {
308
+ const document = await runtime.client.find(collection.slug, id, {
309
+ locale,
310
+ signal: request.signal,
311
+ });
312
+ if (!ownsProfileRequest(request, owner)) return undefined;
313
+
314
+ const activeSession = runtime.session;
315
+ if (activeSession === undefined) return undefined;
316
+
317
+ runtime.session = { ...activeSession, user: document };
318
+ form.reset(document);
319
+ form.setLocalization(locale, document._localization?.sources);
320
+ loading = false;
321
+
322
+ return document;
323
+ } catch (cause) {
324
+ if (!ownsProfileRequest(request, owner)) return undefined;
325
+
326
+ throw cause;
327
+ } finally {
328
+ if (profileRequest === request) profileRequest = undefined;
329
+ }
192
330
  }
193
331
 
194
332
  async function logout() {
195
- await runtime.client.logout();
196
- runtime.session = undefined;
197
- navigate(adminRoutePatterns.login, { replace: true });
333
+ const owner = runtime.session?.id;
334
+ if (owner === undefined) return;
335
+
336
+ const request = beginProfileRequest();
337
+
338
+ try {
339
+ await runtime.client.auth.logout({ signal: request.signal });
340
+ if (!ownsProfileRequest(request, owner)) return;
341
+
342
+ await runtime.hardNavigate(adminRoutePatterns.login);
343
+ } catch (cause) {
344
+ if (!ownsProfileRequest(request, owner)) return;
345
+
346
+ throw cause;
347
+ } finally {
348
+ if (profileRequest === request) profileRequest = undefined;
349
+ }
198
350
  }
199
351
  </script>
200
352
 
@@ -227,7 +379,9 @@
227
379
 
228
380
  <AccountNavigation active="profile" />
229
381
 
230
- {#if error !== undefined}<Banner tone="destructive">{error}</Banner>{/if}
382
+ {#if error !== undefined}
383
+ <Banner tone="destructive">{error}</Banner>
384
+ {/if}
231
385
 
232
386
  <section class="grid gap-4" aria-labelledby="profile-heading">
233
387
  <header class="flex items-center gap-3">
@@ -242,7 +396,10 @@
242
396
  </div>
243
397
  </header>
244
398
  {#if loading}
245
- <div class="grid gap-4 rounded-[4px] border border-control-border bg-control p-5">
399
+ <div
400
+ class="grid gap-4 rounded-[4px] border border-control-border bg-control p-5"
401
+ data-ridu-loading-surface="account"
402
+ >
246
403
  <Skeleton class="h-10 w-full" /><Skeleton class="h-24 w-full" />
247
404
  </div>
248
405
  {:else if collection !== undefined}
@@ -0,0 +1,159 @@
1
+ import type { APIKey, APIKeyInfo, AuthSessionInfo, AdminSecurityDataV1 } from "@riducms/protocol";
2
+ import { RiduError } from "@riducms/sdk";
3
+ import type { AdminI18n } from "@riducms/translations";
4
+
5
+ import type { AdminClient } from "@admin/core/api/admin-client";
6
+
7
+ export type AccountSecurityStatus = "idle" | "loading" | "ready" | "error";
8
+
9
+ export class AccountSecurityController {
10
+ status = $state<AccountSecurityStatus>("idle");
11
+ error = $state<string>();
12
+ operation = $state<string>();
13
+ sessions = $state.raw<readonly AuthSessionInfo[]>([]);
14
+ apiKeys = $state.raw<readonly APIKeyInfo[]>([]);
15
+ createdAPIKey = $state.raw<APIKey>();
16
+ #request?: AbortController;
17
+ #operationRequest?: AbortController;
18
+ #generation = 0;
19
+
20
+ constructor(
21
+ private readonly client: AdminClient,
22
+ readonly apiKeysEnabled: boolean,
23
+ private readonly translate: AdminI18n["t"]
24
+ ) {}
25
+
26
+ load = async (initial?: AdminSecurityDataV1) => {
27
+ this.#request?.abort();
28
+ this.#operationRequest?.abort();
29
+ this.#operationRequest = undefined;
30
+ this.operation = undefined;
31
+ this.sessions = [];
32
+ this.apiKeys = [];
33
+ this.createdAPIKey = undefined;
34
+ if (initial !== undefined) {
35
+ this.#generation += 1;
36
+ this.#request = undefined;
37
+ this.error = initial.sessions.error?.message ?? initial.apiKeys.error?.message;
38
+ this.sessions = initial.sessions.value ?? [];
39
+ this.apiKeys = initial.apiKeys.value ?? [];
40
+ this.status = this.error === undefined ? "ready" : "error";
41
+ return;
42
+ }
43
+ const request = new AbortController();
44
+ this.#request = request;
45
+ const generation = ++this.#generation;
46
+ this.status = "loading";
47
+ this.error = undefined;
48
+ try {
49
+ const [sessions, apiKeys] = await Promise.all([
50
+ this.client.auth.sessions({ signal: request.signal }),
51
+ this.apiKeysEnabled
52
+ ? this.client.auth.apiKeys({ signal: request.signal })
53
+ : Promise.resolve([]),
54
+ ]);
55
+ if (request.signal.aborted || generation !== this.#generation) return;
56
+ this.sessions = sessions;
57
+ this.apiKeys = apiKeys;
58
+ this.status = "ready";
59
+ } catch (cause) {
60
+ if (request.signal.aborted || generation !== this.#generation) return;
61
+ this.error = messageFrom(cause, this.translate("account:securityLoadFailed"));
62
+ this.status = "error";
63
+ }
64
+ };
65
+
66
+ revokeSession = async (id: string) => {
67
+ return this.#run(
68
+ "session:" + id,
69
+ (signal) => this.client.auth.revokeSession(id, { signal }),
70
+ () => (this.sessions = this.sessions.filter((session) => session.id !== id))
71
+ );
72
+ };
73
+
74
+ logoutAll = async () => {
75
+ return this.#run(
76
+ "logout-all",
77
+ (signal) => this.client.auth.logoutAll({ signal }),
78
+ () => (this.sessions = [])
79
+ );
80
+ };
81
+
82
+ createAPIKey = async (name: string, expiresAt?: string) => {
83
+ return this.#run(
84
+ "api-key:create",
85
+ (signal) => this.client.auth.createAPIKey({ name, expiresAt }, { signal }),
86
+ (created) => {
87
+ this.createdAPIKey = created;
88
+ this.apiKeys = [
89
+ {
90
+ id: created.id,
91
+ name: created.name,
92
+ createdAt: created.createdAt,
93
+ expiresAt: created.expiresAt,
94
+ },
95
+ ...this.apiKeys,
96
+ ];
97
+ }
98
+ );
99
+ };
100
+
101
+ dismissCreatedAPIKey = () => {
102
+ this.createdAPIKey = undefined;
103
+ };
104
+
105
+ revokeAPIKey = async (id: string) => {
106
+ return this.#run(
107
+ "api-key:" + id,
108
+ (signal) => this.client.auth.revokeAPIKey(id, { signal }),
109
+ () => {
110
+ this.apiKeys = this.apiKeys.filter((key) => key.id !== id);
111
+ if (this.createdAPIKey?.id === id) this.createdAPIKey = undefined;
112
+ }
113
+ );
114
+ };
115
+
116
+ changePassword = async (currentPassword: string, password: string) => {
117
+ return this.#run("password", (signal) =>
118
+ this.client.auth.changePassword({ currentPassword, password }, { signal })
119
+ );
120
+ };
121
+
122
+ destroy() {
123
+ this.#generation += 1;
124
+ this.#request?.abort();
125
+ this.#operationRequest?.abort();
126
+ }
127
+
128
+ async #run<Result>(
129
+ name: string,
130
+ operation: (signal: AbortSignal) => Promise<Result>,
131
+ apply?: (result: Result) => void
132
+ ) {
133
+ if (this.operation !== undefined) return false;
134
+ const request = new AbortController();
135
+ const generation = this.#generation;
136
+ this.#operationRequest = request;
137
+ this.operation = name;
138
+ this.error = undefined;
139
+ try {
140
+ const result = await operation(request.signal);
141
+ if (request.signal.aborted || generation !== this.#generation) return false;
142
+ apply?.(result);
143
+ return true;
144
+ } catch (cause) {
145
+ if (request.signal.aborted || generation !== this.#generation) return false;
146
+ this.error = messageFrom(cause, this.translate("account:securityOperationFailed"));
147
+ return false;
148
+ } finally {
149
+ if (this.#operationRequest === request) {
150
+ this.#operationRequest = undefined;
151
+ this.operation = undefined;
152
+ }
153
+ }
154
+ }
155
+ }
156
+
157
+ function messageFrom(cause: unknown, fallback: string) {
158
+ return cause instanceof RiduError || cause instanceof Error ? cause.message : fallback;
159
+ }