@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
@@ -0,0 +1,396 @@
1
+ import {
2
+ isRecord,
3
+ ADMIN_PREPARED_ROUTE_STATE_MEDIA_TYPE,
4
+ ADMIN_PREPARED_ROUTE_STATE_VERSION,
5
+ type AdminPreparedRouteDataV1,
6
+ type AdminCreateDataV1,
7
+ type AdminPreparedRouteStateV1,
8
+ } from "@riducms/protocol";
9
+ import { RiduError, type AdminLoader } from "@riducms/sdk";
10
+ import { createContext } from "svelte";
11
+
12
+ import type { AdminRuntime } from "@admin/core/runtime/admin-runtime.svelte";
13
+ import { peekFormDraft } from "@admin/core/forms/form-draft-recovery";
14
+ import { recoverFormDraft } from "@admin/core/forms/form-schema";
15
+ import { preparedCustomView } from "@admin/core/routing/admin-view-selection";
16
+
17
+ import {
18
+ readEmbeddedAdminState,
19
+ validateAdminPreparedState,
20
+ normalizedURLIdentity,
21
+ } from "@admin/core/bootstrap/admin-prepared-state";
22
+ import {
23
+ moduleGroupsForRelativeURL,
24
+ preloadAdminModuleGroups,
25
+ } from "@admin/core/bootstrap/admin-route-modules";
26
+
27
+ export class AdminBootstrapCoordinator {
28
+ #initialState = readEmbeddedAdminState();
29
+ #contextKey = "";
30
+ #routeKey = "";
31
+ #routeData?: {
32
+ identity: string;
33
+ data: AdminPreparedRouteDataV1;
34
+ loaders: AdminPreparedRouteStateV1["loaders"];
35
+ };
36
+ #runtime?: AdminRuntime;
37
+ #basePath = "/admin";
38
+
39
+ get initialState() {
40
+ return this.#initialState;
41
+ }
42
+
43
+ configure(runtime: AdminRuntime) {
44
+ this.#runtime = runtime;
45
+ this.#basePath = normalizeAdminBasePath(runtime.adminBasePath);
46
+ }
47
+
48
+ get contextKey() {
49
+ return this.#contextKey;
50
+ }
51
+
52
+ async prepareInitial(options: { stageRoute?: boolean } = {}) {
53
+ const state = this.initialState;
54
+ if (state === undefined) return;
55
+
56
+ if (state.outcome === "redirect") {
57
+ window.location.replace(state.location);
58
+ return neverSettles();
59
+ }
60
+ if (state.outcome === "reload") {
61
+ window.location.reload();
62
+ return neverSettles();
63
+ }
64
+
65
+ if (options.stageRoute === false) {
66
+ this.clear();
67
+ this.#initialState = undefined;
68
+ this.#clearContext();
69
+ return;
70
+ }
71
+
72
+ this.#adoptContext(state);
73
+ const preparedState = await this.#prepareRestoredCreateDraft(state);
74
+ await preloadAdminModuleGroups(preparedState.moduleGroups);
75
+ if (preparedState.outcome === "prepared") this.stage(preparedState);
76
+ }
77
+
78
+ loader = async ({ request }: { request: Request }) => {
79
+ const url = new URL(request.url);
80
+
81
+ try {
82
+ // Go marks its HTML even when the initial snapshot is missing or invalid.
83
+ // Browser-only hosts such as Vite have neither that marker nor the endpoint.
84
+ if (
85
+ this.#contextKey === "" &&
86
+ document.querySelector('meta[name="ridu-admin-build-id"]') === null
87
+ ) {
88
+ await preloadAdminModuleGroups(
89
+ moduleGroupsForRelativeURL(this.#relativePath(url.pathname))
90
+ );
91
+ return null;
92
+ }
93
+
94
+ // Start URL-known chunks while Go reads the data. The response may name
95
+ // additional chunks (such as an upload preview), which we await below.
96
+ const [state] = await Promise.all([
97
+ this.#fetchRouteState(url, request.signal),
98
+ preloadAdminModuleGroups(moduleGroupsForRelativeURL(this.#relativePath(url.pathname))),
99
+ ]);
100
+ if (request.signal.aborted) return null;
101
+
102
+ // Recover a missing or invalidated Go context through a fresh document;
103
+ // navigation data cannot establish an authoritative runtime on its own.
104
+ if (state.outcome === "reload" || this.#contextKey === "") {
105
+ window.location.assign(url.href);
106
+ return neverSettles();
107
+ }
108
+ if (state.outcome === "redirect") {
109
+ window.location.assign(state.location);
110
+ return neverSettles();
111
+ }
112
+
113
+ const preparedState = await this.#prepareRestoredCreateDraft(state, request.signal);
114
+ await preloadAdminModuleGroups(preparedState.moduleGroups);
115
+ if (request.signal.aborted) return null;
116
+
117
+ // Returning data does not publish it. Only commit() may give it to the
118
+ // destination; a newer navigation can still supersede this loader.
119
+ return preparedState;
120
+ } catch {
121
+ if (request.signal.aborted) return null;
122
+
123
+ // Preparation is an optimization. Any fetch, validation, or decode failure leaves the
124
+ // destination's existing controller loader as the authoritative fallback.
125
+ this.clear();
126
+ return null;
127
+ }
128
+ };
129
+
130
+ shouldRevalidate = ({ currentUrl, nextUrl }: { currentUrl: URL; nextUrl: URL }) =>
131
+ normalizedURLIdentity(currentUrl) !== normalizedURLIdentity(nextUrl);
132
+
133
+ stage(
134
+ state: AdminPreparedRouteStateV1,
135
+ expectedIdentity = normalizedURLIdentity(new URL(window.location.href))
136
+ ) {
137
+ const identity = `${state.pathname}${state.search}`;
138
+ if (
139
+ state.version !== ADMIN_PREPARED_ROUTE_STATE_VERSION ||
140
+ expectedIdentity !== identity ||
141
+ state.fingerprint === "" ||
142
+ (this.#contextKey !== "" && state.contextKey !== this.#contextKey) ||
143
+ state.route === undefined ||
144
+ !this.#canSeedRoute(state)
145
+ ) {
146
+ this.clear();
147
+ return false;
148
+ }
149
+
150
+ this.#routeData = { identity, data: state.route, loaders: state.loaders };
151
+ this.#routeKey = routeStateKey(state);
152
+ this.#adoptContext(state);
153
+ return true;
154
+ }
155
+
156
+ commit(state: AdminPreparedRouteStateV1 | null | undefined, url: URL) {
157
+ if (state == null) {
158
+ this.clear();
159
+ return false;
160
+ }
161
+
162
+ const identity = normalizedURLIdentity(url);
163
+ if (`${state.pathname}${state.search}` !== identity) return false;
164
+ if (this.#contextKey !== "" && state.contextKey !== this.#contextKey) return false;
165
+
166
+ const navigation = state.navigation ?? state.runtime;
167
+ if (navigation !== undefined) this.#runtime?.adoptPreparedNavigation(navigation);
168
+ this.#adoptContext(state);
169
+ if (state.outcome === "prepared") {
170
+ return this.#routeKey === routeStateKey(state) || this.stage(state, identity);
171
+ }
172
+
173
+ this.clear();
174
+ return false;
175
+ }
176
+
177
+ clear() {
178
+ this.#routeKey = "";
179
+ this.#routeData = undefined;
180
+ }
181
+
182
+ routeData(pathname: string, search = "") {
183
+ const base = this.#basePath === "/" ? "" : this.#basePath;
184
+ const url = new URL(`${base}${pathname}${search}`, window.location.origin);
185
+ return this.#routeData?.identity === normalizedURLIdentity(url)
186
+ ? this.#routeData.data
187
+ : undefined;
188
+ }
189
+
190
+ discardRouteData(state: AdminPreparedRouteStateV1) {
191
+ if (this.#routeKey === routeStateKey(state)) this.clear();
192
+ }
193
+
194
+ loaderData(key: string, pathname: string, search = "") {
195
+ return this.routeData(pathname, search) === undefined
196
+ ? undefined
197
+ : this.#routeData?.loaders?.[key];
198
+ }
199
+
200
+ isRouteStaged(state: AdminPreparedRouteStateV1) {
201
+ return this.#routeKey === routeStateKey(state);
202
+ }
203
+
204
+ async #fetchRouteState(url: URL, signal: AbortSignal) {
205
+ const response = await fetch(url, {
206
+ headers: {
207
+ Accept: ADMIN_PREPARED_ROUTE_STATE_MEDIA_TYPE,
208
+ "Ridu-Admin-Context": this.#contextKey,
209
+ },
210
+ credentials: "include",
211
+ signal,
212
+ });
213
+ if (!response.ok) throw new Error(`Admin route preparation failed with ${response.status}.`);
214
+
215
+ return validateAdminPreparedState(await response.json(), url, this.#contextKey);
216
+ }
217
+
218
+ async #prepareRestoredCreateDraft(state: AdminPreparedRouteStateV1, signal?: AbortSignal) {
219
+ if (state.outcome !== "prepared" || state.route?.kind !== "collection-create") return state;
220
+
221
+ const match = /^\/collections\/([^/]+)\/create(?:\/api)?$/.exec(
222
+ this.#relativePath(state.pathname)
223
+ );
224
+ const manifest = state.runtime?.manifest ?? this.#runtime?.manifest;
225
+ const navigation = state.navigation ?? state.runtime;
226
+ const client = this.#runtime?.client;
227
+ if (match === null || manifest === undefined || client === undefined) return state;
228
+
229
+ const slug = decodeURIComponent(match[1]);
230
+ const collection = manifest.collections.find((candidate) => candidate.slug === slug);
231
+ if (collection === undefined) return state;
232
+ const checkpoint = peekFormDraft(collection.id, undefined);
233
+ if (checkpoint === undefined) return state;
234
+
235
+ const defaults = state.route.create.values;
236
+ const recovered = recoverFormDraft(
237
+ { values: defaults, original: defaults },
238
+ { values: checkpoint.values, original: checkpoint.original },
239
+ checkpoint.collection.fields,
240
+ collection.fields
241
+ );
242
+ if (samePreparedCreateValues(recovered.values, defaults)) {
243
+ return state;
244
+ }
245
+
246
+ // Go checked access against its defaults. A local draft can change a
247
+ // data-dependent rule, so settle access for the recovered values before reveal.
248
+ let access: AdminCreateDataV1["access"];
249
+ try {
250
+ const value = await client.collectionAccess(slug, {
251
+ data: recovered.values,
252
+ locale: navigation.contentLocale,
253
+ signal,
254
+ });
255
+ access = { value };
256
+ } catch (cause) {
257
+ if (!(cause instanceof RiduError)) throw cause;
258
+ access = {
259
+ error: {
260
+ code: cause.code,
261
+ status: cause.status,
262
+ message: cause.message,
263
+ issues: [...cause.issues],
264
+ ...(cause.requestId === undefined ? {} : { requestId: cause.requestId }),
265
+ ...(cause.details === undefined ? {} : { details: cause.details }),
266
+ },
267
+ };
268
+ }
269
+
270
+ return {
271
+ ...state,
272
+ route: {
273
+ ...state.route,
274
+ create: { values: recovered.values, access },
275
+ },
276
+ };
277
+ }
278
+
279
+ #adoptContext(state: AdminPreparedRouteStateV1) {
280
+ if (state.contextKey === "") return;
281
+ if (this.#contextKey !== "" && this.#contextKey !== state.contextKey) return;
282
+ this.#contextKey = state.contextKey;
283
+ document.documentElement.dataset.riduContext = state.contextKey;
284
+ }
285
+
286
+ #clearContext() {
287
+ this.#contextKey = "";
288
+ delete document.documentElement.dataset.riduContext;
289
+ }
290
+
291
+ #canSeedRoute(state: AdminPreparedRouteStateV1) {
292
+ const config = this.#runtime?.config;
293
+ const route = state.route;
294
+ if (config === undefined || route === undefined) return route !== undefined;
295
+
296
+ const selected = preparedCustomView(
297
+ config,
298
+ this.#relativePath(state.pathname),
299
+ this.#runtime?.manifest
300
+ );
301
+
302
+ if (route.kind === "custom") {
303
+ const loader = selected?.loader;
304
+ return loader !== undefined && validLoaderSeed(loader, state.loaders);
305
+ }
306
+
307
+ // A core seed belongs to the core view, never to its custom replacement.
308
+ if (selected !== undefined) return false;
309
+
310
+ switch (route.kind) {
311
+ case "dashboard": {
312
+ const panels = config.extensions.dashboardPanels;
313
+ const replacement = panels.find((panel) => panel.position === "replace");
314
+ if (replacement !== undefined) {
315
+ return (
316
+ replacement.loader !== undefined && validLoaderSeed(replacement.loader, state.loaders)
317
+ );
318
+ }
319
+
320
+ return panels.every(
321
+ (panel) => panel.loader === undefined || validLoaderSeed(panel.loader, state.loaders)
322
+ );
323
+ }
324
+ case "account":
325
+ case "security":
326
+ return !config.extensions.account.some((panel) => panel.position === "replace");
327
+ case "login":
328
+ return !config.extensions.login.some((panel) => panel.position === "replace");
329
+ default:
330
+ return true;
331
+ }
332
+ }
333
+
334
+ #relativePath(pathname: string) {
335
+ if (this.#basePath === "/") return pathname;
336
+ if (pathname === this.#basePath) return "/";
337
+ return pathname.startsWith(`${this.#basePath}/`)
338
+ ? pathname.slice(this.#basePath.length)
339
+ : pathname;
340
+ }
341
+ }
342
+
343
+ function validLoaderSeed(
344
+ loader: AdminLoader<never, unknown>,
345
+ results: AdminPreparedRouteStateV1["loaders"]
346
+ ) {
347
+ const result = results?.[loader.key];
348
+ if (result === undefined) return false;
349
+ // A structured read failure is complete data too: the view renders its error state.
350
+ if (result.error !== undefined) return true;
351
+
352
+ try {
353
+ loader.decode(result.value);
354
+ return true;
355
+ } catch {
356
+ return false;
357
+ }
358
+ }
359
+
360
+ function routeStateKey(state: AdminPreparedRouteStateV1) {
361
+ return `${state.pathname}${state.search}:${state.fingerprint}`;
362
+ }
363
+
364
+ // Create access depends on values, including a recovered local draft. Object
365
+ // key order is not data identity, and authored fields named "signal" are data.
366
+ export function samePreparedCreateValues(left: unknown, right: unknown): boolean {
367
+ if (left === right) return true;
368
+ if (Array.isArray(left) && Array.isArray(right))
369
+ return (
370
+ left.length === right.length &&
371
+ left.every((value, index) => samePreparedCreateValues(value, right[index]))
372
+ );
373
+ if (!isRecord(left) || !isRecord(right)) return false;
374
+ const keys = Object.keys(left);
375
+ return (
376
+ keys.length === Object.keys(right).length &&
377
+ keys.every(
378
+ (key) => Object.hasOwn(right, key) && samePreparedCreateValues(left[key], right[key])
379
+ )
380
+ );
381
+ }
382
+
383
+ function normalizeAdminBasePath(basePath: string) {
384
+ return `/${basePath.split("/").filter(Boolean).join("/")}`;
385
+ }
386
+
387
+ function neverSettles(): Promise<never> {
388
+ // A hard navigation transfers ownership to a new document, so the initiating handler must not
389
+ // resume and mutate the outgoing application while that document is loading.
390
+ return new Promise(() => {});
391
+ }
392
+
393
+ const [getAdminBootstrapCoordinator, setAdminBootstrapCoordinator] =
394
+ createContext<AdminBootstrapCoordinator>();
395
+
396
+ export { getAdminBootstrapCoordinator, setAdminBootstrapCoordinator };
@@ -0,0 +1,213 @@
1
+ import {
2
+ isRecord,
3
+ ADMIN_PREPARED_ROUTE_STATE_VERSION,
4
+ isAdminPreparedRouteData,
5
+ isAdminLoaderResults,
6
+ type AdminPreparedRouteStateV1,
7
+ type AdminPreparedRouteDataV1,
8
+ } from "@riducms/protocol";
9
+
10
+ const templateID = "ridu-admin-initial-state";
11
+
12
+ // Cold HTML and navigation JSON cross the same wire boundary. Validate them
13
+ // here, before the coordinator exposes anything to the runtime or controllers.
14
+ export function readEmbeddedAdminState(): AdminPreparedRouteStateV1 | undefined {
15
+ const template = document.getElementById(templateID);
16
+ if (!(template instanceof HTMLTemplateElement)) return undefined;
17
+
18
+ try {
19
+ return validateAdminPreparedState(
20
+ JSON.parse(template.content.textContent ?? ""),
21
+ new URL(location.href)
22
+ );
23
+ } catch {
24
+ // Embedded state is optional; malformed or stale data falls back to the SDK bootstrap.
25
+ return undefined;
26
+ } finally {
27
+ template.remove();
28
+ }
29
+ }
30
+
31
+ export function validateAdminPreparedState(
32
+ value: unknown,
33
+ url: URL,
34
+ contextKey?: string
35
+ ): AdminPreparedRouteStateV1 {
36
+ if (!isRecord(value)) throw new Error("Admin prepared state must be an object.");
37
+ if (value.version !== ADMIN_PREPARED_ROUTE_STATE_VERSION)
38
+ throw new Error("Admin prepared state version is incompatible.");
39
+
40
+ if (typeof value.pathname !== "string" || typeof value.search !== "string")
41
+ throw new Error("Admin prepared route identity is invalid.");
42
+ if (`${value.pathname}${value.search}` !== normalizedURLIdentity(url))
43
+ throw new Error("Admin prepared route identity is stale.");
44
+
45
+ if (
46
+ typeof value.contextKey !== "string" ||
47
+ (value.contextKey !== "" && !/^[0-9a-f]{32}$/.test(value.contextKey))
48
+ )
49
+ throw new Error("Admin prepared context key is invalid.");
50
+ if (
51
+ typeof value.fingerprint !== "string" ||
52
+ (value.fingerprint !== "" && !/^[0-9a-f]{64}$/.test(value.fingerprint))
53
+ )
54
+ throw new Error("Admin prepared fingerprint is invalid.");
55
+ if (
56
+ typeof value.buildId !== "string" ||
57
+ (value.buildId !== "" && !/^[0-9a-f]{24}$/.test(value.buildId))
58
+ )
59
+ throw new Error("Admin prepared build ID is invalid.");
60
+ if (
61
+ !Array.isArray(value.moduleGroups) ||
62
+ !value.moduleGroups.every((group) => typeof group === "string")
63
+ )
64
+ throw new Error("Admin prepared module groups are invalid.");
65
+
66
+ const expectedBuildID = document
67
+ .querySelector<HTMLMetaElement>('meta[name="ridu-admin-build-id"]')
68
+ ?.getAttribute("content");
69
+ if (
70
+ value.outcome !== "reload" &&
71
+ // An empty navigation context requires a fresh document, including its build.
72
+ contextKey !== "" &&
73
+ expectedBuildID != null &&
74
+ value.buildId !== expectedBuildID
75
+ )
76
+ throw new Error("Admin prepared state build is stale.");
77
+
78
+ if (
79
+ value.loaders !== undefined &&
80
+ (value.outcome !== "prepared" || !isAdminLoaderResults(value.loaders))
81
+ )
82
+ throw new Error("Admin loader results are invalid.");
83
+
84
+ // Each outcome has a different payload. Keep those requirements separate so
85
+ // adding a field to one outcome cannot silently relax another's validation.
86
+ switch (value.outcome) {
87
+ case "prepared":
88
+ if (
89
+ !isAdminPreparedRouteData(value.route) ||
90
+ (value.runtime === undefined && value.navigation === undefined) ||
91
+ !validPreparedModuleGroups(value.moduleGroups, value.route)
92
+ )
93
+ throw new Error("Admin prepared route data is invalid.");
94
+ break;
95
+ case "redirect":
96
+ if (typeof value.location !== "string")
97
+ throw new Error("Admin prepared redirect location is invalid.");
98
+ break;
99
+ case "reload":
100
+ if (
101
+ value.runtime !== undefined ||
102
+ value.navigation !== undefined ||
103
+ value.route !== undefined
104
+ )
105
+ throw new Error("Admin reload must not carry runtime or route data.");
106
+ break;
107
+ case "fallback":
108
+ if (value.location !== undefined || !validDiagnostic(value.diagnostic))
109
+ throw new Error("Admin fallback requires a diagnostic and no redirect.");
110
+ break;
111
+ default:
112
+ throw new Error("Admin prepared state outcome is invalid.");
113
+ }
114
+
115
+ if (value.runtime !== undefined && !validPreparedRuntime(value.runtime))
116
+ throw new Error("Admin prepared runtime is invalid.");
117
+
118
+ // Compact navigation can reuse a runtime, but can never establish one.
119
+ if (
120
+ value.navigation !== undefined &&
121
+ (value.runtime !== undefined ||
122
+ !contextKey ||
123
+ value.contextKey !== contextKey ||
124
+ !validPreparedNavigation(value.navigation))
125
+ )
126
+ throw new Error("Admin prepared navigation requires the matching runtime context.");
127
+
128
+ return value as unknown as AdminPreparedRouteStateV1;
129
+ }
130
+
131
+ function validPreparedModuleGroups(groups: string[], route: AdminPreparedRouteDataV1) {
132
+ const allowed = new Set([
133
+ "entry",
134
+ "document",
135
+ "date",
136
+ "upload-preview",
137
+ "bulk-upload",
138
+ "document-api",
139
+ "versions",
140
+ ]);
141
+ if (
142
+ groups.some((group) => !allowed.has(group)) ||
143
+ new Set(groups).size !== groups.length ||
144
+ !groups.includes("entry")
145
+ )
146
+ return false;
147
+
148
+ switch (route.kind) {
149
+ case "collection-create":
150
+ case "collection-document":
151
+ case "global-document":
152
+ return groups.includes("document");
153
+ case "collection-api":
154
+ case "global-api":
155
+ return groups.includes("document") && groups.includes("document-api");
156
+ case "collection-versions":
157
+ case "global-versions":
158
+ return groups.includes("versions");
159
+ case "upload":
160
+ return groups.includes("bulk-upload");
161
+ case "security":
162
+ return groups.includes("date");
163
+ default:
164
+ return true;
165
+ }
166
+ }
167
+
168
+ function validPreparedNavigation(value: unknown) {
169
+ return (
170
+ isRecord(value) &&
171
+ isRecord(value.collectionOperations) &&
172
+ isRecord(value.globalOperations) &&
173
+ (value.contentLocale === undefined || typeof value.contentLocale === "string")
174
+ );
175
+ }
176
+
177
+ function validPreparedRuntime(value: unknown) {
178
+ if (!isRecord(value) || !isRecord(value.manifest) || !isRecord(value.manifest.application))
179
+ return false;
180
+
181
+ if (
182
+ !Array.isArray(value.manifest.collections) ||
183
+ (value.manifest.globals !== undefined && !Array.isArray(value.manifest.globals)) ||
184
+ !Array.isArray(value.manifest.plugins) ||
185
+ typeof value.authBootstrapAvailable !== "boolean" ||
186
+ !validPreparedNavigation(value) ||
187
+ !isRecord(value.preferences) ||
188
+ !["system", "light", "dark"].includes(String(value.theme))
189
+ )
190
+ return false;
191
+
192
+ if (value.session === undefined) return true;
193
+ return (
194
+ isRecord(value.session) &&
195
+ typeof value.session.id === "string" &&
196
+ typeof value.session.collection === "string" &&
197
+ isRecord(value.session.user)
198
+ );
199
+ }
200
+
201
+ function validDiagnostic(value: unknown) {
202
+ return isRecord(value) && typeof value.code === "string" && typeof value.message === "string";
203
+ }
204
+
205
+ export function normalizedURLIdentity(url: Pick<URL, "pathname" | "search">) {
206
+ const parameters = new URLSearchParams(url.search);
207
+ // Stable key sorting matches Go's identity and preserves repeated-value order.
208
+ parameters.sort();
209
+
210
+ const search = parameters.size === 0 ? "" : `?${parameters}`;
211
+ const pathname = url.pathname.length > 1 ? url.pathname.replace(/\/$/, "") : url.pathname;
212
+ return pathname + search;
213
+ }
@@ -0,0 +1,66 @@
1
+ // Keep the exports, not a second import-promise cache: the browser already
2
+ // deduplicates imports. Route wrappers need these synchronously on first render
3
+ // so an already-loaded component does not briefly enter an {#await} placeholder.
4
+ const preparedModules = new Map<string, unknown>();
5
+
6
+ export function preparedAdminModule<Module>(group: string): Module | undefined {
7
+ return preparedModules.get(group) as Module | undefined;
8
+ }
9
+
10
+ export function moduleGroupsForRelativeURL(pathname: string) {
11
+ const segments = pathname.split("/").filter(Boolean);
12
+ if (segments[0] === "collections") {
13
+ if (segments.length === 3 && segments[2] === "upload") return ["bulk-upload"];
14
+ if ((segments.length === 4 || segments.length === 5) && segments[3] === "versions") {
15
+ return ["versions"];
16
+ }
17
+ if (segments.length === 3 && segments[2] !== "trash" && segments[2] !== "upload") {
18
+ return ["document"];
19
+ }
20
+ if (segments.length === 4 && segments[3] === "api") return ["document", "document-api"];
21
+ }
22
+ if (segments[0] === "globals") {
23
+ if ((segments.length === 3 || segments.length === 4) && segments[2] === "versions") {
24
+ return ["versions"];
25
+ }
26
+ if (segments.length === 2) return ["document"];
27
+ if (segments.length === 3 && segments[2] === "api") return ["document", "document-api"];
28
+ }
29
+ if (segments.length === 2 && segments[0] === "account" && segments[1] === "security") {
30
+ return ["date"];
31
+ }
32
+ return [];
33
+ }
34
+
35
+ export async function preloadAdminModuleGroups(groups: readonly string[]) {
36
+ await Promise.all(
37
+ [...new Set(groups)].map(async (group) => {
38
+ if (preparedModules.has(group)) return;
39
+ let module: unknown;
40
+ switch (group) {
41
+ case "bulk-upload":
42
+ module = await import("@admin/features/uploads/bulk-upload-route.svelte");
43
+ break;
44
+ case "document":
45
+ module = await import("@admin/features/documents/document-route.svelte");
46
+ break;
47
+ case "versions":
48
+ module = await import("@admin/features/versions/version-history-route.svelte");
49
+ break;
50
+ case "date":
51
+ module =
52
+ await import("@admin/components/ui/date-value-control/date-value-control.svelte");
53
+ break;
54
+ case "upload-preview":
55
+ module = await import("@admin/features/uploads/upload-control.svelte");
56
+ break;
57
+ case "document-api":
58
+ module = await import("@admin/features/documents/document-api-view.svelte");
59
+ break;
60
+ default:
61
+ return;
62
+ }
63
+ preparedModules.set(group, module);
64
+ })
65
+ );
66
+ }