@riducms/admin 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (352) hide show
  1. package/THIRD_PARTY_NOTICES.md +60 -37
  2. package/package.json +27 -15
  3. package/src/app/admin-core-view-route.svelte +38 -53
  4. package/src/app/admin-extension-route.svelte +19 -0
  5. package/src/app/admin-layout.scss +175 -0
  6. package/src/app/admin-layout.svelte +144 -410
  7. package/src/app/admin-navigation-progress.svelte +11 -77
  8. package/src/app/admin-page-title.svelte +17 -85
  9. package/src/app/admin-page-title.ts +136 -0
  10. package/src/app/admin-route-error.svelte +24 -0
  11. package/src/app/admin-router-provider.svelte +34 -0
  12. package/src/app/admin-router-root.svelte +41 -0
  13. package/src/app/admin-routes.svelte +56 -19
  14. package/src/app/loading-boundary.svelte +3 -1
  15. package/src/app-meta.ts +13 -0
  16. package/src/app.css +138 -496
  17. package/src/app.svelte +60 -26
  18. package/src/components/brand/ridu-logo.svelte +19 -5
  19. package/src/components/data/list-table.scss +63 -0
  20. package/src/components/icons/LICENSE.md +24 -0
  21. package/src/components/icons/chevron.svelte +8 -0
  22. package/src/components/icons/plus.svelte +12 -0
  23. package/src/components/icons/search.svelte +12 -0
  24. package/src/components/icons/x.svelte +8 -0
  25. package/src/components/json-tree/json-tree-node.svelte +61 -67
  26. package/src/components/json-tree/json-viewer.scss +153 -0
  27. package/src/components/json-tree/json-viewer.svelte +57 -96
  28. package/src/components/ui/banner/banner.scss +28 -0
  29. package/src/components/ui/banner/banner.svelte +9 -8
  30. package/src/components/ui/banner/index.ts +1 -5
  31. package/src/components/ui/code-editor/code-editor.scss +20 -0
  32. package/src/components/ui/code-editor/code-editor.svelte +71 -0
  33. package/src/components/ui/code-editor/code-editor.ts +151 -0
  34. package/src/components/ui/code-editor/code-languages.ts +29 -0
  35. package/src/components/ui/collapsible-panel/collapsible-panel.scss +38 -0
  36. package/src/components/ui/collapsible-panel/collapsible-panel.svelte +13 -0
  37. package/src/components/ui/collapsible-panel/index.ts +1 -0
  38. package/src/components/ui/combobox/combobox.scss +163 -0
  39. package/src/components/ui/combobox/index.ts +22 -0
  40. package/src/components/ui/date-value-control/date-value-calendar.svelte +128 -16
  41. package/src/components/ui/date-value-control/date-value-control-loader.scss +26 -0
  42. package/src/components/ui/date-value-control/date-value-control-loader.svelte +35 -13
  43. package/src/components/ui/date-value-control/date-value-control.scss +223 -0
  44. package/src/components/ui/date-value-control/date-value-control.svelte +62 -44
  45. package/src/components/ui/drawer/drawer-depth.ts +8 -0
  46. package/src/components/ui/dropdown-menu/dropdown-menu-content.svelte +2 -5
  47. package/src/components/ui/dropdown-menu/dropdown-menu-item.svelte +2 -5
  48. package/src/components/ui/dropdown-menu/dropdown-menu-separator.svelte +2 -2
  49. package/src/components/ui/dropdown-menu/dropdown-menu.scss +62 -0
  50. package/src/components/ui/kbd/kbd.scss +17 -0
  51. package/src/components/ui/kbd/kbd.svelte +3 -10
  52. package/src/components/ui/navigation-drawer/navigation-drawer.scss +40 -0
  53. package/src/components/ui/navigation-drawer/navigation-drawer.svelte +38 -0
  54. package/src/components/ui/popover/popover-content.svelte +3 -5
  55. package/src/components/ui/popover/popover.scss +18 -0
  56. package/src/components/ui/radio-group/index.ts +0 -1
  57. package/src/components/ui/radio-group/radio-card-item.svelte +7 -12
  58. package/src/components/ui/radio-group/radio-group.svelte +2 -2
  59. package/src/components/ui/radio-group/radio.scss +45 -0
  60. package/src/components/ui/select/select-content.svelte +5 -6
  61. package/src/components/ui/select/select-item.svelte +8 -8
  62. package/src/components/ui/select/select-scroll-down-button.svelte +3 -5
  63. package/src/components/ui/select/select-scroll-up-button.svelte +3 -5
  64. package/src/components/ui/select/select-trigger.svelte +6 -7
  65. package/src/components/ui/select/select.scss +126 -0
  66. package/src/components/ui/sheet/sheet-content.svelte +6 -34
  67. package/src/components/ui/sheet/sheet-description.svelte +2 -2
  68. package/src/components/ui/sheet/sheet-overlay.svelte +2 -21
  69. package/src/components/ui/sheet/sheet-title.svelte +2 -2
  70. package/src/components/ui/sheet/sheet.scss +221 -0
  71. package/src/components/ui/skeleton/skeleton.scss +20 -0
  72. package/src/components/ui/skeleton/skeleton.svelte +4 -16
  73. package/src/components/ui/sonner/sonner.scss +121 -0
  74. package/src/components/ui/sonner/sonner.svelte +11 -10
  75. package/src/components/ui/status-indicator/status-indicator.scss +48 -0
  76. package/src/components/ui/status-indicator/status-indicator.svelte +14 -14
  77. package/src/components/ui/tabs/tabs-content.svelte +2 -2
  78. package/src/components/ui/tabs/tabs-list.svelte +7 -7
  79. package/src/components/ui/tabs/tabs-trigger.svelte +2 -8
  80. package/src/components/ui/tabs/tabs.scss +86 -0
  81. package/src/components/ui/tabs/tabs.svelte +2 -2
  82. package/src/components/ui/timezone-picker/timezone-picker.svelte +152 -0
  83. package/src/core/api/admin-client.ts +2 -2
  84. package/src/core/bootstrap/admin-bootstrap.ts +396 -0
  85. package/src/core/bootstrap/admin-prepared-state.ts +213 -0
  86. package/src/core/bootstrap/admin-route-modules.ts +66 -0
  87. package/src/core/clipboard/copy-feedback.svelte.ts +43 -0
  88. package/src/core/forms/embedded-fields.ts +4 -8
  89. package/src/core/forms/field-editor-authoring.ts +8 -0
  90. package/src/core/forms/field-issue-focus.ts +4 -4
  91. package/src/core/forms/field-occurrence.ts +4 -7
  92. package/src/core/forms/form-controller.svelte.ts +14 -21
  93. package/src/core/forms/form-draft-recovery.ts +17 -4
  94. package/src/core/forms/form-issue-correlation.ts +13 -13
  95. package/src/core/forms/form-path.ts +17 -0
  96. package/src/core/forms/form-schema.ts +12 -43
  97. package/src/core/forms/form-validation.ts +7 -46
  98. package/src/core/forms/plugin-field-authoring.ts +6 -2
  99. package/src/core/forms/plugin-field-write.ts +2 -6
  100. package/src/core/i18n/admin-i18n.svelte.ts +18 -0
  101. package/src/core/i18n/format-admin-date-time.ts +19 -0
  102. package/src/core/i18n/humanize-identifier.ts +5 -0
  103. package/src/core/i18n/time-zone-label.ts +20 -0
  104. package/src/core/loaders/admin-view-host.svelte +72 -0
  105. package/src/core/loaders/admin-view-loader.svelte.ts +90 -0
  106. package/src/core/notifications/validation-toast.svelte +2 -2
  107. package/src/core/plugins/field-registry.ts +7 -8
  108. package/src/core/routing/admin-paths.ts +53 -2
  109. package/src/core/routing/admin-scroll.svelte.ts +74 -0
  110. package/src/core/routing/admin-view-selection.ts +78 -0
  111. package/src/core/runtime/admin-runtime.svelte.ts +121 -124
  112. package/src/core/schema/field-default.ts +33 -0
  113. package/src/core/schema/field-localization.ts +15 -0
  114. package/src/core/schema/read-document-path.ts +11 -0
  115. package/src/features/account/account-menu.scss +37 -0
  116. package/src/features/account/account-menu.svelte +115 -0
  117. package/src/features/{auth → account}/account-navigation.svelte +16 -19
  118. package/src/features/{auth → account}/account-route.svelte +206 -49
  119. package/src/features/account/account-security-controller.svelte.ts +157 -0
  120. package/src/features/{auth → account}/account-security-route.svelte +140 -42
  121. package/src/features/api-reference/api-reference-code.svelte +64 -0
  122. package/src/features/api-reference/api-reference-content.svelte +258 -0
  123. package/src/features/api-reference/api-reference-drawer.scss +74 -0
  124. package/src/features/api-reference/api-reference-examples.ts +381 -0
  125. package/src/features/api-reference/api-reference-schema.ts +332 -0
  126. package/src/features/api-reference/api-reference.scss +190 -0
  127. package/src/features/api-reference/api-reference.svelte +74 -0
  128. package/src/features/auth/auth-brand.svelte +5 -3
  129. package/src/features/auth/auth-field.svelte +92 -0
  130. package/src/features/auth/auth-frame.svelte +5 -8
  131. package/src/features/auth/auth.scss +122 -0
  132. package/src/features/auth/create-first-user-controller.svelte.ts +20 -51
  133. package/src/features/auth/create-first-user-route.svelte +83 -122
  134. package/src/features/auth/forgot-password-route.svelte +31 -23
  135. package/src/features/auth/login-route.svelte +68 -116
  136. package/src/features/auth/logout-button.scss +34 -0
  137. package/src/features/auth/logout-button.svelte +52 -0
  138. package/src/features/auth/request-verification-route.svelte +27 -22
  139. package/src/features/auth/reset-password-route.svelte +108 -41
  140. package/src/features/auth/verify-email-route.svelte +49 -15
  141. package/src/features/bulk-edit/bulk-edit-fields.ts +23 -0
  142. package/src/features/bulk-edit/bulk-edit.scss +152 -0
  143. package/src/features/bulk-edit/field-picker.svelte +176 -0
  144. package/src/features/collections/bulk/bulk-actions.svelte +148 -0
  145. package/src/features/collections/bulk/bulk-confirmation.svelte +153 -0
  146. package/src/features/collections/bulk/bulk-editor-loader.svelte +95 -0
  147. package/src/features/collections/bulk/bulk-editor.svelte +186 -0
  148. package/src/features/collections/collection-list-cell-values.ts +144 -0
  149. package/src/features/collections/collection-list-controller.svelte.ts +268 -379
  150. package/src/features/collections/collection-list-preferences-controller.svelte.ts +54 -40
  151. package/src/features/collections/collection-list-query.ts +223 -0
  152. package/src/features/collections/collection-list-results.svelte +205 -329
  153. package/src/features/collections/collection-list-route.svelte +143 -884
  154. package/src/features/collections/collection-list.scss +261 -0
  155. package/src/features/collections/collection-list.svelte.ts +362 -0
  156. package/src/features/collections/controls/column-picker.svelte +110 -0
  157. package/src/features/collections/controls/column-pill.svelte +62 -0
  158. package/src/features/collections/controls/filter-builder.svelte +193 -0
  159. package/src/features/collections/controls/filter-row.svelte +142 -0
  160. package/src/features/collections/controls/list-controls.scss +196 -0
  161. package/src/features/collections/controls/list-options.svelte +203 -0
  162. package/src/features/collections/controls/list-pagination.scss +78 -0
  163. package/src/features/collections/controls/list-pagination.svelte +115 -0
  164. package/src/features/collections/controls/list-toolbar.svelte +132 -0
  165. package/src/features/collections/controls/sort-heading.scss +52 -0
  166. package/src/features/collections/controls/sort-heading.svelte +78 -0
  167. package/src/features/collections/list-columns.ts +43 -0
  168. package/src/features/collections/list-workspace.ts +155 -54
  169. package/src/features/dashboard/dashboard-route.svelte +82 -86
  170. package/src/features/dashboard/dashboard.scss +131 -0
  171. package/src/features/development/development-tools.svelte +20 -36
  172. package/src/features/documents/document-action-bar.svelte +438 -0
  173. package/src/features/documents/document-api-view-loader.svelte +17 -6
  174. package/src/features/documents/document-api-view.scss +120 -0
  175. package/src/features/documents/document-api-view.svelte +169 -454
  176. package/src/features/documents/document-controller.svelte.ts +526 -613
  177. package/src/features/documents/document-credentials.scss +37 -0
  178. package/src/features/documents/document-credentials.svelte +72 -0
  179. package/src/features/documents/{document-route-dirty.ts → document-credentials.ts} +25 -0
  180. package/src/features/documents/document-field-sections.scss +39 -0
  181. package/src/features/documents/document-field-sections.svelte +4 -13
  182. package/src/features/documents/document-loading.scss +24 -0
  183. package/src/features/documents/document-lock-controller.svelte.ts +187 -0
  184. package/src/features/documents/document-operation-dialogs.svelte +110 -0
  185. package/src/features/documents/document-route-loader.svelte +17 -5
  186. package/src/features/documents/document-route.svelte +305 -811
  187. package/src/features/documents/document-schedule.scss +127 -0
  188. package/src/features/documents/document-schedule.svelte +323 -0
  189. package/src/features/documents/document-title.ts +25 -0
  190. package/src/features/documents/document.scss +274 -0
  191. package/src/features/documents/live-preview-panel.scss +117 -0
  192. package/src/features/documents/live-preview-panel.svelte +167 -168
  193. package/src/features/documents/live-preview.ts +2 -10
  194. package/src/features/documents/upload-document-preview-loader.svelte +26 -23
  195. package/src/features/localization/content-locale-switcher.scss +43 -0
  196. package/src/features/localization/content-locale-switcher.svelte +24 -17
  197. package/src/features/navigation/admin-breadcrumbs.scss +56 -0
  198. package/src/features/navigation/admin-breadcrumbs.svelte +102 -37
  199. package/src/features/navigation/admin-command-menu.svelte +38 -15
  200. package/src/features/navigation/admin-navigation.scss +133 -0
  201. package/src/features/navigation/admin-navigation.svelte +169 -0
  202. package/src/features/navigation/breadcrumb-context.svelte.ts +15 -0
  203. package/src/features/navigation/navigation-preference.svelte.ts +70 -0
  204. package/src/features/reference-browser/reference-browser-editor.svelte +180 -0
  205. package/src/features/reference-browser/reference-browser-list.scss +113 -0
  206. package/src/features/reference-browser/reference-browser-list.svelte +370 -0
  207. package/src/features/reference-browser/reference-browser-loader.svelte +26 -8
  208. package/src/features/reference-browser/reference-browser-workflow.svelte.ts +335 -236
  209. package/src/features/reference-browser/reference-browser.scss +217 -0
  210. package/src/features/reference-browser/reference-browser.svelte +154 -797
  211. package/src/features/reference-browser/reference-collection-picker.svelte +116 -0
  212. package/src/features/reference-browser/reference-list-query.ts +35 -0
  213. package/src/features/uploads/bulk-upload-controller.svelte.ts +604 -0
  214. package/src/features/uploads/bulk-upload-edit-all.svelte +92 -0
  215. package/src/features/uploads/bulk-upload-editor.svelte +191 -0
  216. package/src/features/uploads/bulk-upload-file-picker.svelte +70 -0
  217. package/src/features/uploads/bulk-upload-remote.svelte +100 -0
  218. package/src/features/uploads/bulk-upload-route-loader.svelte +54 -0
  219. package/src/features/uploads/bulk-upload-route.svelte +172 -592
  220. package/src/features/uploads/bulk-upload-sidebar.svelte +125 -0
  221. package/src/features/uploads/bulk-upload.scss +526 -0
  222. package/src/features/uploads/bulk-upload.ts +105 -14
  223. package/src/features/uploads/crop-surface.ts +150 -0
  224. package/src/features/uploads/image-editor.svelte +231 -0
  225. package/src/features/uploads/rendition-preview.svelte +64 -0
  226. package/src/features/uploads/upload-control.svelte +340 -0
  227. package/src/features/uploads/upload-document-contracts.ts +30 -0
  228. package/src/features/uploads/upload-draft.svelte.ts +294 -0
  229. package/src/features/uploads/upload-metadata-fields.scss +32 -0
  230. package/src/features/uploads/upload-metadata-fields.svelte +8 -7
  231. package/src/features/uploads/upload-thumbnail.svelte +47 -0
  232. package/src/features/uploads/upload.scss +361 -0
  233. package/src/features/versions/version-comparison.svelte +361 -0
  234. package/src/features/versions/version-diff-fields.svelte +136 -0
  235. package/src/features/versions/version-diff.ts +296 -41
  236. package/src/features/versions/version-history-controller.svelte.ts +311 -0
  237. package/src/features/versions/version-history-route-loader.svelte +32 -0
  238. package/src/features/versions/version-history-route.svelte +157 -522
  239. package/src/features/versions/version-history.scss +444 -0
  240. package/src/features/versions/version-history.ts +45 -0
  241. package/src/features/versions/version-table.svelte +91 -0
  242. package/src/fields/embedded-schema-draft-editor.scss +48 -0
  243. package/src/fields/embedded-schema-draft-editor.svelte +24 -16
  244. package/src/fields/field-collapsible.svelte +14 -8
  245. package/src/fields/field-layout.scss +171 -0
  246. package/src/fields/field-layout.svelte +3 -5
  247. package/src/fields/field-renderer.svelte +17 -29
  248. package/src/fields/field-shell.svelte +1 -1
  249. package/src/fields/field-tab-layout.svelte +25 -19
  250. package/src/fields/join/join-field-controller.svelte.ts +513 -0
  251. package/src/fields/join/join-field-values.ts +111 -0
  252. package/src/fields/join/join-field.scss +189 -0
  253. package/src/fields/join/join-field.svelte +164 -332
  254. package/src/fields/live-validation-feedback.svelte +4 -3
  255. package/src/fields/local-field-editor.svelte +2 -2
  256. package/src/fields/nested/block-name-input.svelte +11 -12
  257. package/src/fields/nested/block-picker.scss +128 -0
  258. package/src/fields/nested/block-picker.svelte +81 -0
  259. package/src/fields/nested/block-placeholder.svg +2 -0
  260. package/src/fields/nested/nested-field.scss +331 -0
  261. package/src/fields/nested/nested-field.svelte +265 -245
  262. package/src/fields/nested/sortable-row.svelte +9 -7
  263. package/src/fields/plugin-field.svelte +4 -6
  264. package/src/fields/point/point-field.svelte +51 -38
  265. package/src/fields/primitive-list/primitive-list-field.svelte +63 -19
  266. package/src/fields/primitive-list/primitive-list.scss +109 -0
  267. package/src/fields/radio/radio-field.svelte +2 -1
  268. package/src/fields/relationship/relationship-field-controller.svelte.ts +81 -41
  269. package/src/fields/relationship/relationship-field.svelte +144 -175
  270. package/src/fields/relationship/relationship-lookup-controller.svelte.ts +58 -30
  271. package/src/fields/relationship/relationship-query.ts +2 -10
  272. package/src/fields/relationship/relationship-quick-picker-controller.svelte.ts +36 -62
  273. package/src/fields/relationship/relationship-quick-picker.svelte +178 -227
  274. package/src/fields/relationship/relationship-selection.svelte +120 -0
  275. package/src/fields/relationship/relationship-value.ts +46 -7
  276. package/src/fields/relationship/relationship.scss +182 -0
  277. package/src/fields/scalar/code-field.svelte +101 -0
  278. package/src/fields/scalar/date-control-value.ts +13 -0
  279. package/src/fields/scalar/scalar-field.svelte +25 -67
  280. package/src/fields/select/select-field.svelte +132 -94
  281. package/src/fields/text/slug-field.scss +28 -0
  282. package/src/fields/text/slug-field.svelte +19 -15
  283. package/src/fields/text/text-field.svelte +1 -2
  284. package/src/fields/ui/ui-field.svelte +7 -4
  285. package/src/fields/virtual/virtual-field.svelte +2 -5
  286. package/src/index.ts +1 -2
  287. package/src/routing.ts +2 -0
  288. package/src/app/deferred-redirect.svelte +0 -23
  289. package/src/components/code-block/code-block.svelte +0 -107
  290. package/src/components/ui/bulk-action-bar/bulk-action-bar.svelte +0 -50
  291. package/src/components/ui/bulk-action-bar/index.ts +0 -1
  292. package/src/components/ui/button/index.ts +0 -8
  293. package/src/components/ui/calendar/calendar-caption.svelte +0 -72
  294. package/src/components/ui/calendar/calendar-cell.svelte +0 -20
  295. package/src/components/ui/calendar/calendar-day.svelte +0 -27
  296. package/src/components/ui/calendar/calendar-grid-row.svelte +0 -13
  297. package/src/components/ui/calendar/calendar-grid.svelte +0 -17
  298. package/src/components/ui/calendar/calendar-head-cell.svelte +0 -20
  299. package/src/components/ui/calendar/calendar-header.svelte +0 -20
  300. package/src/components/ui/calendar/calendar-heading.svelte +0 -17
  301. package/src/components/ui/calendar/calendar-month-select.svelte +0 -45
  302. package/src/components/ui/calendar/calendar-month.svelte +0 -16
  303. package/src/components/ui/calendar/calendar-months.svelte +0 -20
  304. package/src/components/ui/calendar/calendar-nav.svelte +0 -20
  305. package/src/components/ui/calendar/calendar-next-button.svelte +0 -30
  306. package/src/components/ui/calendar/calendar-prev-button.svelte +0 -30
  307. package/src/components/ui/calendar/calendar-year-select.svelte +0 -44
  308. package/src/components/ui/calendar/calendar.svelte +0 -118
  309. package/src/components/ui/calendar/index.ts +0 -1
  310. package/src/components/ui/checkbox/checkbox.svelte +0 -35
  311. package/src/components/ui/checkbox/index.ts +0 -1
  312. package/src/components/ui/dialog/dialog-close.svelte +0 -11
  313. package/src/components/ui/dialog/dialog-content.svelte +0 -52
  314. package/src/components/ui/dialog/dialog-description.svelte +0 -20
  315. package/src/components/ui/dialog/dialog-footer.svelte +0 -20
  316. package/src/components/ui/dialog/dialog-header.svelte +0 -20
  317. package/src/components/ui/dialog/dialog-overlay.svelte +0 -20
  318. package/src/components/ui/dialog/dialog-title.svelte +0 -17
  319. package/src/components/ui/dialog/dialog-trigger.svelte +0 -11
  320. package/src/components/ui/dialog/dialog.svelte +0 -7
  321. package/src/components/ui/dialog/index.ts +0 -8
  322. package/src/components/ui/empty-state/empty-state.svelte +0 -34
  323. package/src/components/ui/empty-state/index.ts +0 -1
  324. package/src/components/ui/input/index.ts +0 -1
  325. package/src/components/ui/pagination/index.ts +0 -1
  326. package/src/components/ui/pagination/pagination.svelte +0 -88
  327. package/src/components/ui/radio-group/radio-group-item.svelte +0 -17
  328. package/src/components/ui/slider/index.ts +0 -3
  329. package/src/components/ui/slider/slider-content.svelte +0 -59
  330. package/src/components/ui/slider/slider.svelte +0 -35
  331. package/src/components/ui/table/index.ts +0 -6
  332. package/src/components/ui/table/table-body.svelte +0 -20
  333. package/src/components/ui/table/table-cell.svelte +0 -23
  334. package/src/components/ui/table/table-head.svelte +0 -23
  335. package/src/components/ui/table/table-header.svelte +0 -20
  336. package/src/components/ui/table/table-row.svelte +0 -23
  337. package/src/components/ui/table/table.svelte +0 -22
  338. package/src/components/ui/textarea/index.ts +0 -1
  339. package/src/components/ui/tooltip/index.ts +0 -4
  340. package/src/components/ui/tooltip/tooltip-content.svelte +0 -50
  341. package/src/components/ui/tooltip/tooltip-provider.svelte +0 -7
  342. package/src/components/ui/tooltip/tooltip-trigger.svelte +0 -7
  343. package/src/components/ui/tooltip/tooltip.svelte +0 -7
  344. package/src/features/auth/account-menu.svelte +0 -159
  345. package/src/features/auth/account-security-controller.svelte.ts +0 -119
  346. package/src/features/collections/collection-list-bulk-actions.svelte +0 -143
  347. package/src/features/collections/collection-list-bulk-editor.svelte +0 -157
  348. package/src/features/collections/collection-list-filter-popover.svelte +0 -238
  349. package/src/features/collections/collection-list-workspace-toolbar.svelte +0 -409
  350. package/src/features/collections/collection-table-skeleton.svelte +0 -44
  351. package/src/features/documents/document-heading.ts +0 -20
  352. package/src/features/documents/upload-document-preview.svelte +0 -525
@@ -1,6 +1,6 @@
1
1
  import type { SchemaField } from "@riducms/protocol";
2
2
  import type { FormController } from "@admin/core/forms/form-controller.svelte";
3
- import { indexFieldValues } from "@admin/core/forms/form-issue-correlation";
3
+ import { canRebaseIndexedSuffix, indexFieldValues } from "@admin/core/forms/form-issue-correlation";
4
4
  import { fieldAccessPath } from "@admin/fields/nested/scoped-field";
5
5
  import { evaluateFieldCondition } from "@admin/core/forms/field-condition";
6
6
 
@@ -43,11 +43,7 @@ export function writePluginField(form: FormController, field: SchemaField, value
43
43
  const target = current.get(source.token);
44
44
  if (!target) continue;
45
45
  const suffix = path.slice(source.path.length);
46
- if (
47
- /\.\d+(?:\.|$)/.test(suffix) &&
48
- JSON.stringify(source.value) !== JSON.stringify(target.value)
49
- )
50
- continue;
46
+ if (!canRebaseIndexedSuffix(suffix, source.value, target.value)) continue;
51
47
  fields[target.path + suffix] = access;
52
48
  }
53
49
  form.access = { ...form.access, fields };
@@ -214,6 +214,24 @@ export class AdminI18nController implements AdminI18n {
214
214
  this.#applyDocumentLanguage();
215
215
  }
216
216
 
217
+ adoptPreparedPreferences(language: string | undefined, timeZone: string | undefined) {
218
+ this.#preferenceRequest += 1;
219
+ this.#languageWrite += 1;
220
+ this.#timeZoneWrite += 1;
221
+ if (language !== undefined && this.languages.some((item) => item.code === language)) {
222
+ this.#language = language;
223
+ this.#confirmedLanguage = language;
224
+ writeBrowserPreference(browserLanguageKey, language);
225
+ }
226
+ if (timeZone !== undefined && this.timeZones.some((item) => item.id === timeZone)) {
227
+ this.#timeZone = timeZone;
228
+ this.#confirmedTimeZone = timeZone;
229
+ writeBrowserPreference(browserTimeZoneKey, timeZone);
230
+ }
231
+ this.#invalidateTranslator();
232
+ this.#applyDocumentLanguage();
233
+ }
234
+
217
235
  async setLanguage(language: string) {
218
236
  if (!this.languages.some((item) => item.code === language)) {
219
237
  throw new Error(`Admin language ${language} is not configured.`);
@@ -0,0 +1,19 @@
1
+ import type { AdminI18n } from "@riducms/translations";
2
+
3
+ export function formatAdminDateTime(value: string, i18n: AdminI18n) {
4
+ const date = new Date(value);
5
+ if (!Number.isFinite(date.valueOf())) return value;
6
+ if (i18n.language.startsWith("en")) {
7
+ const day = Number(i18n.formatDate(date, { day: "numeric" }));
8
+ const suffix =
9
+ day % 100 >= 11 && day % 100 <= 13 ? "th" : ({ 1: "st", 2: "nd", 3: "rd" }[day % 10] ?? "th");
10
+ return `${i18n.formatDate(date, { month: "long" })} ${day}${suffix} ${i18n.formatDate(date, { year: "numeric" })}, ${i18n.formatDate(date, { hour: "numeric", minute: "2-digit" })}`;
11
+ }
12
+ return i18n.formatDate(date, {
13
+ year: "numeric",
14
+ month: "long",
15
+ day: "numeric",
16
+ hour: "numeric",
17
+ minute: "2-digit",
18
+ });
19
+ }
@@ -0,0 +1,5 @@
1
+ export function humanizeIdentifier(value: string, language: string) {
2
+ return value
3
+ .replaceAll(/[_-]+/g, " ")
4
+ .replace(/\b\w/g, (letter) => letter.toLocaleUpperCase(language));
5
+ }
@@ -0,0 +1,20 @@
1
+ /** Describe a configured timezone using its offset and localized name at the displayed instant. */
2
+ export function timeZoneLabel(id: string, label: string, language: string, at: Date) {
3
+ const fixedOffset = /^[+-]\d{2}:\d{2}$/.test(id);
4
+ const offset = new Intl.DateTimeFormat("en", {
5
+ timeZone: id,
6
+ timeZoneName: "longOffset",
7
+ })
8
+ .formatToParts(at)
9
+ .find((part) => part.type === "timeZoneName")!.value;
10
+ const name = new Intl.DateTimeFormat(language, {
11
+ timeZone: id,
12
+ timeZoneName: "long",
13
+ })
14
+ .formatToParts(at)
15
+ .find((part) => part.type === "timeZoneName")!.value;
16
+ const utcOffset = offset === "GMT" ? "UTC+00:00" : offset.replace("GMT", "UTC");
17
+ const description =
18
+ fixedOffset || name === label || name === offset ? label : `${label} (${name})`;
19
+ return `(${utcOffset}) ${description}`;
20
+ }
@@ -0,0 +1,72 @@
1
+ <script lang="ts" generics="Props extends object">
2
+ import type { AdminViewComponent } from "@riducms/plugin";
3
+ import { useLocation } from "@hvniel/svelte-router";
4
+ import { getAdminRuntime } from "@admin/core/runtime/admin-runtime.svelte";
5
+ import { getAdminBootstrapCoordinator } from "@admin/core/bootstrap/admin-bootstrap";
6
+ import { normalizedURLIdentity } from "@admin/core/bootstrap/admin-prepared-state";
7
+ import { Banner } from "@admin/components/ui/banner";
8
+ import { Button } from "@riducms/ui";
9
+ import { AdminViewLoader } from "@admin/core/loaders/admin-view-loader.svelte";
10
+ import { registerAdminScrollPage } from "@admin/core/routing/admin-scroll.svelte";
11
+
12
+ let {
13
+ view,
14
+ props,
15
+ ownsRoute = false,
16
+ }: { view: AdminViewComponent<Props>; props: Props; ownsRoute?: boolean } = $props();
17
+ const runtime = getAdminRuntime();
18
+ const bootstrap = getAdminBootstrapCoordinator();
19
+ const location = useLocation();
20
+ // Registrations are static for the lifetime of this keyed host.
21
+ // svelte-ignore state_referenced_locally
22
+ const loader = view.loader;
23
+ const controller =
24
+ loader === undefined
25
+ ? undefined
26
+ : new AdminViewLoader({
27
+ client: runtime.client,
28
+ loader,
29
+ get route() {
30
+ return normalizedURLIdentity(location.current);
31
+ },
32
+ get prepared() {
33
+ return bootstrap.loaderData(
34
+ loader.key,
35
+ location.current.pathname,
36
+ location.current.search
37
+ );
38
+ },
39
+ });
40
+ // Route hosts wait for their fallback read or terminal error before restoring scroll. Dashboard
41
+ // panels share a page and deliberately leave this false so they cannot compete for ownership.
42
+ // svelte-ignore state_referenced_locally
43
+ const routeOwner = ownsRoute;
44
+ if (routeOwner) {
45
+ registerAdminScrollPage({
46
+ ready: () => loader === undefined || controller?.settled === true,
47
+ });
48
+ }
49
+ </script>
50
+
51
+ {#if view.loader === undefined}
52
+ <view.component {...props} />
53
+ {:else if controller !== undefined}
54
+ {#if controller.error !== undefined}
55
+ <Banner tone="destructive">
56
+ <p>{controller.error}</p>
57
+ <Button variant="outline" onclick={controller.refresh}>
58
+ {runtime.i18n.t("general:retry")}
59
+ </Button>
60
+ </Banner>
61
+ {/if}
62
+ {#if controller.ready}
63
+ <view.component
64
+ {...props}
65
+ data={controller.data}
66
+ refresh={controller.refresh}
67
+ refreshing={controller.refreshing}
68
+ />
69
+ {:else if controller.error === undefined}
70
+ <div aria-busy="true" data-ridu-loading-surface="admin-view-loader"></div>
71
+ {/if}
72
+ {/if}
@@ -0,0 +1,90 @@
1
+ import { RiduError, type AdminLoader } from "@riducms/sdk";
2
+ import type { AdminReadResultV1 } from "@riducms/protocol";
3
+ import type { AdminClient } from "@admin/core/api/admin-client";
4
+
5
+ interface AdminViewLoaderOptions {
6
+ client: Pick<AdminClient, "adminLoad">;
7
+ loader: AdminLoader<never, unknown>;
8
+ get route(): string;
9
+ get prepared(): AdminReadResultV1<unknown> | undefined;
10
+ }
11
+
12
+ /** One mounted view owns its data and supersedable refresh. */
13
+ export class AdminViewLoader {
14
+ #data = $state.raw<unknown>();
15
+ #ready = $state(false);
16
+ #refreshing = $state(false);
17
+ #error = $state<string>();
18
+ #request?: AbortController;
19
+ #route?: string;
20
+
21
+ constructor(private readonly options: AdminViewLoaderOptions) {
22
+ // Adopt router data before a retained panel can render the destination query.
23
+ $effect.pre(() => {
24
+ const route = options.route;
25
+ if (route === this.#route) return;
26
+ this.#route = route;
27
+ this.#request?.abort();
28
+ this.#request = undefined;
29
+ this.#ready = false;
30
+ this.#refreshing = false;
31
+ this.#error = undefined;
32
+ const prepared = options.prepared;
33
+ if (prepared !== undefined) {
34
+ if (prepared.error !== undefined) this.#error = prepared.error.message;
35
+ else {
36
+ this.#data = options.loader.decode(prepared.value);
37
+ this.#ready = true;
38
+ }
39
+ } else this.refresh();
40
+ });
41
+ $effect(() => () => this.#request?.abort());
42
+ }
43
+
44
+ get data() {
45
+ return this.#data;
46
+ }
47
+
48
+ get ready() {
49
+ return this.#ready;
50
+ }
51
+
52
+ get refreshing() {
53
+ return this.#refreshing;
54
+ }
55
+
56
+ get error() {
57
+ return this.#error;
58
+ }
59
+
60
+ get settled() {
61
+ return this.#ready || this.#error !== undefined;
62
+ }
63
+
64
+ refresh = async () => {
65
+ const route = this.options.route;
66
+ this.#request?.abort();
67
+ const request = new AbortController();
68
+ this.#request = request;
69
+ this.#refreshing = true;
70
+ this.#error = undefined;
71
+ try {
72
+ const data = await this.options.client.adminLoad(this.options.loader.key, route, {
73
+ signal: request.signal,
74
+ });
75
+ // Aborting cannot undo a response that already completed; only the current owner may
76
+ // decode and publish it.
77
+ if (request.signal.aborted) return;
78
+ this.#data = this.options.loader.decode(data);
79
+ this.#ready = true;
80
+ } catch (cause) {
81
+ if (request.signal.aborted) return;
82
+ this.#error = cause instanceof RiduError ? cause.message : "Unable to load view data.";
83
+ } finally {
84
+ if (this.#request === request) {
85
+ this.#request = undefined;
86
+ this.#refreshing = false;
87
+ }
88
+ }
89
+ };
90
+ }
@@ -9,9 +9,9 @@
9
9
  {:else}
10
10
  <div>
11
11
  <p>The following fields are invalid ({labels.length}):</p>
12
- <ul class="mt-1 list-[circle] space-y-0.5 ps-5">
12
+ <ul class="ridu-validation-toast__fields">
13
13
  {#each labels as label (label)}
14
- <li class="ps-0.5">{label}</li>
14
+ <li>{label}</li>
15
15
  {/each}
16
16
  </ul>
17
17
  </div>
@@ -1,22 +1,22 @@
1
1
  import { type ResolvedPluginField } from "@riducms/plugin";
2
2
  import type { FieldType, SchemaField } from "@riducms/protocol";
3
3
 
4
- interface FieldRenderer {
4
+ interface FieldEditorRegistration {
5
5
  type: FieldType;
6
6
  extension?: ResolvedPluginField;
7
7
  }
8
8
  export class FieldRegistry {
9
- #plugins = new Map<string, FieldRenderer>();
9
+ #plugins = new Map<string, FieldEditorRegistration>();
10
10
  register(plugin: ResolvedPluginField) {
11
11
  const identity =
12
12
  plugin.componentKey === undefined
13
13
  ? `plugin:${plugin.key}`
14
14
  : `${plugin.registration.type}:${plugin.owner}:${plugin.key}`;
15
15
  if (this.#plugins.has(identity))
16
- throw new Error(`Field renderer ${identity} is already registered.`);
16
+ throw new Error(`Field editor ${identity} is already registered.`);
17
17
  this.#plugins.set(identity, { type: plugin.registration.type, extension: plugin });
18
18
  }
19
- resolve(field: SchemaField): FieldRenderer {
19
+ resolve(field: SchemaField): FieldEditorRegistration {
20
20
  if (field.admin.editor !== undefined)
21
21
  throw new Error(
22
22
  `No local editor ${field.admin.editor.reference} is registered for ${field.path}.`
@@ -27,10 +27,9 @@ export class FieldRegistry {
27
27
  component === undefined
28
28
  ? `plugin:${field.plugin?.key}`
29
29
  : `${field.type}:${component.plugin}:${component.component}`;
30
- const renderer = this.#plugins.get(identity);
31
- if (!renderer)
32
- throw new Error(`No admin field renderer can render ${field.path} (${identity}).`);
33
- return renderer;
30
+ const editor = this.#plugins.get(identity);
31
+ if (!editor) throw new Error(`No admin field editor can render ${field.path} (${identity}).`);
32
+ return editor;
34
33
  }
35
34
  }
36
35
  export function createCoreFieldRegistry(
@@ -23,6 +23,50 @@ export const adminRoutePatterns = {
23
23
  globalVersion: "/globals/:global/versions/:revision",
24
24
  } as const;
25
25
 
26
+ export function adminPathSegments(pathname: string): string[] {
27
+ return pathname
28
+ .split("/")
29
+ .filter(Boolean)
30
+ .map((segment) => {
31
+ try {
32
+ return decodeURIComponent(segment);
33
+ } catch {
34
+ return segment;
35
+ }
36
+ });
37
+ }
38
+
39
+ export function parseAdminVersionRevision(value: string | undefined): number | undefined {
40
+ const revision = Number(value);
41
+ return Number.isSafeInteger(revision) && revision > 0 ? revision : undefined;
42
+ }
43
+
44
+ export function humanizeAdminPathSegment(value: string, language: string): string {
45
+ const words = value.replaceAll(/[-_]+/g, " ");
46
+ return words.charAt(0).toLocaleUpperCase(language) + words.slice(1);
47
+ }
48
+
49
+ export function adminLayoutRouteBehavior(pathname: string): {
50
+ ownsViewport: boolean;
51
+ waitsForPage: boolean;
52
+ } {
53
+ const segments = adminPathSegments(pathname);
54
+ const collectionDocument =
55
+ segments[0] === "collections" &&
56
+ segments.length > 2 &&
57
+ segments[2] !== "trash" &&
58
+ segments[2] !== "upload" &&
59
+ segments[3] !== "versions";
60
+ const globalDocument =
61
+ segments[0] === "globals" && segments.length > 1 && segments[2] !== "versions";
62
+ const ownsViewport = collectionDocument || globalDocument;
63
+ const collectionPage =
64
+ segments[0] === "collections" &&
65
+ (segments.length === 2 || (segments.length === 3 && segments[2] === "trash"));
66
+
67
+ return { ownsViewport, waitsForPage: ownsViewport || collectionPage };
68
+ }
69
+
26
70
  const adminAuthPaths = new Set<string>([
27
71
  adminRoutePatterns.login,
28
72
  adminRoutePatterns.createFirstUser,
@@ -52,7 +96,8 @@ function safeAdminRedirectPath(requestedPath: string | null | undefined): string
52
96
  requestedPath === null ||
53
97
  requestedPath === undefined ||
54
98
  !requestedPath.startsWith("/") ||
55
- requestedPath.startsWith("//")
99
+ requestedPath.startsWith("//") ||
100
+ requestedPath.includes("\\")
56
101
  ) {
57
102
  return adminRoutePatterns.home;
58
103
  }
@@ -92,16 +137,22 @@ export function collectionUploadPath(collection: string): string {
92
137
  return `${collectionPath(collection)}/upload`;
93
138
  }
94
139
 
140
+ export function withContentLocale(path: string, locale: string | undefined): string {
141
+ if (locale === undefined) return path;
142
+ return `${path}?${new URLSearchParams({ locale }).toString()}`;
143
+ }
144
+
95
145
  export function documentPath(collection: string, document: string): string {
96
146
  return `${collectionPath(collection)}/${encodeURIComponent(document)}`;
97
147
  }
98
148
 
99
149
  export function documentIDFromAdminPath(pathname: string): string | undefined {
100
150
  const segments = pathname.split("/");
101
- if (segments[1] !== "collections" || segments[3] === undefined || segments[3] === "") {
151
+ if (segments[1] === undefined || segments[3] === undefined || segments[3] === "") {
102
152
  return undefined;
103
153
  }
104
154
  try {
155
+ if (decodeURIComponent(segments[1]).toLowerCase() !== "collections") return undefined;
105
156
  return decodeURIComponent(segments[3]);
106
157
  } catch {
107
158
  return undefined;
@@ -0,0 +1,74 @@
1
+ import { useHref, useLocation, useScrollRestoration } from "@hvniel/svelte-router";
2
+ import { createContext, tick } from "svelte";
3
+ import { SvelteMap } from "svelte/reactivity";
4
+
5
+ interface AdminScrollPage {
6
+ ready: () => boolean;
7
+ target?: () => HTMLElement | null;
8
+ }
9
+
10
+ const [getAdminScroll, setAdminScroll] = createContext<{
11
+ readonly page: AdminScrollPage | undefined;
12
+ register: (page: AdminScrollPage) => () => void;
13
+ }>();
14
+
15
+ /** Provide before route definitions so every retained/replaced route inherits one identity. */
16
+ export function createAdminScroll() {
17
+ // A keyed mutation lets outgoing cleanup remove only its own entry. Replacing
18
+ // a rune array from teardown can overwrite registrations made in the incoming batch.
19
+ const pages = new SvelteMap<symbol, AdminScrollPage>();
20
+ setAdminScroll({
21
+ get page() {
22
+ return Array.from(pages.values()).at(-1);
23
+ },
24
+ register(next) {
25
+ const owner = Symbol();
26
+ pages.set(owner, next);
27
+ return () => {
28
+ pages.delete(owner);
29
+ };
30
+ },
31
+ });
32
+ }
33
+
34
+ /** The shell owns restoration; a route supplies its content readiness and optional primary pane. */
35
+ export function useAdminScrollRestoration(
36
+ target: () => Window | HTMLElement | null,
37
+ waitForPage: () => boolean
38
+ ) {
39
+ const location = useLocation();
40
+ const root = useHref("/");
41
+ const scroll = getAdminScroll();
42
+ const page = $derived(scroll.page);
43
+ let renderedLocation = $state.raw<ReturnType<typeof useLocation>["current"]>();
44
+
45
+ $effect(() => {
46
+ const current = location.current;
47
+ const ready = page?.ready() ?? !waitForPage();
48
+ renderedLocation = undefined;
49
+ let active = true;
50
+ // Controllers synchronize the new URL in effects. Wait for those effects and
51
+ // their DOM flush before accepting readiness left over from the outgoing page.
52
+ tick().then(() => {
53
+ if (active && ready) renderedLocation = current;
54
+ });
55
+ return () => {
56
+ active = false;
57
+ };
58
+ });
59
+
60
+ useScrollRestoration({
61
+ storageKey: `ridu-admin-scroll:${root.current}`,
62
+ target: () => {
63
+ const shell = target();
64
+ return shell === window || page?.target === undefined ? shell : page.target();
65
+ },
66
+ ready: () => renderedLocation === location.current && (page?.ready() ?? !waitForPage()),
67
+ });
68
+ }
69
+
70
+ export function registerAdminScrollPage(page: AdminScrollPage) {
71
+ const scroll = getAdminScroll();
72
+ // Register before the shell's restoration effect; release only this route's ownership.
73
+ $effect.pre(() => scroll.register(page));
74
+ }
@@ -0,0 +1,78 @@
1
+ import type { AdminCoreView, AdminCoreViewSurface } from "@riducms/plugin";
2
+ import type { ResolvedAdminConfig } from "@riducms/plugin/admin";
3
+ import type { SchemaManifest } from "@riducms/protocol";
4
+ import { matchRoutes } from "@hvniel/svelte-router";
5
+ import { adminRoutePatterns } from "@admin/core/routing/admin-paths";
6
+
7
+ /** Select the registration first: an un-loaded exact replacement suppresses a wildcard loader. */
8
+ export function resolveCoreView(
9
+ coreViews: readonly AdminCoreView[],
10
+ surface: AdminCoreViewSurface,
11
+ resource?: string
12
+ ) {
13
+ const candidates = coreViews.filter((view) => view.surface === surface);
14
+ const target = (view: AdminCoreView) =>
15
+ view.surface === "global"
16
+ ? view.global
17
+ : view.surface === "notFound"
18
+ ? undefined
19
+ : view.collection;
20
+ return (
21
+ candidates.find((view) => target(view) === resource) ??
22
+ candidates.find((view) => target(view) === undefined)
23
+ );
24
+ }
25
+
26
+ const replacementSurfaces: Partial<Record<keyof typeof adminRoutePatterns, AdminCoreViewSurface>> =
27
+ {
28
+ collection: "collectionList",
29
+ createDocument: "collectionCreate",
30
+ createDocumentAPI: "collectionCreate",
31
+ document: "collectionEdit",
32
+ documentAPI: "collectionEdit",
33
+ global: "global",
34
+ globalAPI: "global",
35
+ };
36
+
37
+ /** Use the router's ranking/decoding, including more-specific document tabs and reserved paths. */
38
+ export function preparedCustomView(
39
+ config: ResolvedAdminConfig,
40
+ pathname: string,
41
+ manifest?: SchemaManifest
42
+ ) {
43
+ const routes = [
44
+ ...Object.entries(adminRoutePatterns).map(([key, path]) => ({
45
+ path,
46
+ surface: replacementSurfaces[key as keyof typeof adminRoutePatterns],
47
+ view: undefined,
48
+ })),
49
+ ...config.extensions.routes.map((view) => ({ path: view.path, surface: undefined, view })),
50
+ ...config.extensions.documentViews.flatMap((view) => [
51
+ {
52
+ path: `collections/${view.collection ?? ":collection"}/:document/${view.key}`,
53
+ surface: undefined,
54
+ view: undefined,
55
+ },
56
+ {
57
+ path: `globals/${view.collection ?? ":global"}/${view.key}`,
58
+ surface: undefined,
59
+ view: undefined,
60
+ },
61
+ ]),
62
+ { path: "*", surface: "notFound" as const, view: undefined },
63
+ ];
64
+ const match = matchRoutes(routes, pathname)?.[0];
65
+ if (match?.route.view !== undefined) return match.route.view;
66
+ const surface = match?.route.surface;
67
+ if (surface === undefined) return undefined;
68
+ const resource = surface === "global" ? match?.params.global : match?.params.collection;
69
+ if (
70
+ manifest !== undefined &&
71
+ surface !== "notFound" &&
72
+ !(surface === "global" ? manifest.globals : manifest.collections)?.some(
73
+ (item) => item.slug === resource
74
+ )
75
+ )
76
+ return undefined;
77
+ return resolveCoreView(config.extensions.coreViews, surface, resource);
78
+ }