@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
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export type CopyFeedbackResult = Readonly<{
|
|
2
|
+
source: string;
|
|
3
|
+
copied: boolean;
|
|
4
|
+
}>;
|
|
5
|
+
|
|
6
|
+
/** Own short-lived clipboard feedback for a component instance. */
|
|
7
|
+
export class CopyFeedback {
|
|
8
|
+
result = $state.raw<CopyFeedbackResult>();
|
|
9
|
+
|
|
10
|
+
#active = true;
|
|
11
|
+
#revision = 0;
|
|
12
|
+
#timer: number | undefined;
|
|
13
|
+
|
|
14
|
+
constructor(private readonly duration = 1_500) {
|
|
15
|
+
$effect(() => () => {
|
|
16
|
+
this.#active = false;
|
|
17
|
+
this.#revision += 1;
|
|
18
|
+
if (this.#timer !== undefined) window.clearTimeout(this.#timer);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async copy(source: string) {
|
|
23
|
+
const revision = ++this.#revision;
|
|
24
|
+
if (this.#timer !== undefined) window.clearTimeout(this.#timer);
|
|
25
|
+
this.#timer = undefined;
|
|
26
|
+
this.result = undefined;
|
|
27
|
+
|
|
28
|
+
let copied = true;
|
|
29
|
+
try {
|
|
30
|
+
await navigator.clipboard.writeText(source);
|
|
31
|
+
} catch {
|
|
32
|
+
copied = false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!this.#active || revision !== this.#revision) return;
|
|
36
|
+
this.result = { source, copied };
|
|
37
|
+
this.#timer = window.setTimeout(() => {
|
|
38
|
+
if (!this.#active || revision !== this.#revision) return;
|
|
39
|
+
this.result = undefined;
|
|
40
|
+
this.#timer = undefined;
|
|
41
|
+
}, this.duration);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveBlockTypes } from "@riducms/protocol";
|
|
1
|
+
import { isRecord, resolveBlockTypes } from "@riducms/protocol";
|
|
2
2
|
import type {
|
|
3
3
|
SchemaBlockType,
|
|
4
4
|
SchemaEmbeddedTree,
|
|
@@ -32,7 +32,7 @@ export function embeddedOccurrences(field: SchemaField, value: unknown, path = f
|
|
|
32
32
|
let root: unknown = value;
|
|
33
33
|
let rootPath = path;
|
|
34
34
|
for (const segment of tree.root) {
|
|
35
|
-
root =
|
|
35
|
+
root = isRecord(root) ? root[segment] : undefined;
|
|
36
36
|
rootPath += `.${segment}`;
|
|
37
37
|
}
|
|
38
38
|
const keys = new Set<string>();
|
|
@@ -41,7 +41,7 @@ export function embeddedOccurrences(field: SchemaField, value: unknown, path = f
|
|
|
41
41
|
fail(nodePath, "Embedded field traversal budget exceeded", "embedded_budget");
|
|
42
42
|
return;
|
|
43
43
|
}
|
|
44
|
-
if (!
|
|
44
|
+
if (!isRecord(node)) {
|
|
45
45
|
fail(nodePath, "Embedded tree node must be an object");
|
|
46
46
|
return;
|
|
47
47
|
}
|
|
@@ -49,7 +49,7 @@ export function embeddedOccurrences(field: SchemaField, value: unknown, path = f
|
|
|
49
49
|
if (branch !== undefined) {
|
|
50
50
|
const payload = node[branch.payload];
|
|
51
51
|
const payloadPath = `${nodePath}.${branch.payload}`;
|
|
52
|
-
if (!
|
|
52
|
+
if (!isRecord(payload)) {
|
|
53
53
|
fail(payloadPath, "Embedded payload must be an object");
|
|
54
54
|
} else {
|
|
55
55
|
const block = resolveBlockTypes(branch).find(
|
|
@@ -126,7 +126,3 @@ export function transformEmbeddedPayloads(
|
|
|
126
126
|
}
|
|
127
127
|
return value;
|
|
128
128
|
}
|
|
129
|
-
|
|
130
|
-
export function isEmbeddedRecord(value: unknown): value is Record<string, unknown> {
|
|
131
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
132
|
-
}
|
|
@@ -22,6 +22,10 @@ export function guardEditorAuthoring(
|
|
|
22
22
|
return document;
|
|
23
23
|
});
|
|
24
24
|
};
|
|
25
|
+
const canCreateDocument: EditorAuthoring["canCreateDocument"] = (collection) => {
|
|
26
|
+
assertActive();
|
|
27
|
+
return authoring().canCreateDocument(collection);
|
|
28
|
+
};
|
|
25
29
|
return {
|
|
26
30
|
get collections() {
|
|
27
31
|
assertActive();
|
|
@@ -35,6 +39,10 @@ export function guardEditorAuthoring(
|
|
|
35
39
|
assertActive();
|
|
36
40
|
return authoring().locale;
|
|
37
41
|
},
|
|
42
|
+
get canCreateDocument() {
|
|
43
|
+
assertActive();
|
|
44
|
+
return canCreateDocument;
|
|
45
|
+
},
|
|
38
46
|
get referenceBrowser() {
|
|
39
47
|
assertActive();
|
|
40
48
|
return referenceBrowser;
|
|
@@ -3,7 +3,7 @@ import { tick } from "svelte";
|
|
|
3
3
|
export const fieldIssueRevealEvent = "ridu:reveal-field-issue";
|
|
4
4
|
|
|
5
5
|
/** Open lazy field disclosures and repeated rows before focusing the invalid control. */
|
|
6
|
-
export async function focusFieldIssue(path: string) {
|
|
6
|
+
export async function focusFieldIssue(path: string, root: HTMLElement) {
|
|
7
7
|
const selector = `[data-field-path="${CSS.escape(path)}"]`;
|
|
8
8
|
let previous: HTMLElement | null = null;
|
|
9
9
|
// Each reveal can mount the next schema boundary. Bound the search by the
|
|
@@ -12,7 +12,7 @@ export async function focusFieldIssue(path: string) {
|
|
|
12
12
|
const segments = path.split(".");
|
|
13
13
|
let container: HTMLElement | null = null;
|
|
14
14
|
while (segments.length > 0 && container === null) {
|
|
15
|
-
container =
|
|
15
|
+
container = root.querySelector<HTMLElement>(
|
|
16
16
|
`[data-field-path="${CSS.escape(segments.join("."))}"]`
|
|
17
17
|
);
|
|
18
18
|
segments.pop();
|
|
@@ -21,7 +21,7 @@ export async function focusFieldIssue(path: string) {
|
|
|
21
21
|
previous = container;
|
|
22
22
|
for (
|
|
23
23
|
let ancestor = container.parentElement;
|
|
24
|
-
ancestor !== null;
|
|
24
|
+
ancestor !== null && root.contains(ancestor);
|
|
25
25
|
ancestor = ancestor.parentElement
|
|
26
26
|
) {
|
|
27
27
|
if (ancestor instanceof HTMLDetailsElement && ancestor.hasAttribute("data-field-collapsible"))
|
|
@@ -29,7 +29,7 @@ export async function focusFieldIssue(path: string) {
|
|
|
29
29
|
}
|
|
30
30
|
container.dispatchEvent(new Event(fieldIssueRevealEvent, { bubbles: true }));
|
|
31
31
|
await tick();
|
|
32
|
-
const revealed =
|
|
32
|
+
const revealed = root.querySelector<HTMLElement>(selector);
|
|
33
33
|
const control = revealed?.querySelector<HTMLElement>(
|
|
34
34
|
"input:not([disabled]), textarea:not([disabled]), [contenteditable='true'], button:not([disabled]), [tabindex]:not([tabindex='-1'])"
|
|
35
35
|
);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveBlockTypes } from "@riducms/protocol";
|
|
1
|
+
import { isRecord, resolveBlockTypes } from "@riducms/protocol";
|
|
2
2
|
import type { SchemaField } from "@riducms/protocol";
|
|
3
3
|
import type { FormController } from "@admin/core/forms/form-controller.svelte";
|
|
4
4
|
import { scopeRepeatedRowField } from "@admin/fields/nested/scoped-field";
|
|
@@ -34,11 +34,11 @@ export function captureFieldOccurrence(form: FormController, target: string): Fi
|
|
|
34
34
|
if (field.type === "group") {
|
|
35
35
|
// An absent optional object may initialize through its visible inputs.
|
|
36
36
|
// Once present, removing it revokes the child occurrence immediately.
|
|
37
|
-
let initialized =
|
|
37
|
+
let initialized = isRecord(form.get(current));
|
|
38
38
|
const objectPath = () => {
|
|
39
39
|
const current = path();
|
|
40
40
|
if (current === undefined) return;
|
|
41
|
-
if (
|
|
41
|
+
if (isRecord(form.get(current))) initialized = true;
|
|
42
42
|
else if (initialized) return;
|
|
43
43
|
return current;
|
|
44
44
|
};
|
|
@@ -50,7 +50,7 @@ export function captureFieldOccurrence(form: FormController, target: string): Fi
|
|
|
50
50
|
const index = Number(target.slice(current.length + 1).split(".")[0]);
|
|
51
51
|
if (!Array.isArray(rows) || !Number.isSafeInteger(index)) continue;
|
|
52
52
|
const row: unknown = rows[index];
|
|
53
|
-
if (!
|
|
53
|
+
if (!isRecord(row) || typeof row._key !== "string" || !row._key)
|
|
54
54
|
throw new Error(`Cannot bind ${target}: rows require stable _key values.`);
|
|
55
55
|
const key = row._key,
|
|
56
56
|
variant = row.blockType;
|
|
@@ -100,6 +100,3 @@ export function captureFieldOccurrence(form: FormController, target: string): Fi
|
|
|
100
100
|
throw new Error(`Cannot bind ${target}: no unique live schema field occurrence exists.`);
|
|
101
101
|
return found;
|
|
102
102
|
}
|
|
103
|
-
function record(value: unknown): value is Record<string, unknown> {
|
|
104
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
105
|
-
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isRecord } from "@riducms/protocol";
|
|
1
2
|
import {
|
|
2
3
|
LiveValidationController,
|
|
3
4
|
type LiveValidationTransport,
|
|
@@ -29,6 +30,7 @@ import {
|
|
|
29
30
|
submissionFormValues,
|
|
30
31
|
} from "@admin/core/forms/form-schema";
|
|
31
32
|
import { validateFormValues, unknownBlockIssues } from "@admin/core/forms/form-validation";
|
|
33
|
+
import { readFormPath } from "@admin/core/forms/form-path";
|
|
32
34
|
|
|
33
35
|
export type { DetachedDraftValue, FormValues } from "@admin/core/forms/form-schema";
|
|
34
36
|
|
|
@@ -263,7 +265,8 @@ export class FormController {
|
|
|
263
265
|
path: string,
|
|
264
266
|
sourcePath: string,
|
|
265
267
|
derive: (source: unknown) => string,
|
|
266
|
-
follows: (current: unknown, source: unknown) => boolean
|
|
268
|
+
follows: (current: unknown, source: unknown) => boolean,
|
|
269
|
+
initial?: { current: unknown; source: unknown }
|
|
267
270
|
): DerivedTextBinding {
|
|
268
271
|
let binding = this.#derivedTextBindings.get(path);
|
|
269
272
|
if (binding !== undefined && binding.sourcePath !== sourcePath) {
|
|
@@ -272,10 +275,11 @@ export class FormController {
|
|
|
272
275
|
binding = undefined;
|
|
273
276
|
}
|
|
274
277
|
if (binding === undefined) {
|
|
275
|
-
const source = this.get(sourcePath);
|
|
278
|
+
const source = initial === undefined ? this.get(sourcePath) : initial.source;
|
|
279
|
+
const current = initial === undefined ? this.get(path) : initial.current;
|
|
276
280
|
const state: DerivedTextBindingState = {
|
|
277
281
|
sourcePath,
|
|
278
|
-
following: follows(
|
|
282
|
+
following: follows(current, source),
|
|
279
283
|
derive,
|
|
280
284
|
stop: () => undefined,
|
|
281
285
|
};
|
|
@@ -284,7 +288,10 @@ export class FormController {
|
|
|
284
288
|
});
|
|
285
289
|
this.#derivedTextBindings.set(path, state);
|
|
286
290
|
binding = state;
|
|
287
|
-
if (state.following)
|
|
291
|
+
if (state.following) {
|
|
292
|
+
const derived = state.derive(source);
|
|
293
|
+
if (String(current ?? "") !== derived) this.set(path, derived);
|
|
294
|
+
}
|
|
288
295
|
}
|
|
289
296
|
const active = binding;
|
|
290
297
|
return {
|
|
@@ -302,7 +309,7 @@ export class FormController {
|
|
|
302
309
|
get(path: string): unknown {
|
|
303
310
|
if (this.#readContext !== undefined && !Object.hasOwn(this.values, path.split(".")[0] ?? ""))
|
|
304
311
|
return this.#readContext.get(path);
|
|
305
|
-
return
|
|
312
|
+
return readFormPath(this.values, path);
|
|
306
313
|
}
|
|
307
314
|
|
|
308
315
|
/** Shared identity lookups: a mounted header never scans every sibling on its own. */
|
|
@@ -628,6 +635,7 @@ export class FormController {
|
|
|
628
635
|
this.original = result.original;
|
|
629
636
|
this.issues = [];
|
|
630
637
|
this.#submittingIssues = [];
|
|
638
|
+
this.submitting = false;
|
|
631
639
|
this.revision = ++this.#revision;
|
|
632
640
|
return { detached: result.detached, restoredFields: 0 };
|
|
633
641
|
}
|
|
@@ -646,6 +654,7 @@ export class FormController {
|
|
|
646
654
|
this.original = result.original;
|
|
647
655
|
this.issues = [];
|
|
648
656
|
this.#submittingIssues = [];
|
|
657
|
+
this.submitting = false;
|
|
649
658
|
this.revision = ++this.#revision;
|
|
650
659
|
return { detached: result.detached, restoredFields: result.restoredFields };
|
|
651
660
|
}
|
|
@@ -830,18 +839,6 @@ function uniqueValidationIssues(issues: readonly ValidationIssue[]) {
|
|
|
830
839
|
return [...unique.values()];
|
|
831
840
|
}
|
|
832
841
|
|
|
833
|
-
function readPath(values: FormValues, path: string) {
|
|
834
|
-
let current: unknown = values;
|
|
835
|
-
for (const segment of path.split(".")) {
|
|
836
|
-
if (Array.isArray(current)) {
|
|
837
|
-
current = current[Number(segment)];
|
|
838
|
-
} else if (isRecord(current)) {
|
|
839
|
-
current = current[segment];
|
|
840
|
-
} else return undefined;
|
|
841
|
-
}
|
|
842
|
-
return current;
|
|
843
|
-
}
|
|
844
|
-
|
|
845
842
|
function writePath(values: FormValues, path: string, value: unknown) {
|
|
846
843
|
const segments = path.split(".");
|
|
847
844
|
let current: FormValues | unknown[] = values;
|
|
@@ -869,10 +866,6 @@ function setChild(container: FormValues | unknown[], segment: string, value: unk
|
|
|
869
866
|
else container[segment] = value;
|
|
870
867
|
}
|
|
871
868
|
|
|
872
|
-
function isRecord(value: unknown): value is FormValues {
|
|
873
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
874
|
-
}
|
|
875
|
-
|
|
876
869
|
function embeddedOccurrenceKey(occurrence: EmbeddedOccurrence) {
|
|
877
870
|
return JSON.stringify([
|
|
878
871
|
occurrence.tree.key,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isRecord } from "@riducms/protocol";
|
|
1
2
|
import type { SchemaCollection } from "@riducms/protocol";
|
|
2
3
|
|
|
3
4
|
import { cloneFormValues, type FormValues } from "@admin/core/forms/form-schema";
|
|
@@ -52,6 +53,22 @@ export function takeFormDraft(
|
|
|
52
53
|
}
|
|
53
54
|
}
|
|
54
55
|
|
|
56
|
+
/** Read a valid recovery checkpoint without consuming it. Startup uses this to
|
|
57
|
+
* finish data-dependent access before the owning form adopts the draft. */
|
|
58
|
+
export function peekFormDraft(
|
|
59
|
+
collectionID: string,
|
|
60
|
+
documentID: string | undefined
|
|
61
|
+
): FormDraftCheckpoint | undefined {
|
|
62
|
+
try {
|
|
63
|
+
const encoded = sessionStorage.getItem(storageKey(collectionID, documentID));
|
|
64
|
+
if (encoded === null) return undefined;
|
|
65
|
+
const decoded: unknown = JSON.parse(encoded);
|
|
66
|
+
return isCheckpoint(decoded, collectionID, documentID) ? decoded : undefined;
|
|
67
|
+
} catch {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
55
72
|
export function clearFormDraft(collectionID: string, documentID: string | undefined): void {
|
|
56
73
|
try {
|
|
57
74
|
sessionStorage.removeItem(storageKey(collectionID, documentID));
|
|
@@ -74,7 +91,3 @@ function isCheckpoint(
|
|
|
74
91
|
if (value.documentID !== documentID) return false;
|
|
75
92
|
return isRecord(value.values) && isRecord(value.original) && typeof value.createdAt === "string";
|
|
76
93
|
}
|
|
77
|
-
|
|
78
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
79
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
80
|
-
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { resolveBlockTypes } from "@riducms/protocol";
|
|
1
|
+
import { isRecord, resolveBlockTypes } from "@riducms/protocol";
|
|
2
2
|
import type { SchemaField, ValidationIssue } from "@riducms/protocol";
|
|
3
|
-
import { embeddedOccurrences
|
|
3
|
+
import { embeddedOccurrences } from "@admin/core/forms/embedded-fields";
|
|
4
4
|
|
|
5
5
|
export interface FieldValueLocation {
|
|
6
6
|
schema?: SchemaField;
|
|
@@ -64,7 +64,7 @@ export function indexFieldValues(fields: readonly SchemaField[], values: Record<
|
|
|
64
64
|
path: string,
|
|
65
65
|
token: readonly string[]
|
|
66
66
|
) => {
|
|
67
|
-
if (!
|
|
67
|
+
if (!isRecord(value)) return;
|
|
68
68
|
for (const child of children)
|
|
69
69
|
visit(child, value[child.name], `${path}.${child.name}`, [...token, child.id]);
|
|
70
70
|
};
|
|
@@ -74,12 +74,11 @@ export function indexFieldValues(fields: readonly SchemaField[], values: Record<
|
|
|
74
74
|
if ((field.type === "array" || field.type === "blocks") && Array.isArray(value)) {
|
|
75
75
|
const keys = new Map<string, number>();
|
|
76
76
|
for (const row of value) {
|
|
77
|
-
if (
|
|
77
|
+
if (isRecord(row) && typeof row._key === "string")
|
|
78
78
|
keys.set(row._key, (keys.get(row._key) ?? 0) + 1);
|
|
79
79
|
}
|
|
80
80
|
for (const [i, row] of value.entries()) {
|
|
81
|
-
if (!
|
|
82
|
-
continue;
|
|
81
|
+
if (!isRecord(row) || typeof row._key !== "string" || keys.get(row._key) !== 1) continue;
|
|
83
82
|
const block = resolveBlockTypes(field.blocks).find(
|
|
84
83
|
(candidate) => candidate.slug === row.blockType
|
|
85
84
|
);
|
|
@@ -109,6 +108,12 @@ export function indexFieldValues(fields: readonly SchemaField[], values: Record<
|
|
|
109
108
|
return locations;
|
|
110
109
|
}
|
|
111
110
|
|
|
111
|
+
// An index below a schema-owned location has no stable identity of its own.
|
|
112
|
+
// After the container changes, neither issues nor access may follow that index.
|
|
113
|
+
export function canRebaseIndexedSuffix(suffix: string, before: unknown, after: unknown) {
|
|
114
|
+
return !/\.\d+(?:\.|$)/.test(suffix) || JSON.stringify(before) === JSON.stringify(after);
|
|
115
|
+
}
|
|
116
|
+
|
|
112
117
|
function correlate(
|
|
113
118
|
before: FieldValueLocation[],
|
|
114
119
|
after: FieldValueLocation[],
|
|
@@ -137,12 +142,7 @@ function correlate(
|
|
|
137
142
|
const target = current.get(source.token);
|
|
138
143
|
if (target === undefined) return [];
|
|
139
144
|
const suffix = issue.path.slice(source.path.length);
|
|
140
|
-
|
|
141
|
-
if (
|
|
142
|
-
/\.\d+(?:\.|$)/.test(suffix) &&
|
|
143
|
-
JSON.stringify(source.value) !== JSON.stringify(target.value)
|
|
144
|
-
)
|
|
145
|
-
return [];
|
|
145
|
+
if (!canRebaseIndexedSuffix(suffix, source.value, target.value)) return [];
|
|
146
146
|
if (JSON.stringify(at(source.value, suffix)) !== JSON.stringify(at(target.value, suffix)))
|
|
147
147
|
return [];
|
|
148
148
|
return [{ ...issue, path: target.path + suffix }];
|
|
@@ -152,7 +152,7 @@ function correlate(
|
|
|
152
152
|
function at(value: unknown, suffix: string) {
|
|
153
153
|
for (const segment of suffix.split(".").filter(Boolean)) {
|
|
154
154
|
if (Array.isArray(value)) value = value[Number(segment)];
|
|
155
|
-
else if (
|
|
155
|
+
else if (isRecord(value)) value = value[segment];
|
|
156
156
|
else return undefined;
|
|
157
157
|
}
|
|
158
158
|
return value;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { isRecord } from "@riducms/protocol";
|
|
2
|
+
|
|
3
|
+
// Form paths address concrete row indexes as well as object fields. Document
|
|
4
|
+
// display paths deliberately stop at arrays; keep those semantics separate.
|
|
5
|
+
export function readFormPath(values: unknown, path: string): unknown {
|
|
6
|
+
let current = values;
|
|
7
|
+
for (const segment of path.split(".")) {
|
|
8
|
+
if (Array.isArray(current)) current = current[Number(segment)];
|
|
9
|
+
else if (isRecord(current)) current = current[segment];
|
|
10
|
+
else return undefined;
|
|
11
|
+
}
|
|
12
|
+
return current;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function joinFormPath(parent: string, child: string) {
|
|
16
|
+
return parent === "" ? child : `${parent}.${child}`;
|
|
17
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { resolveBlockTypes } from "@riducms/protocol";
|
|
1
|
+
import { isRecord, resolveBlockTypes } from "@riducms/protocol";
|
|
2
2
|
import { embeddedOccurrences, transformEmbeddedPayloads } from "@admin/core/forms/embedded-fields";
|
|
3
|
+
import { joinFormPath, readFormPath } from "@admin/core/forms/form-path";
|
|
4
|
+
import { schemaFieldDefault } from "@admin/core/schema/field-default";
|
|
3
5
|
import type { SchemaField } from "@riducms/protocol";
|
|
4
6
|
|
|
5
7
|
export type FormValues = Record<string, unknown>;
|
|
@@ -52,7 +54,7 @@ export function shouldSubmitLocalizedPath(
|
|
|
52
54
|
return (
|
|
53
55
|
source === undefined ||
|
|
54
56
|
source === locale ||
|
|
55
|
-
!deepEqual(
|
|
57
|
+
!deepEqual(readFormPath(values, path), readFormPath(original, provenancePath))
|
|
56
58
|
);
|
|
57
59
|
}
|
|
58
60
|
|
|
@@ -103,7 +105,7 @@ function submissionRecord(
|
|
|
103
105
|
const values = isRecord(input) ? input : {};
|
|
104
106
|
const submitted: FormValues = {};
|
|
105
107
|
for (const field of fields) {
|
|
106
|
-
const path =
|
|
108
|
+
const path = joinFormPath(prefix, field.name);
|
|
107
109
|
if (
|
|
108
110
|
field.category === "presentation" ||
|
|
109
111
|
!include(path, field.path) ||
|
|
@@ -195,18 +197,6 @@ function localizationProvenancePath(
|
|
|
195
197
|
return resolved.join(".");
|
|
196
198
|
}
|
|
197
199
|
|
|
198
|
-
function readPath(values: unknown, path: string) {
|
|
199
|
-
let current = values;
|
|
200
|
-
for (const segment of path.split(".")) {
|
|
201
|
-
current = Array.isArray(current)
|
|
202
|
-
? current[Number(segment)]
|
|
203
|
-
: isRecord(current)
|
|
204
|
-
? current[segment]
|
|
205
|
-
: undefined;
|
|
206
|
-
}
|
|
207
|
-
return current;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
200
|
export function reconcileFormSchema(
|
|
211
201
|
state: FormSchemaState,
|
|
212
202
|
previousFields: readonly SchemaField[],
|
|
@@ -360,7 +350,7 @@ function reconcileFieldValue(
|
|
|
360
350
|
currentValue,
|
|
361
351
|
originalValue,
|
|
362
352
|
detached,
|
|
363
|
-
|
|
353
|
+
joinFormPath(parentPath, next.name),
|
|
364
354
|
initializeDefaults
|
|
365
355
|
);
|
|
366
356
|
return {
|
|
@@ -406,7 +396,7 @@ function reconcileFieldValue(
|
|
|
406
396
|
) {
|
|
407
397
|
const reconcilePayloads = (value: unknown, before: unknown, report: DetachedDraftValue[]) => {
|
|
408
398
|
const copy = cloneFormValue(value);
|
|
409
|
-
const fieldPath =
|
|
399
|
+
const fieldPath = joinFormPath(parentPath, next.name);
|
|
410
400
|
const old = embeddedOccurrences(previous, before, fieldPath);
|
|
411
401
|
const currentOld = embeddedOccurrences(previous, value, fieldPath);
|
|
412
402
|
const candidates = embeddedOccurrences(next, copy, fieldPath);
|
|
@@ -482,7 +472,7 @@ function reconcileArrayValue(
|
|
|
482
472
|
const originalRows = Array.isArray(originalValue) ? originalValue : [];
|
|
483
473
|
const previousChildren = previous.nested?.fields ?? [];
|
|
484
474
|
const nextChildren = next.nested?.fields ?? [];
|
|
485
|
-
const rowPath =
|
|
475
|
+
const rowPath = joinFormPath(parentPath, next.name);
|
|
486
476
|
|
|
487
477
|
return {
|
|
488
478
|
currentPresent,
|
|
@@ -541,7 +531,7 @@ function reconcileBlocksValue(
|
|
|
541
531
|
const nextBlocks = new Map(
|
|
542
532
|
(resolveBlockTypes(next.blocks) ?? []).map((block) => [block.slug, block])
|
|
543
533
|
);
|
|
544
|
-
const rowPath =
|
|
534
|
+
const rowPath = joinFormPath(parentPath, next.name);
|
|
545
535
|
const keptCurrent: unknown[] = [];
|
|
546
536
|
|
|
547
537
|
for (const [index, row] of currentRows.entries()) {
|
|
@@ -625,19 +615,6 @@ function compatibleFields(previous: SchemaField, next: SchemaField) {
|
|
|
625
615
|
return true;
|
|
626
616
|
}
|
|
627
617
|
|
|
628
|
-
function fieldDefault(field: SchemaField) {
|
|
629
|
-
if (field.type === "select" && field.select?.hasMany === true) {
|
|
630
|
-
if ((field.select.defaultValues?.length ?? 0) === 0) return { present: false };
|
|
631
|
-
return { present: true, value: field.select.defaultValues };
|
|
632
|
-
}
|
|
633
|
-
if (field.default === undefined) return { present: false };
|
|
634
|
-
if (field.type === "text-list" || field.type === "number-list")
|
|
635
|
-
return { present: true, value: JSON.parse(field.default) };
|
|
636
|
-
if (field.type === "number") return { present: true, value: Number(field.default) };
|
|
637
|
-
if (field.type === "checkbox") return { present: true, value: field.default === "true" };
|
|
638
|
-
return { present: true, value: field.default };
|
|
639
|
-
}
|
|
640
|
-
|
|
641
618
|
function initialFieldValue(
|
|
642
619
|
field: SchemaField,
|
|
643
620
|
value?: unknown,
|
|
@@ -665,7 +642,7 @@ function initialFieldValue(
|
|
|
665
642
|
}
|
|
666
643
|
return { present: true, value };
|
|
667
644
|
}
|
|
668
|
-
const direct =
|
|
645
|
+
const direct = schemaFieldDefault(field);
|
|
669
646
|
if (direct.present) return direct;
|
|
670
647
|
if (field.type === "group") {
|
|
671
648
|
const nested = initialFormValues(field.nested?.fields ?? []);
|
|
@@ -692,7 +669,7 @@ function detachedValue(
|
|
|
692
669
|
return {
|
|
693
670
|
fieldId: field.id,
|
|
694
671
|
label: field.admin.label,
|
|
695
|
-
path:
|
|
672
|
+
path: joinFormPath(parentPath, field.name),
|
|
696
673
|
reason,
|
|
697
674
|
value: cloneFormValue(value),
|
|
698
675
|
};
|
|
@@ -711,18 +688,10 @@ function copyReservedValue(source: FormValues, target: FormValues, name: string)
|
|
|
711
688
|
if (Object.hasOwn(source, name)) target[name] = cloneFormValue(source[name]);
|
|
712
689
|
}
|
|
713
690
|
|
|
714
|
-
function joinPath(parent: string, child: string) {
|
|
715
|
-
return parent === "" ? child : `${parent}.${child}`;
|
|
716
|
-
}
|
|
717
|
-
|
|
718
691
|
function deepEqual(left: unknown, right: unknown) {
|
|
719
692
|
return JSON.stringify(left) === JSON.stringify(right);
|
|
720
693
|
}
|
|
721
694
|
|
|
722
|
-
function isRecord(value: unknown): value is FormValues {
|
|
723
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
724
|
-
}
|
|
725
|
-
|
|
726
695
|
export function cloneFormValues(values: FormValues): FormValues {
|
|
727
696
|
return cloneFormValue(values) as FormValues;
|
|
728
697
|
}
|
|
@@ -745,7 +714,7 @@ function embeddedProvenancePath(
|
|
|
745
714
|
prefix = ""
|
|
746
715
|
): string | undefined {
|
|
747
716
|
for (const field of fields) {
|
|
748
|
-
const fieldPath =
|
|
717
|
+
const fieldPath = joinFormPath(prefix, field.name);
|
|
749
718
|
if (!path.startsWith(`${fieldPath}.`)) continue;
|
|
750
719
|
const value = current[field.name];
|
|
751
720
|
const before = original[field.name];
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { resolveBlockTypes } from "@riducms/protocol";
|
|
1
|
+
import { isRecord, resolveBlockTypes } from "@riducms/protocol";
|
|
2
2
|
import { embeddedOccurrences } from "@admin/core/forms/embedded-fields";
|
|
3
3
|
import type { AdminI18n } from "@riducms/plugin";
|
|
4
4
|
import { createAdminI18n } from "@riducms/translations";
|
|
5
5
|
import type { SchemaField, ValidationIssue } from "@riducms/protocol";
|
|
6
6
|
|
|
7
7
|
import { initialFormValues, type FormValues } from "@admin/core/forms/form-schema";
|
|
8
|
+
import { joinFormPath } from "@admin/core/forms/form-path";
|
|
8
9
|
|
|
9
10
|
export interface FormValidationOptions {
|
|
10
11
|
requireMissing: boolean;
|
|
@@ -66,13 +67,8 @@ function resolveFieldLabel(
|
|
|
66
67
|
const [index, ...childSegments] = remaining;
|
|
67
68
|
const rowLabels = isIndex(index)
|
|
68
69
|
? [
|
|
69
|
-
...
|
|
70
|
-
|
|
71
|
-
i18n,
|
|
72
|
-
"fields:rowNumber",
|
|
73
|
-
{ number: Number(index) + 1 },
|
|
74
|
-
`Row ${Number(index) + 1}`
|
|
75
|
-
),
|
|
70
|
+
...parents,
|
|
71
|
+
`${localizedFieldLabel(field, i18n)} ${i18n?.formatNumber(Number(index) + 1) ?? Number(index) + 1}`,
|
|
76
72
|
]
|
|
77
73
|
: labels;
|
|
78
74
|
return resolveFieldLabel(
|
|
@@ -121,7 +117,7 @@ function validateFields(
|
|
|
121
117
|
) {
|
|
122
118
|
for (const field of fields) {
|
|
123
119
|
if (field.category === "presentation") continue;
|
|
124
|
-
const path =
|
|
120
|
+
const path = joinFormPath(prefix, field.name);
|
|
125
121
|
if (!include(path, field.path)) continue;
|
|
126
122
|
const exists = Object.hasOwn(values, field.name);
|
|
127
123
|
const value = values[field.name];
|
|
@@ -182,11 +178,9 @@ function validateFields(
|
|
|
182
178
|
case "text":
|
|
183
179
|
case "code":
|
|
184
180
|
case "textarea":
|
|
185
|
-
validateString(field, value, path, issues, i18n);
|
|
186
|
-
break;
|
|
187
181
|
case "email":
|
|
188
182
|
case "date":
|
|
189
|
-
|
|
183
|
+
validateString(field, value, path, issues, i18n);
|
|
190
184
|
break;
|
|
191
185
|
case "number":
|
|
192
186
|
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
@@ -351,31 +345,6 @@ function validateString(
|
|
|
351
345
|
}
|
|
352
346
|
}
|
|
353
347
|
|
|
354
|
-
function validateOptionalString(
|
|
355
|
-
field: SchemaField,
|
|
356
|
-
value: unknown,
|
|
357
|
-
path: string,
|
|
358
|
-
issues: ValidationIssue[],
|
|
359
|
-
i18n?: AdminI18n
|
|
360
|
-
) {
|
|
361
|
-
if (typeof value !== "string") {
|
|
362
|
-
issues.push(
|
|
363
|
-
issue(
|
|
364
|
-
"invalid_type",
|
|
365
|
-
path,
|
|
366
|
-
translate(
|
|
367
|
-
i18n,
|
|
368
|
-
"errors:string",
|
|
369
|
-
{ label: localizedFieldLabel(field, i18n) },
|
|
370
|
-
`${localizedFieldLabel(field, i18n)} must be a string`
|
|
371
|
-
)
|
|
372
|
-
)
|
|
373
|
-
);
|
|
374
|
-
} else if (field.required && value.length === 0) {
|
|
375
|
-
issues.push(requiredIssue(field, path, i18n));
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
|
|
379
348
|
function validateSelect(
|
|
380
349
|
field: SchemaField,
|
|
381
350
|
value: unknown,
|
|
@@ -837,14 +806,6 @@ function issue(code: string, path: string, message: string): ValidationIssue {
|
|
|
837
806
|
return { code, path, message };
|
|
838
807
|
}
|
|
839
808
|
|
|
840
|
-
function joinPath(prefix: string, name: string) {
|
|
841
|
-
return prefix === "" ? name : `${prefix}.${name}`;
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
845
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
846
|
-
}
|
|
847
|
-
|
|
848
809
|
function translate(
|
|
849
810
|
i18n: AdminI18n | undefined,
|
|
850
811
|
key: Parameters<AdminI18n["t"]>[0],
|
|
@@ -868,7 +829,7 @@ export function unknownBlockIssues(
|
|
|
868
829
|
const issues: ValidationIssue[] = [];
|
|
869
830
|
for (const field of fields) {
|
|
870
831
|
const value = values[field.name];
|
|
871
|
-
const path =
|
|
832
|
+
const path = joinFormPath(prefix, field.name);
|
|
872
833
|
if (field.type === "group" && isRecord(value))
|
|
873
834
|
issues.push(...unknownBlockIssues(field.nested?.fields ?? [], value, i18n, path));
|
|
874
835
|
if (field.type === "plugin") {
|
|
@@ -25,15 +25,19 @@ export function guardPluginAuthoring(
|
|
|
25
25
|
binding.assertActive();
|
|
26
26
|
return host.locale;
|
|
27
27
|
},
|
|
28
|
+
canCreateDocument(collection) {
|
|
29
|
+
binding.assertActive();
|
|
30
|
+
return host.canCreateDocument(collection);
|
|
31
|
+
},
|
|
28
32
|
referenceBrowser: (anchor, props) => {
|
|
29
33
|
binding.assertActive();
|
|
30
34
|
const overrides = {
|
|
31
35
|
get readOnly() {
|
|
32
36
|
return binding.readOnly || props.readOnly;
|
|
33
37
|
},
|
|
34
|
-
onCommit: async (ids: string[]) => {
|
|
38
|
+
onCommit: async (ids: string[], collectionSlug: string) => {
|
|
35
39
|
binding.assertEditable();
|
|
36
|
-
const result = await props.onCommit([...ids]);
|
|
40
|
+
const result = await props.onCommit([...ids], collectionSlug);
|
|
37
41
|
binding.assertEditable();
|
|
38
42
|
return result;
|
|
39
43
|
},
|