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