@riducms/admin 0.2.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (352) hide show
  1. package/THIRD_PARTY_NOTICES.md +60 -37
  2. package/package.json +29 -15
  3. package/src/app/admin-core-view-route.svelte +38 -53
  4. package/src/app/admin-extension-route.svelte +19 -0
  5. package/src/app/admin-layout.scss +175 -0
  6. package/src/app/admin-layout.svelte +144 -410
  7. package/src/app/admin-navigation-progress.svelte +11 -77
  8. package/src/app/admin-page-title.svelte +17 -85
  9. package/src/app/admin-page-title.ts +136 -0
  10. package/src/app/admin-route-error.svelte +24 -0
  11. package/src/app/admin-router-provider.svelte +34 -0
  12. package/src/app/admin-router-root.svelte +41 -0
  13. package/src/app/admin-routes.svelte +56 -19
  14. package/src/app/loading-boundary.svelte +3 -1
  15. package/src/app-meta.ts +13 -0
  16. package/src/app.css +138 -496
  17. package/src/app.svelte +60 -26
  18. package/src/components/brand/ridu-logo.svelte +19 -5
  19. package/src/components/data/list-table.scss +63 -0
  20. package/src/components/icons/LICENSE.md +24 -0
  21. package/src/components/icons/chevron.svelte +8 -0
  22. package/src/components/icons/plus.svelte +12 -0
  23. package/src/components/icons/search.svelte +12 -0
  24. package/src/components/icons/x.svelte +8 -0
  25. package/src/components/json-tree/json-tree-node.svelte +61 -67
  26. package/src/components/json-tree/json-viewer.scss +153 -0
  27. package/src/components/json-tree/json-viewer.svelte +57 -96
  28. package/src/components/ui/banner/banner.scss +28 -0
  29. package/src/components/ui/banner/banner.svelte +9 -8
  30. package/src/components/ui/banner/index.ts +1 -5
  31. package/src/components/ui/code-editor/code-editor.scss +20 -0
  32. package/src/components/ui/code-editor/code-editor.svelte +71 -0
  33. package/src/components/ui/code-editor/code-editor.ts +151 -0
  34. package/src/components/ui/code-editor/code-languages.ts +29 -0
  35. package/src/components/ui/collapsible-panel/collapsible-panel.scss +38 -0
  36. package/src/components/ui/collapsible-panel/collapsible-panel.svelte +13 -0
  37. package/src/components/ui/collapsible-panel/index.ts +1 -0
  38. package/src/components/ui/combobox/combobox.scss +163 -0
  39. package/src/components/ui/combobox/index.ts +22 -0
  40. package/src/components/ui/date-value-control/date-value-calendar.svelte +128 -16
  41. package/src/components/ui/date-value-control/date-value-control-loader.scss +26 -0
  42. package/src/components/ui/date-value-control/date-value-control-loader.svelte +35 -13
  43. package/src/components/ui/date-value-control/date-value-control.scss +223 -0
  44. package/src/components/ui/date-value-control/date-value-control.svelte +62 -44
  45. package/src/components/ui/drawer/drawer-depth.ts +8 -0
  46. package/src/components/ui/dropdown-menu/dropdown-menu-content.svelte +2 -5
  47. package/src/components/ui/dropdown-menu/dropdown-menu-item.svelte +2 -5
  48. package/src/components/ui/dropdown-menu/dropdown-menu-separator.svelte +2 -2
  49. package/src/components/ui/dropdown-menu/dropdown-menu.scss +62 -0
  50. package/src/components/ui/kbd/kbd.scss +17 -0
  51. package/src/components/ui/kbd/kbd.svelte +3 -10
  52. package/src/components/ui/navigation-drawer/navigation-drawer.scss +40 -0
  53. package/src/components/ui/navigation-drawer/navigation-drawer.svelte +38 -0
  54. package/src/components/ui/popover/popover-content.svelte +3 -5
  55. package/src/components/ui/popover/popover.scss +18 -0
  56. package/src/components/ui/radio-group/index.ts +0 -1
  57. package/src/components/ui/radio-group/radio-card-item.svelte +7 -12
  58. package/src/components/ui/radio-group/radio-group.svelte +2 -2
  59. package/src/components/ui/radio-group/radio.scss +45 -0
  60. package/src/components/ui/select/select-content.svelte +5 -6
  61. package/src/components/ui/select/select-item.svelte +8 -8
  62. package/src/components/ui/select/select-scroll-down-button.svelte +3 -5
  63. package/src/components/ui/select/select-scroll-up-button.svelte +3 -5
  64. package/src/components/ui/select/select-trigger.svelte +6 -7
  65. package/src/components/ui/select/select.scss +126 -0
  66. package/src/components/ui/sheet/sheet-content.svelte +6 -34
  67. package/src/components/ui/sheet/sheet-description.svelte +2 -2
  68. package/src/components/ui/sheet/sheet-overlay.svelte +2 -21
  69. package/src/components/ui/sheet/sheet-title.svelte +2 -2
  70. package/src/components/ui/sheet/sheet.scss +221 -0
  71. package/src/components/ui/skeleton/skeleton.scss +20 -0
  72. package/src/components/ui/skeleton/skeleton.svelte +4 -16
  73. package/src/components/ui/sonner/sonner.scss +121 -0
  74. package/src/components/ui/sonner/sonner.svelte +11 -10
  75. package/src/components/ui/status-indicator/status-indicator.scss +48 -0
  76. package/src/components/ui/status-indicator/status-indicator.svelte +14 -14
  77. package/src/components/ui/tabs/tabs-content.svelte +2 -2
  78. package/src/components/ui/tabs/tabs-list.svelte +7 -7
  79. package/src/components/ui/tabs/tabs-trigger.svelte +2 -8
  80. package/src/components/ui/tabs/tabs.scss +86 -0
  81. package/src/components/ui/tabs/tabs.svelte +2 -2
  82. package/src/components/ui/timezone-picker/timezone-picker.svelte +152 -0
  83. package/src/core/api/admin-client.ts +2 -2
  84. package/src/core/bootstrap/admin-bootstrap.ts +396 -0
  85. package/src/core/bootstrap/admin-prepared-state.ts +213 -0
  86. package/src/core/bootstrap/admin-route-modules.ts +66 -0
  87. package/src/core/clipboard/copy-feedback.svelte.ts +43 -0
  88. package/src/core/forms/embedded-fields.ts +4 -8
  89. package/src/core/forms/field-editor-authoring.ts +8 -0
  90. package/src/core/forms/field-issue-focus.ts +4 -4
  91. package/src/core/forms/field-occurrence.ts +4 -7
  92. package/src/core/forms/form-controller.svelte.ts +14 -21
  93. package/src/core/forms/form-draft-recovery.ts +17 -4
  94. package/src/core/forms/form-issue-correlation.ts +13 -13
  95. package/src/core/forms/form-path.ts +17 -0
  96. package/src/core/forms/form-schema.ts +12 -43
  97. package/src/core/forms/form-validation.ts +7 -46
  98. package/src/core/forms/plugin-field-authoring.ts +6 -2
  99. package/src/core/forms/plugin-field-write.ts +2 -6
  100. package/src/core/i18n/admin-i18n.svelte.ts +18 -0
  101. package/src/core/i18n/format-admin-date-time.ts +19 -0
  102. package/src/core/i18n/humanize-identifier.ts +5 -0
  103. package/src/core/i18n/time-zone-label.ts +20 -0
  104. package/src/core/loaders/admin-view-host.svelte +72 -0
  105. package/src/core/loaders/admin-view-loader.svelte.ts +90 -0
  106. package/src/core/notifications/validation-toast.svelte +2 -2
  107. package/src/core/plugins/field-registry.ts +7 -8
  108. package/src/core/routing/admin-paths.ts +53 -2
  109. package/src/core/routing/admin-scroll.svelte.ts +74 -0
  110. package/src/core/routing/admin-view-selection.ts +78 -0
  111. package/src/core/runtime/admin-runtime.svelte.ts +131 -135
  112. package/src/core/schema/field-default.ts +33 -0
  113. package/src/core/schema/field-localization.ts +15 -0
  114. package/src/core/schema/read-document-path.ts +11 -0
  115. package/src/features/account/account-menu.scss +37 -0
  116. package/src/features/account/account-menu.svelte +115 -0
  117. package/src/features/{auth → account}/account-navigation.svelte +16 -19
  118. package/src/features/{auth → account}/account-route.svelte +206 -49
  119. package/src/features/account/account-security-controller.svelte.ts +159 -0
  120. package/src/features/{auth → account}/account-security-route.svelte +140 -42
  121. package/src/features/api-reference/api-reference-code.svelte +114 -0
  122. package/src/features/api-reference/api-reference-content.svelte +258 -0
  123. package/src/features/api-reference/api-reference-drawer.scss +74 -0
  124. package/src/features/api-reference/api-reference-examples.ts +381 -0
  125. package/src/features/api-reference/api-reference-schema.ts +332 -0
  126. package/src/features/api-reference/api-reference.scss +190 -0
  127. package/src/features/api-reference/api-reference.svelte +74 -0
  128. package/src/features/auth/auth-brand.svelte +5 -3
  129. package/src/features/auth/auth-field.svelte +92 -0
  130. package/src/features/auth/auth-frame.svelte +5 -8
  131. package/src/features/auth/auth.scss +122 -0
  132. package/src/features/auth/create-first-user-controller.svelte.ts +30 -60
  133. package/src/features/auth/create-first-user-route.svelte +83 -122
  134. package/src/features/auth/forgot-password-route.svelte +32 -24
  135. package/src/features/auth/login-route.svelte +69 -117
  136. package/src/features/auth/logout-button.scss +34 -0
  137. package/src/features/auth/logout-button.svelte +52 -0
  138. package/src/features/auth/request-verification-route.svelte +28 -23
  139. package/src/features/auth/reset-password-route.svelte +111 -41
  140. package/src/features/auth/verify-email-route.svelte +52 -15
  141. package/src/features/bulk-edit/bulk-edit-fields.ts +23 -0
  142. package/src/features/bulk-edit/bulk-edit.scss +152 -0
  143. package/src/features/bulk-edit/field-picker.svelte +176 -0
  144. package/src/features/collections/bulk/bulk-actions.svelte +148 -0
  145. package/src/features/collections/bulk/bulk-confirmation.svelte +153 -0
  146. package/src/features/collections/bulk/bulk-editor-loader.svelte +95 -0
  147. package/src/features/collections/bulk/bulk-editor.svelte +186 -0
  148. package/src/features/collections/collection-list-cell-values.ts +144 -0
  149. package/src/features/collections/collection-list-controller.svelte.ts +268 -379
  150. package/src/features/collections/collection-list-preferences-controller.svelte.ts +54 -40
  151. package/src/features/collections/collection-list-query.ts +223 -0
  152. package/src/features/collections/collection-list-results.svelte +205 -329
  153. package/src/features/collections/collection-list-route.svelte +143 -884
  154. package/src/features/collections/collection-list.scss +261 -0
  155. package/src/features/collections/collection-list.svelte.ts +362 -0
  156. package/src/features/collections/controls/column-picker.svelte +110 -0
  157. package/src/features/collections/controls/column-pill.svelte +62 -0
  158. package/src/features/collections/controls/filter-builder.svelte +193 -0
  159. package/src/features/collections/controls/filter-row.svelte +142 -0
  160. package/src/features/collections/controls/list-controls.scss +196 -0
  161. package/src/features/collections/controls/list-options.svelte +203 -0
  162. package/src/features/collections/controls/list-pagination.scss +78 -0
  163. package/src/features/collections/controls/list-pagination.svelte +115 -0
  164. package/src/features/collections/controls/list-toolbar.svelte +132 -0
  165. package/src/features/collections/controls/sort-heading.scss +52 -0
  166. package/src/features/collections/controls/sort-heading.svelte +78 -0
  167. package/src/features/collections/list-columns.ts +43 -0
  168. package/src/features/collections/list-workspace.ts +155 -54
  169. package/src/features/dashboard/dashboard-route.svelte +82 -86
  170. package/src/features/dashboard/dashboard.scss +131 -0
  171. package/src/features/development/development-tools.svelte +20 -36
  172. package/src/features/documents/document-action-bar.svelte +438 -0
  173. package/src/features/documents/document-api-view-loader.svelte +17 -6
  174. package/src/features/documents/document-api-view.scss +120 -0
  175. package/src/features/documents/document-api-view.svelte +169 -454
  176. package/src/features/documents/document-controller.svelte.ts +531 -617
  177. package/src/features/documents/document-credentials.scss +37 -0
  178. package/src/features/documents/document-credentials.svelte +72 -0
  179. package/src/features/documents/{document-route-dirty.ts → document-credentials.ts} +25 -0
  180. package/src/features/documents/document-field-sections.scss +39 -0
  181. package/src/features/documents/document-field-sections.svelte +4 -13
  182. package/src/features/documents/document-loading.scss +24 -0
  183. package/src/features/documents/document-lock-controller.svelte.ts +187 -0
  184. package/src/features/documents/document-operation-dialogs.svelte +110 -0
  185. package/src/features/documents/document-route-loader.svelte +17 -5
  186. package/src/features/documents/document-route.svelte +305 -811
  187. package/src/features/documents/document-schedule.scss +127 -0
  188. package/src/features/documents/document-schedule.svelte +323 -0
  189. package/src/features/documents/document-title.ts +25 -0
  190. package/src/features/documents/document.scss +274 -0
  191. package/src/features/documents/live-preview-panel.scss +117 -0
  192. package/src/features/documents/live-preview-panel.svelte +167 -168
  193. package/src/features/documents/live-preview.ts +2 -10
  194. package/src/features/documents/upload-document-preview-loader.svelte +26 -23
  195. package/src/features/localization/content-locale-switcher.scss +43 -0
  196. package/src/features/localization/content-locale-switcher.svelte +24 -17
  197. package/src/features/navigation/admin-breadcrumbs.scss +56 -0
  198. package/src/features/navigation/admin-breadcrumbs.svelte +102 -37
  199. package/src/features/navigation/admin-command-menu.svelte +38 -15
  200. package/src/features/navigation/admin-navigation.scss +133 -0
  201. package/src/features/navigation/admin-navigation.svelte +169 -0
  202. package/src/features/navigation/breadcrumb-context.svelte.ts +15 -0
  203. package/src/features/navigation/navigation-preference.svelte.ts +70 -0
  204. package/src/features/reference-browser/reference-browser-editor.svelte +180 -0
  205. package/src/features/reference-browser/reference-browser-list.scss +113 -0
  206. package/src/features/reference-browser/reference-browser-list.svelte +370 -0
  207. package/src/features/reference-browser/reference-browser-loader.svelte +26 -8
  208. package/src/features/reference-browser/reference-browser-workflow.svelte.ts +336 -239
  209. package/src/features/reference-browser/reference-browser.scss +217 -0
  210. package/src/features/reference-browser/reference-browser.svelte +154 -797
  211. package/src/features/reference-browser/reference-collection-picker.svelte +116 -0
  212. package/src/features/reference-browser/reference-list-query.ts +35 -0
  213. package/src/features/uploads/bulk-upload-controller.svelte.ts +604 -0
  214. package/src/features/uploads/bulk-upload-edit-all.svelte +92 -0
  215. package/src/features/uploads/bulk-upload-editor.svelte +191 -0
  216. package/src/features/uploads/bulk-upload-file-picker.svelte +70 -0
  217. package/src/features/uploads/bulk-upload-remote.svelte +100 -0
  218. package/src/features/uploads/bulk-upload-route-loader.svelte +54 -0
  219. package/src/features/uploads/bulk-upload-route.svelte +172 -592
  220. package/src/features/uploads/bulk-upload-sidebar.svelte +125 -0
  221. package/src/features/uploads/bulk-upload.scss +526 -0
  222. package/src/features/uploads/bulk-upload.ts +105 -14
  223. package/src/features/uploads/crop-surface.ts +150 -0
  224. package/src/features/uploads/image-editor.svelte +231 -0
  225. package/src/features/uploads/rendition-preview.svelte +64 -0
  226. package/src/features/uploads/upload-control.svelte +340 -0
  227. package/src/features/uploads/upload-document-contracts.ts +30 -0
  228. package/src/features/uploads/upload-draft.svelte.ts +294 -0
  229. package/src/features/uploads/upload-metadata-fields.scss +32 -0
  230. package/src/features/uploads/upload-metadata-fields.svelte +8 -7
  231. package/src/features/uploads/upload-thumbnail.svelte +47 -0
  232. package/src/features/uploads/upload.scss +361 -0
  233. package/src/features/versions/version-comparison.svelte +361 -0
  234. package/src/features/versions/version-diff-fields.svelte +136 -0
  235. package/src/features/versions/version-diff.ts +296 -41
  236. package/src/features/versions/version-history-controller.svelte.ts +311 -0
  237. package/src/features/versions/version-history-route-loader.svelte +32 -0
  238. package/src/features/versions/version-history-route.svelte +157 -522
  239. package/src/features/versions/version-history.scss +444 -0
  240. package/src/features/versions/version-history.ts +45 -0
  241. package/src/features/versions/version-table.svelte +91 -0
  242. package/src/fields/embedded-schema-draft-editor.scss +48 -0
  243. package/src/fields/embedded-schema-draft-editor.svelte +24 -16
  244. package/src/fields/field-collapsible.svelte +14 -8
  245. package/src/fields/field-layout.scss +171 -0
  246. package/src/fields/field-layout.svelte +3 -5
  247. package/src/fields/field-renderer.svelte +17 -29
  248. package/src/fields/field-shell.svelte +1 -1
  249. package/src/fields/field-tab-layout.svelte +25 -19
  250. package/src/fields/join/join-field-controller.svelte.ts +513 -0
  251. package/src/fields/join/join-field-values.ts +111 -0
  252. package/src/fields/join/join-field.scss +189 -0
  253. package/src/fields/join/join-field.svelte +164 -332
  254. package/src/fields/live-validation-feedback.svelte +4 -3
  255. package/src/fields/local-field-editor.svelte +2 -2
  256. package/src/fields/nested/block-name-input.svelte +11 -12
  257. package/src/fields/nested/block-picker.scss +128 -0
  258. package/src/fields/nested/block-picker.svelte +81 -0
  259. package/src/fields/nested/block-placeholder.svg +2 -0
  260. package/src/fields/nested/nested-field.scss +331 -0
  261. package/src/fields/nested/nested-field.svelte +265 -245
  262. package/src/fields/nested/sortable-row.svelte +9 -7
  263. package/src/fields/plugin-field.svelte +4 -6
  264. package/src/fields/point/point-field.svelte +51 -38
  265. package/src/fields/primitive-list/primitive-list-field.svelte +63 -19
  266. package/src/fields/primitive-list/primitive-list.scss +109 -0
  267. package/src/fields/radio/radio-field.svelte +2 -1
  268. package/src/fields/relationship/relationship-field-controller.svelte.ts +81 -41
  269. package/src/fields/relationship/relationship-field.svelte +144 -175
  270. package/src/fields/relationship/relationship-lookup-controller.svelte.ts +58 -30
  271. package/src/fields/relationship/relationship-query.ts +2 -10
  272. package/src/fields/relationship/relationship-quick-picker-controller.svelte.ts +36 -62
  273. package/src/fields/relationship/relationship-quick-picker.svelte +178 -227
  274. package/src/fields/relationship/relationship-selection.svelte +120 -0
  275. package/src/fields/relationship/relationship-value.ts +46 -7
  276. package/src/fields/relationship/relationship.scss +182 -0
  277. package/src/fields/scalar/code-field.svelte +101 -0
  278. package/src/fields/scalar/date-control-value.ts +13 -0
  279. package/src/fields/scalar/scalar-field.svelte +25 -67
  280. package/src/fields/select/select-field.svelte +148 -94
  281. package/src/fields/text/slug-field.scss +28 -0
  282. package/src/fields/text/slug-field.svelte +19 -15
  283. package/src/fields/text/text-field.svelte +1 -2
  284. package/src/fields/ui/ui-field.svelte +7 -4
  285. package/src/fields/virtual/virtual-field.svelte +2 -5
  286. package/src/index.ts +1 -2
  287. package/src/routing.ts +2 -0
  288. package/src/app/deferred-redirect.svelte +0 -23
  289. package/src/components/code-block/code-block.svelte +0 -107
  290. package/src/components/ui/bulk-action-bar/bulk-action-bar.svelte +0 -50
  291. package/src/components/ui/bulk-action-bar/index.ts +0 -1
  292. package/src/components/ui/button/index.ts +0 -8
  293. package/src/components/ui/calendar/calendar-caption.svelte +0 -72
  294. package/src/components/ui/calendar/calendar-cell.svelte +0 -20
  295. package/src/components/ui/calendar/calendar-day.svelte +0 -27
  296. package/src/components/ui/calendar/calendar-grid-row.svelte +0 -13
  297. package/src/components/ui/calendar/calendar-grid.svelte +0 -17
  298. package/src/components/ui/calendar/calendar-head-cell.svelte +0 -20
  299. package/src/components/ui/calendar/calendar-header.svelte +0 -20
  300. package/src/components/ui/calendar/calendar-heading.svelte +0 -17
  301. package/src/components/ui/calendar/calendar-month-select.svelte +0 -45
  302. package/src/components/ui/calendar/calendar-month.svelte +0 -16
  303. package/src/components/ui/calendar/calendar-months.svelte +0 -20
  304. package/src/components/ui/calendar/calendar-nav.svelte +0 -20
  305. package/src/components/ui/calendar/calendar-next-button.svelte +0 -30
  306. package/src/components/ui/calendar/calendar-prev-button.svelte +0 -30
  307. package/src/components/ui/calendar/calendar-year-select.svelte +0 -44
  308. package/src/components/ui/calendar/calendar.svelte +0 -118
  309. package/src/components/ui/calendar/index.ts +0 -1
  310. package/src/components/ui/checkbox/checkbox.svelte +0 -35
  311. package/src/components/ui/checkbox/index.ts +0 -1
  312. package/src/components/ui/dialog/dialog-close.svelte +0 -11
  313. package/src/components/ui/dialog/dialog-content.svelte +0 -52
  314. package/src/components/ui/dialog/dialog-description.svelte +0 -20
  315. package/src/components/ui/dialog/dialog-footer.svelte +0 -20
  316. package/src/components/ui/dialog/dialog-header.svelte +0 -20
  317. package/src/components/ui/dialog/dialog-overlay.svelte +0 -20
  318. package/src/components/ui/dialog/dialog-title.svelte +0 -17
  319. package/src/components/ui/dialog/dialog-trigger.svelte +0 -11
  320. package/src/components/ui/dialog/dialog.svelte +0 -7
  321. package/src/components/ui/dialog/index.ts +0 -8
  322. package/src/components/ui/empty-state/empty-state.svelte +0 -34
  323. package/src/components/ui/empty-state/index.ts +0 -1
  324. package/src/components/ui/input/index.ts +0 -1
  325. package/src/components/ui/pagination/index.ts +0 -1
  326. package/src/components/ui/pagination/pagination.svelte +0 -88
  327. package/src/components/ui/radio-group/radio-group-item.svelte +0 -17
  328. package/src/components/ui/slider/index.ts +0 -3
  329. package/src/components/ui/slider/slider-content.svelte +0 -59
  330. package/src/components/ui/slider/slider.svelte +0 -35
  331. package/src/components/ui/table/index.ts +0 -6
  332. package/src/components/ui/table/table-body.svelte +0 -20
  333. package/src/components/ui/table/table-cell.svelte +0 -23
  334. package/src/components/ui/table/table-head.svelte +0 -23
  335. package/src/components/ui/table/table-header.svelte +0 -20
  336. package/src/components/ui/table/table-row.svelte +0 -23
  337. package/src/components/ui/table/table.svelte +0 -22
  338. package/src/components/ui/textarea/index.ts +0 -1
  339. package/src/components/ui/tooltip/index.ts +0 -4
  340. package/src/components/ui/tooltip/tooltip-content.svelte +0 -50
  341. package/src/components/ui/tooltip/tooltip-provider.svelte +0 -7
  342. package/src/components/ui/tooltip/tooltip-trigger.svelte +0 -7
  343. package/src/components/ui/tooltip/tooltip.svelte +0 -7
  344. package/src/features/auth/account-menu.svelte +0 -159
  345. package/src/features/auth/account-security-controller.svelte.ts +0 -119
  346. package/src/features/collections/collection-list-bulk-actions.svelte +0 -143
  347. package/src/features/collections/collection-list-bulk-editor.svelte +0 -157
  348. package/src/features/collections/collection-list-filter-popover.svelte +0 -238
  349. package/src/features/collections/collection-list-workspace-toolbar.svelte +0 -409
  350. package/src/features/collections/collection-table-skeleton.svelte +0 -44
  351. package/src/features/documents/document-heading.ts +0 -20
  352. package/src/features/documents/upload-document-preview.svelte +0 -525
@@ -6,12 +6,12 @@
6
6
  type RiduLivePreviewUpdateMessage,
7
7
  } from "@riducms/sdk";
8
8
  import ExternalLinkIcon from "~icons/lucide/external-link";
9
- import XIcon from "~icons/lucide/x";
10
- import { TooltipContent, TooltipRoot, TooltipTrigger } from "@riducms/ui";
9
+ import { TooltipContent, TooltipRoot, TooltipTrigger, Button, Input } from "@riducms/ui";
10
+
11
+ import "@admin/features/documents/live-preview-panel.scss";
11
12
 
12
- import { Button, buttonVariants } from "@admin/components/ui/button";
13
13
  import { Banner } from "@admin/components/ui/banner";
14
- import { Input } from "@admin/components/ui/input";
14
+
15
15
  import { Select, SelectContent, SelectItem, SelectTrigger } from "@admin/components/ui/select";
16
16
  import type { FormValues } from "@admin/core/forms/form-schema";
17
17
  import { getAdminRuntime } from "@admin/core/runtime/admin-runtime.svelte";
@@ -28,21 +28,21 @@
28
28
  documentID,
29
29
  resource,
30
30
  values,
31
- onclose,
32
31
  }: {
33
32
  preview: SchemaLivePreview;
34
33
  collection: string;
35
34
  documentID: string;
36
35
  resource: "collection" | "global";
37
36
  values: FormValues;
38
- onclose: () => void;
39
37
  } = $props();
40
38
 
41
39
  const runtime = getAdminRuntime();
42
40
  let iframe = $state<HTMLIFrameElement>();
43
41
  let viewport = $state("responsive");
44
- let responsiveWidth = $state(768);
45
- let responsiveHeight = $state(720);
42
+ let availableWidth = $state(768);
43
+ let availableHeight = $state(720);
44
+ let customWidth = $state(768);
45
+ let customHeight = $state(720);
46
46
  let zoom = $state(100);
47
47
  let iframeReady = $state(false);
48
48
  let popupReady = false;
@@ -52,12 +52,41 @@
52
52
  let capability = $state<PreviewToken>();
53
53
  let capabilityError = $state<string>();
54
54
  let capabilityRevision = $state(0);
55
+ let activeCapabilityToken: string | undefined;
56
+ let activeCapabilityOwner: string | undefined;
55
57
  const channel = crypto.randomUUID();
56
58
  const selectedBreakpoint = $derived(
57
- preview.breakpoints?.find((breakpoint) => breakpoint.name === viewport)
59
+ preview.breakpoints?.find((breakpoint) => `breakpoint:${breakpoint.name}` === viewport)
58
60
  );
59
- const frameWidth = $derived(selectedBreakpoint?.width ?? responsiveWidth);
60
- const frameHeight = $derived(selectedBreakpoint?.height ?? responsiveHeight);
61
+ const frameWidth = $derived(
62
+ Math.max(
63
+ 1,
64
+ Math.round(
65
+ selectedBreakpoint?.width ?? (viewport === "custom" ? customWidth : availableWidth)
66
+ )
67
+ )
68
+ );
69
+ const frameHeight = $derived(
70
+ Math.max(
71
+ 1,
72
+ Math.round(
73
+ selectedBreakpoint?.height ?? (viewport === "custom" ? customHeight : availableHeight)
74
+ )
75
+ )
76
+ );
77
+ const viewportLabel = $derived(
78
+ selectedBreakpoint
79
+ ? runtime.i18n.text(selectedBreakpoint.label, selectedBreakpoint.labelTranslations)
80
+ : runtime.i18n.t(viewport === "custom" ? "documents:customViewport" : "documents:responsive")
81
+ );
82
+
83
+ function resizeViewport(axis: "width" | "height", event: Event) {
84
+ const value = Number((event.currentTarget as HTMLInputElement).value);
85
+ if (!Number.isFinite(value) || value < 1 || value > 3840) return;
86
+ customWidth = axis === "width" ? value : frameWidth;
87
+ customHeight = axis === "height" ? value : frameHeight;
88
+ viewport = "custom";
89
+ }
61
90
  const previewURL = $derived(
62
91
  capability === undefined
63
92
  ? undefined
@@ -169,25 +198,18 @@
169
198
  $effect(() => {
170
199
  capabilityRevision;
171
200
  const controller = new AbortController();
172
- void authorizePreview(controller.signal);
201
+ const owner = `${resource}:${collection}:${documentID}`;
202
+ if (activeCapabilityOwner !== owner) releaseCapability();
203
+ void authorizePreview(owner, controller.signal);
173
204
  return () => controller.abort();
174
205
  });
175
206
 
176
- $effect(() => {
177
- const token = capability?.token;
178
- if (token === undefined) return;
179
- return () => {
180
- void runtime.client.revokePreviewToken(token, { keepalive: true }).catch(() => undefined);
181
- };
182
- });
183
-
184
- $effect(() => {
185
- return () => {
186
- if (popupWindow !== null && !popupWindow.closed) popupWindow.close();
187
- popupWindow = null;
188
- popupReady = false;
189
- popupCapabilityToken = undefined;
190
- };
207
+ $effect(() => () => {
208
+ releaseCapability();
209
+ if (popupWindow !== null && !popupWindow.closed) popupWindow.close();
210
+ popupWindow = null;
211
+ popupReady = false;
212
+ popupCapabilityToken = undefined;
191
213
  });
192
214
 
193
215
  $effect(() => {
@@ -197,35 +219,50 @@
197
219
  return () => window.clearTimeout(timeout);
198
220
  });
199
221
 
200
- async function authorizePreview(signal: AbortSignal) {
222
+ async function authorizePreview(owner: string, signal: AbortSignal) {
201
223
  capabilityError = undefined;
202
224
  try {
203
- capability =
225
+ const nextCapability =
204
226
  resource === "global"
205
227
  ? await runtime.client.createGlobalPreviewToken(collection, { signal })
206
228
  : await runtime.client.createPreviewToken(collection, documentID, { signal });
229
+ if (signal.aborted) {
230
+ revokeCapability(nextCapability.token);
231
+ return;
232
+ }
233
+ const previousToken = activeCapabilityToken;
234
+ activeCapabilityToken = nextCapability.token;
235
+ activeCapabilityOwner = owner;
236
+ capability = nextCapability;
237
+ if (previousToken !== undefined && previousToken !== nextCapability.token) {
238
+ revokeCapability(previousToken);
239
+ }
207
240
  } catch (cause) {
208
241
  if (signal.aborted) return;
209
- capability = undefined;
242
+ releaseCapability();
210
243
  capabilityError =
211
244
  cause instanceof Error
212
245
  ? cause.message
213
246
  : runtime.i18n.t("documents:previewAuthorizationFailed");
214
247
  }
215
248
  }
249
+
250
+ function releaseCapability() {
251
+ const token = activeCapabilityToken;
252
+ activeCapabilityToken = undefined;
253
+ activeCapabilityOwner = undefined;
254
+ capability = undefined;
255
+ if (token !== undefined) revokeCapability(token);
256
+ }
257
+
258
+ function revokeCapability(token: string) {
259
+ void runtime.client.revokePreviewToken(token, { keepalive: true }).catch(() => undefined);
260
+ }
216
261
  </script>
217
262
 
218
- <section
219
- class="flex min-h-[540px] min-w-0 flex-1 flex-col border-t border-control-border bg-background-layer min-[1240px]:min-h-0 min-[1240px]:border-t-0 min-[1240px]:border-s"
220
- aria-label={runtime.i18n.t("documents:livePreview")}
221
- >
222
- <div
223
- class="flex min-h-12 flex-wrap items-center gap-1.5 border-b border-control-border px-3 py-2"
224
- >
225
- <span
226
- class="font-mono me-1 text-[9px] tracking-[0.08em] text-foreground-faint uppercase"
227
- role="status"
228
- >
263
+ <section class="ridu-live-preview" aria-label={runtime.i18n.t("documents:livePreview")}>
264
+ <div class="ridu-live-preview__toolbar">
265
+ <span class="ridu-live-preview__status" role="status">
229
266
  {capabilityError !== undefined
230
267
  ? runtime.i18n.t("documents:previewUnavailable")
231
268
  : capability === undefined
@@ -234,154 +271,116 @@
234
271
  ? runtime.i18n.t("documents:previewConnected")
235
272
  : runtime.i18n.t("documents:previewConnecting")}
236
273
  </span>
237
- <div class="flex items-center gap-1" aria-label={runtime.i18n.t("documents:previewViewport")}>
238
- <Button
239
- variant="ghost"
240
- size="sm"
241
- class={[
242
- "h-7 px-2.5 text-[11px]",
243
- viewport === "responsive" && "bg-control-hover text-foreground",
244
- ]}
245
- onclick={() => (viewport = "responsive")}
274
+ <Select type="single" bind:value={viewport}>
275
+ <SelectTrigger
276
+ class="ridu-live-preview__select"
277
+ aria-label={runtime.i18n.t("documents:previewViewport")}
246
278
  >
247
- {runtime.i18n.t("documents:responsive")}
248
- </Button>
249
- {#each preview.breakpoints ?? [] as breakpoint (breakpoint.name)}
250
- <Button
251
- variant="ghost"
252
- size="sm"
253
- class={[
254
- "h-7 px-2.5 text-[11px]",
255
- viewport === breakpoint.name && "bg-control-hover text-foreground",
256
- ]}
257
- onclick={() => (viewport = breakpoint.name)}
258
- >
259
- {runtime.i18n.text(breakpoint.label, breakpoint.labelTranslations)}
260
- </Button>
261
- {/each}
279
+ {viewportLabel}
280
+ </SelectTrigger>
281
+ <SelectContent class="ridu-live-preview-menu" align="end">
282
+ {#each preview.breakpoints ?? [] as breakpoint (breakpoint.name)}
283
+ <SelectItem
284
+ value={`breakpoint:${breakpoint.name}`}
285
+ label={runtime.i18n.text(breakpoint.label, breakpoint.labelTranslations)}
286
+ />
287
+ {/each}
288
+ <SelectItem value="responsive" label={runtime.i18n.t("documents:responsive")} />
289
+ {#if viewport === "custom"}
290
+ <SelectItem value="custom" label={runtime.i18n.t("documents:customViewport")} />
291
+ {/if}
292
+ </SelectContent>
293
+ </Select>
294
+
295
+ <div class="ridu-live-preview__dimensions">
296
+ <Input
297
+ class="ridu-live-preview__size"
298
+ type="number"
299
+ min="1"
300
+ max="3840"
301
+ value={frameWidth}
302
+ aria-label={runtime.i18n.t("documents:previewWidth")}
303
+ oninput={(event) => resizeViewport("width", event)}
304
+ />
305
+ <span aria-hidden="true">×</span>
306
+ <Input
307
+ class="ridu-live-preview__size"
308
+ type="number"
309
+ min="1"
310
+ max="3840"
311
+ value={frameHeight}
312
+ aria-label={runtime.i18n.t("documents:previewHeight")}
313
+ oninput={(event) => resizeViewport("height", event)}
314
+ />
262
315
  </div>
263
- <div class="ms-auto flex items-center gap-1.5">
264
- <div class="font-mono flex items-center gap-1 text-[9.5px] text-foreground-faint">
265
- <label>
266
- <span class="sr-only">{runtime.i18n.t("documents:previewWidth")}</span>
267
- {#if selectedBreakpoint !== undefined}
268
- <Input
269
- class="h-7 w-15 px-1.5 text-center text-[10px]"
270
- type="number"
271
- value={frameWidth}
272
- disabled
273
- />
274
- {:else}
275
- <Input
276
- class="h-7 w-15 px-1.5 text-center text-[10px]"
277
- type="number"
278
- min="240"
279
- max="3840"
280
- bind:value={responsiveWidth}
281
- />
282
- {/if}
283
- </label>
284
- ×
285
- <label>
286
- <span class="sr-only">{runtime.i18n.t("documents:previewHeight")}</span>
287
- {#if selectedBreakpoint !== undefined}
288
- <Input
289
- class="h-7 w-15 px-1.5 text-center text-[10px]"
290
- type="number"
291
- value={frameHeight}
292
- disabled
293
- />
294
- {:else}
295
- <Input
296
- class="h-7 w-15 px-1.5 text-center text-[10px]"
297
- type="number"
298
- min="240"
299
- max="3840"
300
- bind:value={responsiveHeight}
301
- />
302
- {/if}
303
- </label>
304
- </div>
305
- <label>
306
- <span class="sr-only">{runtime.i18n.t("documents:previewZoom")}</span>
307
- <Select type="single" value={String(zoom)} onValueChange={(next) => (zoom = Number(next))}>
308
- <SelectTrigger
309
- size="toolbar"
310
- class="w-18 text-[10px]"
311
- aria-label={runtime.i18n.t("documents:previewZoom")}
312
- >
313
- {runtime.i18n.formatNumber(zoom / 100, { style: "percent" })}
314
- </SelectTrigger>
315
- <SelectContent>
316
- {#each [50, 75, 100] as percentage}
317
- <SelectItem
318
- value={String(percentage)}
319
- label={runtime.i18n.formatNumber(percentage / 100, { style: "percent" })}
320
- />
321
- {/each}
322
- </SelectContent>
323
- </Select>
324
- </label>
325
- {#if previewURL !== undefined}
326
- <TooltipRoot>
327
- <TooltipTrigger>
328
- {#snippet child({ props })}
329
- <a
330
- {...props}
331
- class={buttonVariants({ variant: "ghost", size: "icon-sm" })}
332
- href={previewURL}
333
- target="_blank"
334
- rel="opener"
335
- aria-label={runtime.i18n.t("documents:openPreviewWindow")}
336
- onclick={openPreviewWindow}
337
- >
338
- <ExternalLinkIcon class="size-3.5" />
339
- </a>
340
- {/snippet}
341
- </TooltipTrigger>
342
- <TooltipContent>{runtime.i18n.t("documents:openPreviewWindow")}</TooltipContent>
343
- </TooltipRoot>
344
- {/if}
345
- <Button
346
- variant="ghost"
347
- size="icon-sm"
348
- onclick={onclose}
349
- aria-label={runtime.i18n.t("documents:closeLivePreview")}
350
- tooltip={runtime.i18n.t("documents:closeLivePreview")}
316
+ <Select type="single" value={String(zoom)} onValueChange={(next) => (zoom = Number(next))}>
317
+ <SelectTrigger
318
+ class="ridu-live-preview__select"
319
+ aria-label={runtime.i18n.t("documents:previewZoom")}
351
320
  >
352
- <XIcon class="size-3.5" />
353
- </Button>
354
- </div>
321
+ {runtime.i18n.formatNumber(zoom / 100, { style: "percent" })}
322
+ </SelectTrigger>
323
+ <SelectContent class="ridu-live-preview-menu" align="end">
324
+ {#each [50, 75, 100, 125, 150, 200] as percentage}
325
+ <SelectItem
326
+ value={String(percentage)}
327
+ label={runtime.i18n.formatNumber(percentage / 100, { style: "percent" })}
328
+ />
329
+ {/each}
330
+ </SelectContent>
331
+ </Select>
332
+ {#if previewURL !== undefined}
333
+ <TooltipRoot>
334
+ <TooltipTrigger>
335
+ {#snippet child({ props })}
336
+ <a
337
+ {...props}
338
+ class="ridu-live-preview__external"
339
+ href={previewURL}
340
+ target="_blank"
341
+ rel="opener"
342
+ aria-label={runtime.i18n.t("documents:openPreviewWindow")}
343
+ onclick={openPreviewWindow}
344
+ >
345
+ <ExternalLinkIcon />
346
+ </a>
347
+ {/snippet}
348
+ </TooltipTrigger>
349
+ <TooltipContent>{runtime.i18n.t("documents:openPreviewWindow")}</TooltipContent>
350
+ </TooltipRoot>
351
+ {/if}
355
352
  </div>
356
- <div class="min-h-0 flex-1 overflow-auto p-4" data-preview-viewport={viewport}>
353
+
354
+ <div
355
+ class="ridu-live-preview__viewport"
356
+ data-preview-viewport={viewport}
357
+ bind:clientWidth={availableWidth}
358
+ bind:clientHeight={availableHeight}
359
+ >
357
360
  {#if capabilityError !== undefined}
358
- <Banner class="mx-auto mt-8 max-w-lg" tone="destructive">
359
- <div class="flex flex-1 items-center justify-between gap-4">
360
- <span>{capabilityError}</span>
361
- <Button variant="outline" size="sm" onclick={() => capabilityRevision++}>
362
- {runtime.i18n.t("documents:retryPreview")}
363
- </Button>
364
- </div>
361
+ <Banner class="ridu-live-preview__error" tone="destructive">
362
+ <span>{capabilityError}</span>
363
+ <Button variant="outline" size="sm" onclick={() => capabilityRevision++}>
364
+ {runtime.i18n.t("documents:retryPreview")}
365
+ </Button>
365
366
  </Banner>
366
367
  {:else if previewURL === undefined}
367
- <div class="grid min-h-72 place-items-center text-[12px] text-foreground-faint">
368
+ <div class="ridu-live-preview__loading">
368
369
  {runtime.i18n.t("documents:authorizingDraftPreview")}
369
370
  </div>
370
371
  {:else}
371
372
  <div
372
- class="mx-auto overflow-hidden rounded-[4px] border border-control-border bg-preview-canvas shadow-lg"
373
+ class="ridu-live-preview__frame"
373
374
  style:width="{frameWidth * (zoom / 100)}px"
374
375
  style:height="{frameHeight * (zoom / 100)}px"
375
376
  >
376
377
  <iframe
377
378
  bind:this={iframe}
378
- class="block border-0 bg-preview-canvas"
379
379
  src={previewURL}
380
380
  title={runtime.i18n.t("documents:livePreview")}
381
381
  width={frameWidth}
382
382
  height={frameHeight}
383
383
  style:transform="scale({zoom / 100})"
384
- style:transform-origin="top left"
385
384
  onload={handleLoad}
386
385
  ></iframe>
387
386
  </div>
@@ -2,6 +2,7 @@ import type { SchemaLivePreview } from "@riducms/protocol";
2
2
  import { RIDU_LIVE_PREVIEW_CHANNEL_PARAM, RIDU_PREVIEW_TOKEN_PARAM } from "@riducms/sdk";
3
3
 
4
4
  import type { FormValues } from "@admin/core/forms/form-schema";
5
+ import { readDocumentPath } from "@admin/core/schema/read-document-path";
5
6
 
6
7
  export interface LivePreviewContext {
7
8
  collection: string;
@@ -22,7 +23,7 @@ export function resolveLivePreviewURL(
22
23
  ? context.documentID
23
24
  : token === "collection"
24
25
  ? context.collection
25
- : readValue(context.values, fieldPath ?? "");
26
+ : readDocumentPath(context.values, fieldPath ?? "");
26
27
  return encodeURIComponent(stringValue(value));
27
28
  });
28
29
  return new URL(path, baseURL).toString();
@@ -44,15 +45,6 @@ export function addLivePreviewToken(url: string, token: string) {
44
45
  return target.toString();
45
46
  }
46
47
 
47
- function readValue(values: FormValues, path: string) {
48
- let current: unknown = values;
49
- for (const segment of path.split(".")) {
50
- if (current === null || typeof current !== "object" || Array.isArray(current)) return undefined;
51
- current = (current as Record<string, unknown>)[segment];
52
- }
53
- return current;
54
- }
55
-
56
48
  function stringValue(value: unknown) {
57
49
  if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
58
50
  return String(value);
@@ -1,40 +1,43 @@
1
1
  <script lang="ts">
2
2
  import { getAdminI18n } from "@riducms/plugin";
3
- import type { AdminDocument } from "@admin/core/api/admin-client";
4
- import type { Component } from "svelte";
3
+ import { Button } from "@riducms/ui";
4
+ import type { UploadDraft } from "@admin/features/uploads/upload-draft.svelte";
5
+ import type { SchemaUploadSettings } from "@riducms/protocol";
6
+ import { preparedAdminModule } from "@admin/core/bootstrap/admin-route-modules";
5
7
 
6
- type ImageEditInput = {
7
- focalX: number;
8
- focalY: number;
9
- cropX: number;
10
- cropY: number;
11
- cropWidth: number;
12
- cropHeight: number;
13
- };
14
8
  type UploadDocumentPreviewProps = {
15
- document: AdminDocument;
9
+ draft: UploadDraft;
10
+ settings: SchemaUploadSettings;
16
11
  editable?: boolean;
17
- updating?: boolean;
18
- onUpdate?: (input: ImageEditInput) => Promise<boolean>;
19
12
  };
20
13
 
21
14
  let props: UploadDocumentPreviewProps = $props();
22
15
  const i18n = getAdminI18n();
23
- let Preview = $state<Component<UploadDocumentPreviewProps> | undefined>(undefined);
16
+ const preview =
17
+ preparedAdminModule<typeof import("@admin/features/uploads/upload-control.svelte")>(
18
+ "upload-preview"
19
+ ) ?? import("@admin/features/uploads/upload-control.svelte");
24
20
 
25
- void import("@admin/features/documents/upload-document-preview.svelte").then(
26
- ({ default: component }) => (Preview = component)
27
- );
21
+ import "@admin/features/documents/document-loading.scss";
28
22
  </script>
29
23
 
30
- {#if Preview !== undefined}
31
- <Preview {...props} />
32
- {:else}
24
+ {#await preview}
33
25
  <section
34
- class="grid min-h-48 place-items-center"
26
+ class="ridu-document-module ridu-document-module--upload"
27
+ data-ridu-loading-surface="upload-preview-module"
35
28
  aria-busy="true"
36
29
  aria-label={i18n.t("uploads:loadingAssetPreview")}
37
30
  >
38
- <p class="text-[12px] text-foreground-faint">{i18n.t("uploads:loadingAssetPreview")}</p>
31
+ <p class="ridu-document-module__message">{i18n.t("uploads:loadingAssetPreview")}</p>
32
+ </section>
33
+ {:then { default: Preview }}
34
+ <Preview {...props} />
35
+ {:catch}
36
+ <section
37
+ class="ridu-document-module ridu-document-module--upload ridu-document-module--error"
38
+ role="alert"
39
+ >
40
+ <p>{i18n.t("uploads:previewLoadFailed")}</p>
41
+ <Button onclick={() => window.location.reload()}>{i18n.t("general:reloadPage")}</Button>
39
42
  </section>
40
- {/if}
43
+ {/await}
@@ -0,0 +1,43 @@
1
+ @use "@riducms/ui/styles.scss" as ui;
2
+
3
+ @layer ridu.components {
4
+ .ridu-content-locale {
5
+ display: flex;
6
+ align-items: center;
7
+ gap: 3px;
8
+ padding: 0 6px;
9
+ border: 0;
10
+ border-radius: 2px;
11
+ background: var(--background-layer);
12
+ color: var(--foreground);
13
+ font: inherit;
14
+ line-height: 20px;
15
+ cursor: pointer;
16
+ white-space: nowrap;
17
+
18
+ &:hover {
19
+ background: var(--control-border);
20
+ }
21
+
22
+ &:focus-visible {
23
+ @include ui.focus-outline;
24
+ }
25
+
26
+ &:disabled {
27
+ opacity: 0.5;
28
+ cursor: not-allowed;
29
+ }
30
+ }
31
+ .ridu-content-locale__label {
32
+ color: var(--foreground-muted);
33
+ }
34
+ .ridu-content-locale__chevron {
35
+ width: 12px;
36
+ height: 12px;
37
+ }
38
+ @media (max-width: ui.$small) {
39
+ .ridu-content-locale__label {
40
+ display: none;
41
+ }
42
+ }
43
+ }
@@ -3,7 +3,7 @@
3
3
  import CheckIcon from "~icons/lucide/check";
4
4
  import ChevronDownIcon from "~icons/lucide/chevron-down";
5
5
 
6
- import { buttonVariants } from "@admin/components/ui/button";
6
+ import "@admin/features/localization/content-locale-switcher.scss";
7
7
  import {
8
8
  DropdownMenu,
9
9
  DropdownMenuContent,
@@ -16,48 +16,55 @@
16
16
  const runtime = getAdminRuntime();
17
17
  const notifications = getNotificationCenter();
18
18
  const navigate = useNavigate();
19
- const location = $derived(useLocation());
19
+ const location = useLocation();
20
+
20
21
  const localization = $derived(runtime.manifest?.application.localization);
21
22
  const locale = $derived(
22
23
  localization?.locales.find((candidate) => candidate.code === runtime.contentLocale)
23
24
  );
24
- const disabled = $derived(runtime.contentLocaleSwitchBlocked);
25
+ const disabled = $derived(
26
+ runtime.contentLocaleSwitchBlocked || runtime.contentLocaleTransitioning
27
+ );
25
28
 
26
29
  $effect(() => {
27
- const requested = new URLSearchParams(location.search).get("locale");
30
+ const requested = new URLSearchParams(location.current.search).get("locale");
28
31
  if (
29
32
  requested !== null &&
30
33
  localization?.locales.some((candidate) => candidate.code === requested) === true &&
31
34
  requested !== runtime.contentLocale
32
35
  ) {
33
- if (!runtime.adoptContentLocale(requested)) updateRouteLocale(runtime.contentLocale);
36
+ updateRouteLocale(runtime.contentLocale);
34
37
  }
35
38
  });
36
39
 
37
40
  async function selectLocale(nextLocale: string) {
38
41
  if (disabled || nextLocale === runtime.contentLocale) return;
39
- const previousLocale = runtime.contentLocale;
40
- updateRouteLocale(nextLocale);
42
+
43
+ runtime.contentLocaleTransitioning = true;
44
+
41
45
  try {
42
- await runtime.setContentLocale(nextLocale);
46
+ await runtime.persistContentLocalePreference(nextLocale);
47
+ await updateRouteLocale(nextLocale);
43
48
  } catch (cause) {
44
- updateRouteLocale(runtime.contentLocale ?? previousLocale);
45
49
  notifications.error({
46
50
  title: runtime.i18n.t("errors:save"),
47
51
  message: cause instanceof Error ? cause.message : undefined,
48
52
  });
53
+ } finally {
54
+ runtime.contentLocaleTransitioning = false;
49
55
  }
50
56
  }
51
57
 
52
- function updateRouteLocale(nextLocale: string | undefined) {
53
- const search = new URLSearchParams(location.search);
58
+ async function updateRouteLocale(nextLocale: string | undefined) {
59
+ const search = new URLSearchParams(location.current.search);
54
60
  if (nextLocale === undefined) search.delete("locale");
55
61
  else search.set("locale", nextLocale);
56
- navigate(
62
+
63
+ await navigate(
57
64
  {
58
- pathname: location.pathname,
65
+ pathname: location.current.pathname,
59
66
  search: search.size === 0 ? "" : `?${search}`,
60
- hash: location.hash,
67
+ hash: location.current.hash,
61
68
  },
62
69
  { replace: true }
63
70
  );
@@ -67,16 +74,16 @@
67
74
  {#if localization !== undefined && localization.locales.length > 1 && locale !== undefined}
68
75
  <DropdownMenu>
69
76
  <DropdownMenuTrigger
70
- class={buttonVariants({ variant: "ghost", size: "sm", class: "gap-1.5 px-2.5" })}
77
+ class="ridu-content-locale"
71
78
  {disabled}
72
79
  aria-label={runtime.i18n.t("documents:contentLocale")}
73
80
  title={disabled ? runtime.i18n.t("documents:saveBeforeLocaleSwitch") : undefined}
74
81
  >
75
- <span class="hidden text-foreground-faint sm:inline">
82
+ <span class="ridu-content-locale__label">
76
83
  {runtime.i18n.t("documents:locale")}:
77
84
  </span>
78
85
  <span>{locale.label}</span>
79
- <ChevronDownIcon class="size-3 text-foreground-faint" aria-hidden="true" />
86
+ <ChevronDownIcon class="ridu-content-locale__chevron" aria-hidden="true" />
80
87
  </DropdownMenuTrigger>
81
88
  <DropdownMenuContent class="min-w-48">
82
89
  {#each localization.locales as candidate (candidate.code)}