@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,122 +1,111 @@
1
1
  <script lang="ts">
2
+ import APIReference from "@admin/features/api-reference/api-reference.svelte";
3
+ import {
4
+ isErrorEnvelope,
5
+ isRecord,
6
+ type AdminDocumentV1,
7
+ type AdminReadResultV1,
8
+ } from "@riducms/protocol";
2
9
  import { getAdminI18n } from "@riducms/plugin";
3
- import { onDestroy } from "svelte";
4
10
  import CheckIcon from "~icons/lucide/check";
5
11
  import CopyIcon from "~icons/lucide/copy";
6
- import ExternalLinkIcon from "~icons/lucide/external-link";
7
12
  import RefreshCwIcon from "~icons/lucide/refresh-cw";
8
13
 
9
- import CodeBlock from "@admin/components/code-block/code-block.svelte";
10
14
  import JsonViewer from "@admin/components/json-tree/json-viewer.svelte";
11
15
  import { Banner } from "@admin/components/ui/banner";
12
- import { Button } from "@admin/components/ui/button";
13
- import { Checkbox } from "@admin/components/ui/checkbox";
14
- import { Input } from "@admin/components/ui/input";
15
- import { Tabs, TabsContent, TabsList, TabsTrigger } from "@admin/components/ui/tabs";
16
-
17
- type APIAction = "list" | "find" | "create" | "update" | "duplicate" | "delete";
18
- const collectionActions: readonly {
19
- key: APIAction;
20
- method: "GET" | "POST" | "PATCH" | "DELETE";
21
- }[] = [
22
- { key: "list", method: "GET" },
23
- { key: "find", method: "GET" },
24
- { key: "create", method: "POST" },
25
- { key: "update", method: "PATCH" },
26
- { key: "duplicate", method: "POST" },
27
- { key: "delete", method: "DELETE" },
28
- ];
29
- const i18n = getAdminI18n();
16
+ import { CopyFeedback } from "@admin/core/clipboard/copy-feedback.svelte";
17
+ import { Checkbox, Input } from "@riducms/ui";
18
+ import { Select, SelectTrigger, SelectContent, SelectItem } from "@admin/components/ui/select";
19
+ import { getAdminRuntime } from "@admin/core/runtime/admin-runtime.svelte";
20
+ import "@admin/features/documents/document-api-view.scss";
30
21
 
31
22
  let {
32
23
  resourceSlug,
33
- resourceLabel,
34
24
  documentID,
35
25
  globalResource = false,
26
+ contentLocale,
36
27
  fallbackValue,
28
+ prepared,
37
29
  }: {
38
30
  resourceSlug: string;
39
- resourceLabel: string;
40
31
  documentID?: string;
41
32
  globalResource?: boolean;
33
+ contentLocale?: string;
42
34
  fallbackValue: unknown;
35
+ prepared?: AdminReadResultV1<AdminDocumentV1>;
43
36
  } = $props();
44
37
 
38
+ const i18n = getAdminI18n();
39
+ const runtime = getAdminRuntime();
40
+ const id = $props.id();
41
+ const referenceCollection = $derived(
42
+ !globalResource
43
+ ? runtime.manifest?.collections.find((collection) => collection.slug === resourceSlug)
44
+ : undefined
45
+ );
45
46
  let depth = $state(2);
46
47
  let authenticated = $state(true);
48
+ let locale = $state<string>();
49
+ let expanded = $state(false);
47
50
  let requestRevision = $state(0);
48
51
  let loading = $state(false);
49
- // Begin with a non-reactive form snapshot until a live request becomes available.
50
- // svelte-ignore state_referenced_locally
51
- let responseValue = $state.raw<unknown>($state.snapshot(fallbackValue));
52
- let responseStatus = $state<number>();
53
- let responseStatusText = $state("");
54
- let responseDuration = $state<number>();
55
- let responseSize = $state<number>();
52
+ let responseValue = $state.raw<unknown>();
56
53
  let requestError = $state<string>();
57
- let example = $state<"fetch" | "sdk" | "curl">("fetch");
58
- let apiAction = $state<APIAction>("find");
59
- let copied = $state<"url" | "example">();
60
- let copyResetTimer: ReturnType<typeof setTimeout> | undefined;
54
+ const copyFeedback = new CopyFeedback();
55
+ let adoptedRouteKey = "";
56
+ let preparedRequestKey = "";
61
57
 
62
- onDestroy(() => clearTimeout(copyResetTimer));
63
-
64
- const availableActions = $derived(
65
- globalResource
66
- ? collectionActions.filter((action) => action.key === "find" || action.key === "update")
67
- : collectionActions
68
- );
69
- const selectedAction = $derived(
70
- availableActions.find((action) => action.key === apiAction) ?? availableActions[0]!
58
+ const routeKey = $derived(
59
+ JSON.stringify([resourceSlug, documentID, globalResource, contentLocale])
71
60
  );
72
- const runnableAction = $derived(apiAction === "list" || apiAction === "find");
73
- const liveRequest = $derived(
74
- runnableAction && (apiAction === "list" || globalResource || documentID !== undefined)
61
+ const requestKey = $derived(
62
+ JSON.stringify([routeKey, depth, authenticated, locale, requestRevision])
75
63
  );
76
- const collectionEndpoint = $derived(`/api/collections/${encodeURIComponent(resourceSlug)}`);
77
- const documentEndpoint = $derived(
78
- `${collectionEndpoint}/${documentID === undefined ? ":id" : encodeURIComponent(documentID)}`
79
- );
80
- const endpointPath = $derived.by(() => {
81
- if (globalResource) return `/api/globals/${encodeURIComponent(resourceSlug)}`;
82
- if (apiAction === "list" || apiAction === "create") return collectionEndpoint;
83
- if (apiAction === "duplicate") return `${documentEndpoint}/duplicate`;
84
- return documentEndpoint;
85
- });
86
64
  const requestURL = $derived.by(() => {
87
- if (endpointPath.includes(":id")) return "";
88
- const url = new URL(endpointPath, window.location.origin);
89
- if (runnableAction) url.searchParams.set("depth", String(depth));
65
+ if (!globalResource && documentID === undefined) return "";
66
+ const path = globalResource
67
+ ? `/api/globals/${encodeURIComponent(resourceSlug)}`
68
+ : `/api/collections/${encodeURIComponent(resourceSlug)}/${encodeURIComponent(documentID!)}`;
69
+ const url = new URL(path, window.location.origin);
70
+ url.searchParams.set("depth", String(depth));
71
+ if (locale) url.searchParams.set("locale", locale);
90
72
  return url.href;
91
73
  });
92
- const requestExample = $derived(requestExampleFor(example));
93
74
 
94
- function requestExampleFor(exampleKind: typeof example) {
95
- if (apiAction !== "find") return actionExample(exampleKind);
96
- if (exampleKind === "sdk") {
97
- if (globalResource)
98
- return `const settings = await client.global(${JSON.stringify(resourceSlug)}, { depth: ${depth} });`;
99
- return `const document = await client.find(\n ${JSON.stringify(resourceSlug)},\n ${JSON.stringify(documentID ?? "document-id")},\n { depth: ${depth} },\n);`;
100
- }
101
- if (exampleKind === "curl") {
102
- return `curl ${authenticated ? "-H 'Authorization: Bearer <api-key>' \\\n " : ""}${JSON.stringify(requestURL || endpointPath)}`;
103
- }
104
- return `const response = await fetch(${JSON.stringify(requestURL || endpointPath)}, {\n headers: { Accept: "application/json" },\n credentials: ${JSON.stringify(authenticated ? "include" : "omit")},\n});\n\nif (!response.ok) throw new Error(\`Request failed: \${response.status}\`);\nconst { doc } = await response.json();`;
105
- }
75
+ const currentCopy = $derived(
76
+ copyFeedback.result?.source === requestURL ? copyFeedback.result : undefined
77
+ );
78
+
79
+ // A retained route adopts its prepared read once; changing query controls owns a new request.
80
+ $effect.pre(() => {
81
+ if (routeKey === adoptedRouteKey) return;
82
+ adoptedRouteKey = routeKey;
83
+ depth = 2;
84
+ authenticated = true;
85
+ locale = contentLocale;
86
+ expanded = false;
87
+ requestRevision = 0;
88
+ loading = false;
89
+ requestError = prepared?.error?.message;
90
+ responseValue =
91
+ prepared === undefined
92
+ ? $state.snapshot(fallbackValue)
93
+ : prepared.error === undefined
94
+ ? prepared.value
95
+ : { error: prepared.error.message };
96
+ preparedRequestKey = prepared === undefined ? "" : requestKey;
97
+ });
106
98
 
107
99
  $effect(() => {
108
- requestRevision;
100
+ const key = requestKey;
109
101
  const url = requestURL;
110
102
  const includeSession = authenticated;
111
- if (!liveRequest || url === "") {
112
- responseValue = $state.snapshot(fallbackValue);
113
- responseStatus = undefined;
114
- responseStatusText = "";
115
- responseDuration = undefined;
116
- responseSize = undefined;
117
- requestError = undefined;
103
+ if (preparedRequestKey === key) {
104
+ preparedRequestKey = "";
118
105
  return;
119
106
  }
107
+ if (!url) return;
108
+
120
109
  const abort = new AbortController();
121
110
  execute(url, includeSession, abort.signal);
122
111
  return () => abort.abort();
@@ -125,7 +114,6 @@
125
114
  async function execute(url: string, includeSession: boolean, signal: AbortSignal) {
126
115
  loading = true;
127
116
  requestError = undefined;
128
- const startedAt = performance.now();
129
117
  try {
130
118
  const response = await fetch(url, {
131
119
  headers: { Accept: "application/json" },
@@ -134,24 +122,17 @@
134
122
  });
135
123
  const encoded = await response.text();
136
124
  if (signal.aborted) return;
137
- responseStatus = response.status;
138
- responseStatusText = response.statusText;
139
- responseDuration = Math.round(performance.now() - startedAt);
140
- responseSize = new TextEncoder().encode(encoded).byteLength;
125
+
141
126
  let body: unknown = encoded;
142
127
  try {
143
128
  body = encoded === "" ? null : JSON.parse(encoded);
144
129
  } catch {
145
- // Non-JSON failures remain inspectable in the response viewer.
130
+ // Keep non-JSON failures inspectable too.
146
131
  }
147
- responseValue = documentFromEnvelope(body);
132
+ responseValue = isRecord(body) && "doc" in body ? body.doc : body;
148
133
  if (!response.ok) requestError = errorMessage(body, response);
149
134
  } catch (cause) {
150
135
  if (signal.aborted) return;
151
- responseDuration = Math.round(performance.now() - startedAt);
152
- responseStatus = undefined;
153
- responseStatusText = "";
154
- responseSize = undefined;
155
136
  requestError = cause instanceof Error ? cause.message : i18n.t("documents:apiRequestFailed");
156
137
  responseValue = { error: requestError };
157
138
  } finally {
@@ -159,389 +140,123 @@
159
140
  }
160
141
  }
161
142
 
162
- function documentFromEnvelope(value: unknown) {
163
- return isRecord(value) && "doc" in value ? value.doc : value;
164
- }
165
-
166
143
  function errorMessage(value: unknown, response: Response) {
167
- if (isRecord(value) && typeof value.message === "string") return value.message;
168
- if (isRecord(value) && isRecord(value.error) && typeof value.error.message === "string") {
169
- return value.error.message;
170
- }
144
+ if (isErrorEnvelope(value)) return value.error.message;
171
145
  return `${response.status} ${response.statusText}`.trim();
172
146
  }
173
147
 
174
- function isRecord(value: unknown): value is Record<string, unknown> {
175
- return value !== null && typeof value === "object" && !Array.isArray(value);
176
- }
177
-
178
- function actionExample(language: "fetch" | "sdk" | "curl") {
179
- if (language === "sdk") return sdkActionExample();
180
- if (language === "curl") return curlActionExample();
181
- return fetchActionExample();
182
- }
183
-
184
- function sdkActionExample() {
185
- if (globalResource) {
186
- return [
187
- `const settings = await client.updateGlobal(${JSON.stringify(resourceSlug)}, {`,
188
- ' siteName: "Updated value",',
189
- "});",
190
- ].join("\n");
191
- }
192
- const slug = JSON.stringify(resourceSlug);
193
- const id = JSON.stringify(documentID ?? "document-id");
194
- switch (apiAction) {
195
- case "list":
196
- return [
197
- `const page = await client.list(${slug}, {`,
198
- " page: 1,",
199
- " limit: 10,",
200
- ` depth: ${depth},`,
201
- "});",
202
- ].join("\n");
203
- case "create":
204
- return `const document = await client.create(${slug}, {\n title: "New ${resourceLabel}",\n});`;
205
- case "update":
206
- return `const document = await client.update(${slug}, ${id}, {\n title: "Updated ${resourceLabel}",\n});`;
207
- case "duplicate":
208
- return `const copy = await client.duplicate(${slug}, ${id}, {\n title: "Copy of ${resourceLabel}",\n});`;
209
- case "delete":
210
- return `const result = await client.delete(${slug}, ${id});`;
211
- default:
212
- return "";
213
- }
214
- }
215
-
216
- function fetchActionExample() {
217
- const hasBody = apiAction === "create" || apiAction === "update" || apiAction === "duplicate";
218
- const resultName = apiAction === "list" ? "page" : apiAction === "delete" ? "result" : "doc";
219
- const lines = [
220
- `const response = await fetch(${JSON.stringify(requestURL || endpointPath)}, {`,
221
- ` method: ${JSON.stringify(selectedAction.method)},`,
222
- " headers: {",
223
- ' Accept: "application/json",',
224
- ];
225
- if (hasBody) lines.push(' "Content-Type": "application/json",');
226
- lines.push(" },", ` credentials: ${JSON.stringify(authenticated ? "include" : "omit")},`);
227
- if (hasBody) lines.push(' body: JSON.stringify({ title: "Example value" }),');
228
- lines.push(
229
- "});",
230
- "",
231
- "if (!response.ok) throw new Error(`Request failed: ${response.status}`);"
232
- );
233
- lines.push(
234
- apiAction === "list" || apiAction === "delete"
235
- ? `const ${resultName} = await response.json();`
236
- : `const { ${resultName} } = await response.json();`
237
- );
238
- return lines.join("\n");
239
- }
240
-
241
- function curlActionExample() {
242
- const hasBody = apiAction === "create" || apiAction === "update" || apiAction === "duplicate";
243
- const lines = [`curl -X ${selectedAction.method} \\`, " -H 'Accept: application/json' \\"];
244
- if (authenticated) lines.push(" -H 'Authorization: Bearer <api-key>' \\");
245
- if (hasBody) {
246
- lines.push(
247
- " -H 'Content-Type: application/json' \\",
248
- ' -d \'{"title":"Example value"}\' \\'
249
- );
250
- }
251
- lines.push(` ${JSON.stringify(requestURL || endpointPath)}`);
252
- return lines.join("\n");
253
- }
254
-
255
148
  function setDepth(event: Event) {
256
149
  const next = Number((event.currentTarget as HTMLInputElement).value);
257
150
  depth = Number.isFinite(next) ? Math.min(5, Math.max(0, Math.round(next))) : 0;
258
151
  }
259
-
260
- async function copy(kind: "url" | "example", value: string) {
261
- await navigator.clipboard.writeText(value);
262
- copied = kind;
263
- clearTimeout(copyResetTimer);
264
- copyResetTimer = window.setTimeout(() => {
265
- if (copied === kind) copied = undefined;
266
- }, 1_500);
267
- }
268
-
269
- function formatBytes(value: number | undefined) {
270
- if (value === undefined) return "";
271
- return value < 1_024
272
- ? i18n.formatNumber(value, { style: "unit", unit: "byte" })
273
- : i18n.formatNumber(value / 1_024, {
274
- maximumFractionDigits: 1,
275
- style: "unit",
276
- unit: "kilobyte",
277
- });
278
- }
279
-
280
- function actionLabel(action: APIAction) {
281
- switch (action) {
282
- case "list":
283
- return i18n.t("documents:apiListSearch");
284
- case "find":
285
- return i18n.t("documents:apiView");
286
- case "create":
287
- return i18n.t("documents:apiCreate");
288
- case "update":
289
- return i18n.t("documents:apiUpdate");
290
- case "duplicate":
291
- return i18n.t("documents:duplicate");
292
- case "delete":
293
- return i18n.t("documents:delete");
294
- }
295
- }
296
152
  </script>
297
153
 
298
- <section class="grid gap-6" aria-labelledby="api-heading">
299
- <header class="grid gap-2">
300
- <p class="font-mono text-[9.5px] tracking-[0.15em] text-foreground-faint uppercase">
301
- {i18n.t("documents:liveRESTExplorer")}
302
- </p>
303
- <h2
304
- id="api-heading"
305
- class="text-[28px] font-semibold leading-tight tracking-[-0.02em] text-foreground-strong"
306
- >
307
- {i18n.t("documents:api")}
308
- </h2>
309
- <p class="max-w-2xl text-[13px] leading-5 text-foreground-muted">
310
- {i18n.t("documents:apiDescription", {
311
- label: resourceLabel.toLocaleLowerCase(i18n.language),
312
- })}
313
- </p>
314
- </header>
315
-
316
- <div class="grid gap-5 xl:grid-cols-[minmax(0,1.65fr)_minmax(340px,0.85fr)] xl:items-start">
317
- <div class="grid min-w-0 gap-4">
318
- <div class="overflow-hidden rounded-[4px] border border-control-border bg-background">
319
- <div
320
- class="flex min-w-0 flex-wrap items-center gap-2 border-b border-control-border bg-control px-3 py-2.5"
321
- >
322
- <span
323
- class={[
324
- "rounded-[3px] px-2 py-1 font-mono text-[10px] font-semibold",
325
- selectedAction.method === "GET" && "bg-success/12 text-success",
326
- selectedAction.method === "POST" && "bg-primary/12 text-primary",
327
- selectedAction.method === "PATCH" && "bg-warning/12 text-warning",
328
- selectedAction.method === "DELETE" && "bg-destructive/12 text-destructive",
329
- ]}
154
+ <section
155
+ class={["ridu-document-api", expanded && "ridu-document-api--expanded"]}
156
+ aria-label={i18n.t("documents:api")}
157
+ >
158
+ <div class="ridu-document-api__configuration" hidden={expanded}>
159
+ <div class="ridu-document-api__url">
160
+ <div class="ridu-document-api__label">
161
+ {i18n.t("documents:apiURL")}
162
+ {#if requestURL}
163
+ <button
164
+ type="button"
165
+ class="ridu-document-api__icon"
166
+ onclick={() => copyFeedback.copy(requestURL)}
167
+ aria-label={i18n.t("documents:copyURL")}
168
+ title={i18n.t(
169
+ currentCopy?.copied
170
+ ? "general:copied"
171
+ : currentCopy?.copied === false
172
+ ? "general:copyUnavailable"
173
+ : "documents:copyURL"
174
+ )}
330
175
  >
331
- {selectedAction.method}
332
- </span>
333
- <code class="min-w-0 flex-1 truncate text-[11.5px] text-foreground-muted">
334
- {endpointPath || i18n.t("documents:apiAvailableAfterSave")}
335
- </code>
336
- {#if requestURL !== ""}
337
- <Button variant="ghost" size="xs" onclick={() => copy("url", requestURL)}>
338
- {#if copied === "url"}<CheckIcon class="size-3" />
339
- {i18n.t("documents:copied")}{:else}<CopyIcon class="size-3" />
340
- {i18n.t("documents:copyURL")}{/if}
341
- </Button>
342
- <Button
343
- variant="ghost"
344
- size="icon-xs"
345
- href={requestURL}
346
- target="_blank"
347
- aria-label={i18n.t("documents:openAPIURL")}
348
- tooltip={i18n.t("documents:openAPIURL")}
349
- >
350
- <ExternalLinkIcon class="size-3.5" />
351
- </Button>
352
- {/if}
353
- </div>
354
-
355
- <div
356
- class="grid gap-4 border-b border-control-border px-4 py-4 md:grid-cols-[minmax(0,1fr)_auto] md:items-end"
357
- >
358
- <div class="flex flex-wrap items-end gap-5">
359
- {#if runnableAction && !globalResource}
360
- <label class="grid gap-1.5" for="ridu-api-depth">
361
- <span class="text-[11.5px] font-medium text-foreground-muted">
362
- {i18n.t("documents:apiDepth")}
363
- </span>
364
- <Input
365
- id="ridu-api-depth"
366
- class="w-20 font-mono"
367
- type="number"
368
- min="0"
369
- max="5"
370
- step="1"
371
- value={depth}
372
- oninput={setDepth}
373
- />
374
- </label>
176
+ {#if currentCopy?.copied}
177
+ <CheckIcon />
178
+ {:else}
179
+ <CopyIcon />
375
180
  {/if}
376
- <label
377
- class="flex min-h-10 cursor-pointer items-center gap-2 pb-0.5"
378
- for="ridu-api-authenticated"
379
- >
380
- <Checkbox id="ridu-api-authenticated" bind:checked={authenticated} />
381
- <span class="text-[12.5px] text-foreground-muted">
382
- {i18n.t("documents:apiAuthenticated")}
383
- </span>
384
- </label>
385
- </div>
386
- <Button
387
- variant="outline"
388
- size="sm"
389
- disabled={!liveRequest || loading}
390
- onclick={() => (requestRevision += 1)}
391
- >
392
- <RefreshCwIcon class={["size-3.5", loading && "animate-spin"]} />
393
- {loading
394
- ? i18n.t("documents:apiRequesting")
395
- : runnableAction
396
- ? i18n.t("documents:apiRunRequest")
397
- : i18n.t("documents:apiExampleOnly")}
398
- </Button>
399
- </div>
400
-
401
- <div
402
- class="flex min-h-10 flex-wrap items-center gap-x-4 gap-y-1 px-4 py-2 text-[10.5px] text-foreground-faint"
403
- >
404
- {#if responseStatus !== undefined}
405
- <strong class={responseStatus < 400 ? "text-success" : "text-destructive"}>
406
- {i18n.formatNumber(responseStatus, { useGrouping: false })}
407
- {responseStatusText}
408
- </strong>
409
- {/if}
410
- {#if responseDuration !== undefined}<span>
411
- {i18n.formatNumber(responseDuration, {
412
- style: "unit",
413
- unit: "millisecond",
414
- })}
415
- </span>{/if}
416
- {#if responseSize !== undefined}<span>{formatBytes(responseSize)}</span>{/if}
417
- <span>
418
- {authenticated
419
- ? i18n.t("documents:apiCurrentSession")
420
- : i18n.t("documents:apiPublicRequest")}
181
+ </button>
182
+ <span class="ridu-document-api__status" role="status">
183
+ {currentCopy
184
+ ? i18n.t(currentCopy.copied ? "general:copied" : "general:copyUnavailable")
185
+ : ""}
421
186
  </span>
422
- </div>
187
+ <button
188
+ type="button"
189
+ class="ridu-document-api__icon ridu-document-api__refresh"
190
+ disabled={loading}
191
+ onclick={() => requestRevision++}
192
+ aria-label={i18n.t("documents:apiRunRequest")}
193
+ title={i18n.t("documents:apiRunRequest")}
194
+ >
195
+ <RefreshCwIcon />
196
+ </button>
197
+ {/if}
423
198
  </div>
199
+ {#if requestURL}
200
+ <a href={requestURL} target="_blank" rel="noopener noreferrer">{requestURL}</a>
201
+ {:else}
202
+ <Banner>{i18n.t("documents:apiRunnableAfterSave")}</Banner>
203
+ {/if}
204
+ </div>
424
205
 
425
- {const requestBanner = $derived(
426
- !runnableAction
427
- ? {
428
- message: i18n.t("documents:apiMutationDescription"),
429
- }
430
- : !liveRequest
431
- ? { message: i18n.t("documents:apiRunnableAfterSave") }
432
- : requestError === undefined
433
- ? undefined
434
- : { message: requestError, tone: "destructive" as const }
435
- )}
206
+ <div class="ridu-document-api__controls">
207
+ <div class="ridu-document-api__checkboxes">
208
+ <label for="{id}-authenticated">
209
+ <Checkbox id="{id}-authenticated" bind:checked={authenticated} />{i18n.t(
210
+ "documents:apiAuthenticated"
211
+ )}
212
+ </label>
213
+ </div>
436
214
 
437
- {#if requestBanner !== undefined}
438
- <Banner tone={requestBanner.tone}>{requestBanner.message}</Banner>
215
+ {#if runtime.contentLocales.length > 0}
216
+ <div class="ridu-document-api__field">
217
+ <label for="{id}-locale">{i18n.t("documents:locale")}</label>
218
+ <Select type="single" bind:value={locale}>
219
+ <SelectTrigger id="{id}-locale" aria-label={i18n.t("documents:locale")}>
220
+ {runtime.contentLocales.find((option) => option.code === locale)?.label ??
221
+ locale ??
222
+ i18n.t("documents:locale")}
223
+ </SelectTrigger>
224
+ <SelectContent>
225
+ {#each runtime.contentLocales as option (option.code)}
226
+ <SelectItem value={option.code} label={option.label ?? option.code} />
227
+ {/each}
228
+ </SelectContent>
229
+ </Select>
230
+ </div>
439
231
  {/if}
440
232
 
441
- <JsonViewer class="xl:h-[calc(100vh-9rem)] xl:min-h-[620px]" value={responseValue} />
233
+ <div class="ridu-document-api__field">
234
+ <label for="{id}-depth">{i18n.t("documents:apiDepth")}</label>
235
+ <Input
236
+ id="{id}-depth"
237
+ type="number"
238
+ min="0"
239
+ max="5"
240
+ step="1"
241
+ value={depth}
242
+ oninput={setDepth}
243
+ />
244
+ </div>
442
245
  </div>
443
246
 
444
- <aside class="grid min-w-0 gap-4 xl:sticky xl:top-4">
445
- <nav
446
- class="overflow-hidden rounded-[4px] border border-control-border bg-background"
447
- aria-label={i18n.t("documents:apiActions")}
448
- >
449
- <p
450
- class="border-b border-control-border bg-control px-3 py-2 font-mono text-[9.5px] tracking-[0.12em] text-foreground-faint uppercase"
451
- >
452
- {i18n.t("documents:apiActions")}
453
- </p>
454
- <div class="grid grid-cols-2 gap-px bg-control-border p-px xl:grid-cols-1">
455
- {#each availableActions as action (action.key)}
456
- <button
457
- type="button"
458
- class={[
459
- "flex min-h-10 items-center gap-2.5 bg-background px-3 text-start text-[12.5px] outline-none transition-colors hover:bg-control focus-visible:bg-control",
460
- apiAction === action.key && "bg-control text-foreground-strong",
461
- ]}
462
- onclick={() => (apiAction = action.key)}
463
- aria-current={apiAction === action.key ? "page" : undefined}
464
- >
465
- <span
466
- class={[
467
- "w-12 font-mono text-[9px] font-semibold",
468
- action.method === "GET" && "text-success",
469
- action.method === "POST" && "text-primary",
470
- action.method === "PATCH" && "text-warning",
471
- action.method === "DELETE" && "text-destructive",
472
- ]}
473
- >
474
- {action.method}
475
- </span>
476
- <span>{actionLabel(action.key)}</span>
477
- </button>
478
- {/each}
479
- </div>
480
- </nav>
481
- <Tabs
482
- bind:value={example}
483
- class="overflow-hidden rounded-[4px] border border-control-border"
484
- aria-labelledby="api-example-heading"
485
- >
486
- <div
487
- class="flex flex-wrap items-center justify-between gap-3 border-b border-control-border bg-control px-3 py-2"
488
- >
489
- <TabsList
490
- variant="line"
491
- class="h-auto gap-1 rounded-none border-0 bg-transparent p-0"
492
- aria-label={i18n.t("documents:apiExampleLanguage")}
493
- >
494
- {#each ["fetch", "sdk", "curl"] as option}
495
- <TabsTrigger
496
- value={option}
497
- class={[
498
- "h-auto flex-none rounded-[3px] px-2.5 py-1.5 text-[11.5px] capitalize",
499
- "data-[state=active]:bg-background data-[state=active]:shadow-sm",
500
- ]}
501
- >
502
- {option === "sdk" ? "Ridu SDK" : option}
503
- </TabsTrigger>
504
- {/each}
505
- </TabsList>
506
- <Button variant="ghost" size="xs" onclick={() => copy("example", requestExample)}>
507
- {#if copied === "example"}<CheckIcon class="size-3" />
508
- {i18n.t("documents:copied")}{:else}<CopyIcon class="size-3" />
509
- {i18n.t("documents:copy")}{/if}
510
- </Button>
511
- </div>
512
- <h3 id="api-example-heading" class="sr-only">
513
- {i18n.t("documents:apiRequestExample")}
514
- </h3>
515
- <TabsContent value="fetch" class="mt-0">
516
- <CodeBlock value={requestExampleFor("fetch")} language="javascript" />
517
- </TabsContent>
518
- <TabsContent value="sdk" class="mt-0">
519
- <CodeBlock value={requestExampleFor("sdk")} language="javascript" />
520
- </TabsContent>
521
- <TabsContent value="curl" class="mt-0">
522
- <CodeBlock value={requestExampleFor("curl")} language="bash" />
523
- </TabsContent>
524
- </Tabs>
247
+ {#if referenceCollection}
248
+ {#key routeKey}
249
+ <APIReference collection={referenceCollection} {documentID} />
250
+ {/key}
251
+ {/if}
525
252
 
526
- <details class="group rounded-[4px] border border-control-border px-4 py-3">
527
- <summary class="cursor-pointer text-[12.5px] font-medium text-foreground outline-none">
528
- {i18n.t("documents:apiRequestParameters")}
529
- </summary>
530
- <div
531
- class="mt-3 grid gap-3 border-t border-control-border pt-3 text-[12px] leading-5 text-foreground-muted sm:grid-cols-2"
532
- >
533
- {#if runnableAction}
534
- <p>
535
- <code class="text-foreground">depth</code>
536
- {i18n.t("documents:apiDepthDescription")}
537
- </p>
538
- {/if}
539
- <p>
540
- <code class="text-foreground">authenticated</code>
541
- {i18n.t("documents:apiAuthenticatedDescription")}
542
- </p>
543
- </div>
544
- </details>
545
- </aside>
253
+ {#if requestError}
254
+ <Banner tone="destructive">{requestError}</Banner>
255
+ {/if}
256
+ <span class="ridu-document-api__status" role="status">
257
+ {loading ? i18n.t("documents:apiRequesting") : ""}
258
+ </span>
546
259
  </div>
260
+
261
+ <JsonViewer value={responseValue} bind:expanded class="ridu-document-api__response" />
547
262
  </section>