@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,9 +1,10 @@
|
|
|
1
1
|
import { connectDocumentLiveValidation } from "@admin/core/forms/live-validation.svelte";
|
|
2
|
-
import type {
|
|
2
|
+
import type { AdminCreateDataV1, AdminDocumentDataV1, SchemaCollection } from "@riducms/protocol";
|
|
3
3
|
import { RiduError } from "@riducms/sdk";
|
|
4
|
-
import {
|
|
4
|
+
import { formatAdminDateTime } from "@admin/core/i18n/format-admin-date-time";
|
|
5
|
+
import { tick } from "svelte";
|
|
5
6
|
|
|
6
|
-
import type { AdminDocument
|
|
7
|
+
import type { AdminDocument } from "@admin/core/api/admin-client";
|
|
7
8
|
import { FormController, FormValidationError } from "@admin/core/forms/form-controller.svelte";
|
|
8
9
|
import {
|
|
9
10
|
clearFormDraft,
|
|
@@ -13,14 +14,19 @@ import {
|
|
|
13
14
|
import { documentFormValues, initialFormValues } from "@admin/core/forms/form-schema";
|
|
14
15
|
import { invalidFieldLabels } from "@admin/core/forms/form-validation";
|
|
15
16
|
import type { NotificationCenter } from "@admin/core/notifications/notification-center.svelte";
|
|
17
|
+
import { samePreparedCreateValues } from "@admin/core/bootstrap/admin-bootstrap";
|
|
16
18
|
import {
|
|
17
19
|
collectionPath,
|
|
18
20
|
createDocumentPath,
|
|
19
21
|
documentPath,
|
|
20
22
|
globalPath,
|
|
23
|
+
withContentLocale,
|
|
21
24
|
} from "@admin/core/routing/admin-paths";
|
|
22
25
|
import type { AdminRuntime } from "@admin/core/runtime/admin-runtime.svelte";
|
|
23
|
-
import {
|
|
26
|
+
import { DocumentLockController } from "@admin/features/documents/document-lock-controller.svelte";
|
|
27
|
+
import { documentTitleField } from "@admin/features/documents/document-title";
|
|
28
|
+
import { isUploadMetadataField } from "@admin/features/uploads/upload-document-contracts";
|
|
29
|
+
import { UploadDraft } from "@admin/features/uploads/upload-draft.svelte";
|
|
24
30
|
|
|
25
31
|
type Navigate = (to: string, options?: { replace?: boolean }) => void;
|
|
26
32
|
|
|
@@ -31,96 +37,84 @@ interface DocumentControllerOptions {
|
|
|
31
37
|
get slug(): string;
|
|
32
38
|
get documentID(): string | undefined;
|
|
33
39
|
get global(): boolean;
|
|
34
|
-
get view(): "edit" | "api";
|
|
35
40
|
get locale(): string | undefined;
|
|
41
|
+
get editable(): boolean;
|
|
42
|
+
readonly prepared?: AdminDocumentDataV1 | AdminCreateDataV1;
|
|
36
43
|
}
|
|
37
44
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
"focalX",
|
|
48
|
-
"focalY",
|
|
49
|
-
"cropX",
|
|
50
|
-
"cropY",
|
|
51
|
-
"cropWidth",
|
|
52
|
-
"cropHeight",
|
|
53
|
-
]);
|
|
45
|
+
interface DocumentRouteSnapshot {
|
|
46
|
+
slug: string;
|
|
47
|
+
documentID: string | undefined;
|
|
48
|
+
locale: string | undefined;
|
|
49
|
+
manifestRevision: number;
|
|
50
|
+
global: boolean;
|
|
51
|
+
editable: boolean;
|
|
52
|
+
prepared?: AdminDocumentDataV1 | AdminCreateDataV1;
|
|
53
|
+
}
|
|
54
54
|
|
|
55
55
|
export class DocumentController {
|
|
56
56
|
readonly form: FormController;
|
|
57
|
+
readonly lock: DocumentLockController;
|
|
57
58
|
collection = $state.raw<SchemaCollection>();
|
|
58
59
|
collectionAvailable = $state(true);
|
|
59
60
|
loading = $state(true);
|
|
60
61
|
error = $state<string>();
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
currentStatus = $state<"draft" | "published">("published");
|
|
64
|
-
versions = $state.raw<AdminVersion[]>([]);
|
|
65
|
-
versionOperation = $state(false);
|
|
66
|
-
imageOperation = $state(false);
|
|
67
|
-
imageOutcomeUncertain = $state(false);
|
|
62
|
+
readonly upload: UploadDraft;
|
|
63
|
+
publicationOperation = $state(false);
|
|
68
64
|
saveOutcomeUncertain = $state(false);
|
|
69
65
|
duplicateOperation = $state(false);
|
|
70
66
|
deleteDialogOpen = $state(false);
|
|
71
|
-
restoreDialogOpen = $state(false);
|
|
72
|
-
pendingRestore = $state.raw<AdminVersion>();
|
|
73
67
|
currentDocument = $state.raw<AdminDocument>();
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
lockOperation = $state(false);
|
|
68
|
+
lastSavedAt = $state<number>();
|
|
69
|
+
unlockOperation = $state(false);
|
|
70
|
+
copyLocaleOperation = $state(false);
|
|
78
71
|
#activeRouteKey = "";
|
|
72
|
+
#routeGeneration = 0;
|
|
79
73
|
#activeContentLocale?: string;
|
|
80
74
|
#appliedManifestRevision = 0;
|
|
81
|
-
#
|
|
82
|
-
#
|
|
83
|
-
#
|
|
75
|
+
#scheduledRouteKey = "";
|
|
76
|
+
#formChangeGeneration = 0;
|
|
77
|
+
#stopObservingFormChanges: () => void;
|
|
78
|
+
#loadRequest?: AbortController;
|
|
84
79
|
#saveRequest?: AbortController;
|
|
85
|
-
#
|
|
86
|
-
|
|
87
|
-
|
|
80
|
+
#forceUnlockRequest?: AbortController;
|
|
81
|
+
#copyLocaleRequest?: AbortController;
|
|
82
|
+
|
|
83
|
+
constructor(private readonly options: DocumentControllerOptions) {
|
|
84
|
+
this.upload = new UploadDraft(
|
|
85
|
+
options.runtime.client,
|
|
86
|
+
() => this.collectionSlug,
|
|
87
|
+
options.runtime.i18n
|
|
88
|
+
);
|
|
88
89
|
this.form = new FormController({}, options.runtime.i18n);
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const locale = this.options.locale;
|
|
94
|
-
// Route setup reads the manifest inside an untracked controller operation, so manifest
|
|
95
|
-
// readiness must remain an explicit synchronization dependency for direct lazy-route loads.
|
|
96
|
-
if (this.options.runtime.manifestRevision === 0) return;
|
|
97
|
-
return untrack(() => this.#enterRoute(slug, documentID, locale));
|
|
90
|
+
this.lock = new DocumentLockController({
|
|
91
|
+
runtime: options.runtime,
|
|
92
|
+
notifications: options.notifications,
|
|
93
|
+
form: this.form,
|
|
98
94
|
});
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const view = this.options.view;
|
|
102
|
-
untrack(() => {
|
|
103
|
-
if (view === "api") this.documentView = "api";
|
|
104
|
-
else if (this.documentView === "api") this.documentView = "edit";
|
|
105
|
-
});
|
|
95
|
+
this.#stopObservingFormChanges = this.form.observeChanges(() => {
|
|
96
|
+
this.#formChangeGeneration += 1;
|
|
106
97
|
});
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
this
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
98
|
+
connectDocumentLiveValidation(this.form, options.runtime.client);
|
|
99
|
+
this.#scheduleRouteSync(this.#routeSnapshot());
|
|
100
|
+
$effect.pre(() => {
|
|
101
|
+
const global = this.options.global;
|
|
102
|
+
this.#scheduleRouteSync({
|
|
103
|
+
slug: this.options.slug,
|
|
104
|
+
documentID: global ? this.options.slug : this.options.documentID,
|
|
105
|
+
locale: this.options.locale,
|
|
106
|
+
manifestRevision: this.options.runtime.manifestRevision,
|
|
107
|
+
global,
|
|
108
|
+
editable: this.options.editable,
|
|
109
|
+
prepared: this.options.prepared,
|
|
110
|
+
});
|
|
117
111
|
});
|
|
118
112
|
|
|
119
113
|
$effect(() => {
|
|
120
114
|
const checkpoint = () => this.checkpointDraft();
|
|
121
115
|
const leavePage = () => {
|
|
122
116
|
checkpoint();
|
|
123
|
-
this
|
|
117
|
+
this.lock.release();
|
|
124
118
|
};
|
|
125
119
|
window.addEventListener("pagehide", leavePage);
|
|
126
120
|
const hot = import.meta.hot;
|
|
@@ -128,6 +122,14 @@ export class DocumentController {
|
|
|
128
122
|
return () => {
|
|
129
123
|
window.removeEventListener("pagehide", leavePage);
|
|
130
124
|
hot?.off("vite:beforeFullReload", checkpoint);
|
|
125
|
+
this.#routeGeneration += 1;
|
|
126
|
+
this.#cancelRouteRequests();
|
|
127
|
+
this.#loadRequest?.abort();
|
|
128
|
+
this.#stopObservingFormChanges();
|
|
129
|
+
this.form.disposeBindings();
|
|
130
|
+
this.upload.dispose();
|
|
131
|
+
this.#saveRequest?.abort();
|
|
132
|
+
this.lock.release();
|
|
131
133
|
};
|
|
132
134
|
});
|
|
133
135
|
|
|
@@ -137,7 +139,7 @@ export class DocumentController {
|
|
|
137
139
|
if (documentID === undefined || interval <= 0) return;
|
|
138
140
|
|
|
139
141
|
const timer = window.setInterval(async () => {
|
|
140
|
-
if (!this.canSave || this.
|
|
142
|
+
if (!this.canSave || this.publicationOperation) return;
|
|
141
143
|
// A background update to a published document would make its edits public.
|
|
142
144
|
// Keep a recoverable local checkpoint until the author explicitly chooses
|
|
143
145
|
// Publish changes; draft documents can use the ordinary server mutation.
|
|
@@ -146,15 +148,40 @@ export class DocumentController {
|
|
|
146
148
|
}, interval * 1_000);
|
|
147
149
|
return () => window.clearInterval(timer);
|
|
148
150
|
});
|
|
151
|
+
}
|
|
149
152
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
this
|
|
157
|
-
|
|
153
|
+
#routeSnapshot(): DocumentRouteSnapshot {
|
|
154
|
+
const slug = this.options.slug;
|
|
155
|
+
const global = this.options.global;
|
|
156
|
+
return {
|
|
157
|
+
slug,
|
|
158
|
+
documentID: global ? slug : this.options.documentID,
|
|
159
|
+
locale: this.options.locale,
|
|
160
|
+
manifestRevision: this.options.runtime.manifestRevision,
|
|
161
|
+
global,
|
|
162
|
+
editable: this.options.editable,
|
|
163
|
+
prepared: this.options.prepared,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
#scheduleRouteSync(snapshot: DocumentRouteSnapshot) {
|
|
168
|
+
const { slug, documentID, locale, manifestRevision, global, editable } = snapshot;
|
|
169
|
+
if (manifestRevision === 0) return;
|
|
170
|
+
const key = `${global ? "global" : "collection"}:${slug}:${documentID ?? "new"}:${locale ?? "default"}:${editable ? "edit" : "read"}:${manifestRevision}`;
|
|
171
|
+
if (key === this.#scheduledRouteKey) return;
|
|
172
|
+
this.#scheduledRouteKey = key;
|
|
173
|
+
// Initial construction is outside dependency collection; retained routes commit in
|
|
174
|
+
// $effect.pre. The key guard makes any dependencies read during the commit disappear on its
|
|
175
|
+
// guarded rerun, leaving only the explicit route-option reads above.
|
|
176
|
+
this.#enterRoute(
|
|
177
|
+
slug,
|
|
178
|
+
documentID,
|
|
179
|
+
locale,
|
|
180
|
+
manifestRevision,
|
|
181
|
+
global,
|
|
182
|
+
editable,
|
|
183
|
+
snapshot.prepared
|
|
184
|
+
);
|
|
158
185
|
}
|
|
159
186
|
|
|
160
187
|
get documentID() {
|
|
@@ -189,12 +216,36 @@ export class DocumentController {
|
|
|
189
216
|
return this.collection?.versionSettings?.drafts === true;
|
|
190
217
|
}
|
|
191
218
|
|
|
219
|
+
get currentRevision() {
|
|
220
|
+
const revision = this.currentDocument?._revision;
|
|
221
|
+
return typeof revision === "number" ? revision : 0;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
get currentStatus(): "draft" | "published" {
|
|
225
|
+
if (this.currentDocument === undefined) return this.draftsCollection ? "draft" : "published";
|
|
226
|
+
return this.currentDocument._status === "draft" ? "draft" : "published";
|
|
227
|
+
}
|
|
228
|
+
|
|
192
229
|
get selectedFile() {
|
|
193
|
-
return this.
|
|
230
|
+
return this.upload.file;
|
|
194
231
|
}
|
|
195
232
|
|
|
196
233
|
get hasUnsavedChanges() {
|
|
197
|
-
return this.form.dirty ||
|
|
234
|
+
return this.form.dirty || this.upload.dirty;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
get creatingAuthUser() {
|
|
238
|
+
return this.creating && !this.globalResource && this.collection?.capabilities.auth === true;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
get canForceUnlock() {
|
|
242
|
+
return (
|
|
243
|
+
!this.globalResource &&
|
|
244
|
+
!this.creating &&
|
|
245
|
+
this.collection?.capabilities.auth === true &&
|
|
246
|
+
(this.collection.authSettings?.maxLoginAttempts ?? 0) > 0 &&
|
|
247
|
+
this.form.access?.operations.update === true
|
|
248
|
+
);
|
|
198
249
|
}
|
|
199
250
|
|
|
200
251
|
get canSave() {
|
|
@@ -204,102 +255,81 @@ export class DocumentController {
|
|
|
204
255
|
const publicationAllowed =
|
|
205
256
|
!this.creating || !this.versionedCollection || this.draftsCollection || this.canPublish;
|
|
206
257
|
const inputReady = this.creating
|
|
207
|
-
? !this.uploadCollection || this.
|
|
258
|
+
? !this.uploadCollection || this.upload.file !== undefined
|
|
208
259
|
: this.hasUnsavedChanges;
|
|
209
260
|
return (
|
|
210
261
|
this.collectionAvailable &&
|
|
211
262
|
!this.saveOutcomeUncertain &&
|
|
212
|
-
!this.
|
|
213
|
-
!this.
|
|
214
|
-
!this.lockedByAnotherEditor &&
|
|
263
|
+
!this.upload.busy &&
|
|
264
|
+
!this.upload.editingImage &&
|
|
265
|
+
!this.lock.lockedByAnotherEditor &&
|
|
215
266
|
operationAllowed &&
|
|
216
267
|
publicationAllowed &&
|
|
217
268
|
!this.form.submitting &&
|
|
218
|
-
|
|
269
|
+
!this.form.writeBlocked &&
|
|
270
|
+
inputReady &&
|
|
271
|
+
(!this.uploadCollection || this.upload.present)
|
|
219
272
|
);
|
|
220
273
|
}
|
|
221
274
|
|
|
222
|
-
get lockedByAnotherEditor() {
|
|
223
|
-
return this.documentLock?.lock !== null && this.documentLock?.owned === false;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
get lockOwnerLabel() {
|
|
227
|
-
return this.documentLock?.lock?.ownerLabel;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
get lockUpdatedAt() {
|
|
231
|
-
return this.documentLock?.lock?.updatedAt;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
get canTakeOverLock() {
|
|
235
|
-
return this.lockedByAnotherEditor && this.documentLock?.canTakeOver === true;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
275
|
get headingField() {
|
|
239
|
-
return
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
get titleField() {
|
|
243
|
-
if (this.globalResource) return undefined;
|
|
244
|
-
return this.headingField?.type === "text" && this.headingField.admin.row === undefined
|
|
245
|
-
? this.headingField
|
|
246
|
-
: undefined;
|
|
276
|
+
return this.uploadCollection
|
|
277
|
+
? undefined
|
|
278
|
+
: documentTitleField(this.collection, (path) => this.form.canRead(path));
|
|
247
279
|
}
|
|
248
280
|
|
|
249
281
|
get documentFields() {
|
|
250
282
|
return (this.collection?.fields ?? []).filter(
|
|
251
283
|
(field) =>
|
|
252
284
|
this.form.canRead(field.path) &&
|
|
253
|
-
!(this.uploadCollection &&
|
|
285
|
+
!(this.uploadCollection && isUploadMetadataField(field.name))
|
|
254
286
|
);
|
|
255
287
|
}
|
|
256
288
|
|
|
257
289
|
get canDelete() {
|
|
258
|
-
return !this.lockedByAnotherEditor && this.form.access?.operations.delete === true;
|
|
290
|
+
return !this.lock.lockedByAnotherEditor && this.form.access?.operations.delete === true;
|
|
259
291
|
}
|
|
260
292
|
|
|
261
293
|
get canDuplicate() {
|
|
262
|
-
return !this.lockedByAnotherEditor && this.form.access?.operations.duplicate === true;
|
|
294
|
+
return !this.lock.lockedByAnotherEditor && this.form.access?.operations.duplicate === true;
|
|
263
295
|
}
|
|
264
296
|
|
|
265
297
|
get canPublish() {
|
|
266
|
-
return
|
|
298
|
+
return (
|
|
299
|
+
!this.lock.lockedByAnotherEditor &&
|
|
300
|
+
!this.form.writeBlocked &&
|
|
301
|
+
this.form.access?.operations.publish === true
|
|
302
|
+
);
|
|
267
303
|
}
|
|
268
304
|
|
|
269
305
|
get canUnpublish() {
|
|
270
|
-
return
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
const status = this.pendingRestore?.Status;
|
|
276
|
-
return status === "draft"
|
|
277
|
-
? this.form.access?.operations.unpublish === true
|
|
278
|
-
: this.form.access?.operations.publish === true;
|
|
306
|
+
return (
|
|
307
|
+
!this.lock.lockedByAnotherEditor &&
|
|
308
|
+
!this.form.writeBlocked &&
|
|
309
|
+
this.form.access?.operations.unpublish === true
|
|
310
|
+
);
|
|
279
311
|
}
|
|
280
312
|
|
|
281
313
|
get canReadVersions() {
|
|
282
314
|
return !this.creating && this.form.access?.operations.readVersions === true;
|
|
283
315
|
}
|
|
284
316
|
|
|
285
|
-
get
|
|
317
|
+
get canEditUpload() {
|
|
286
318
|
return (
|
|
287
319
|
this.uploadCollection &&
|
|
288
|
-
!this.
|
|
289
|
-
!this.lockedByAnotherEditor &&
|
|
320
|
+
!this.lock.lockedByAnotherEditor &&
|
|
290
321
|
!this.form.submitting &&
|
|
291
|
-
!this.
|
|
292
|
-
this.
|
|
293
|
-
this.
|
|
294
|
-
|
|
295
|
-
this.
|
|
296
|
-
this.form.access?.operations.publish === true)
|
|
322
|
+
!this.saveOutcomeUncertain &&
|
|
323
|
+
!this.form.writeBlocked &&
|
|
324
|
+
(this.creating
|
|
325
|
+
? this.form.access?.operations.create === true
|
|
326
|
+
: this.form.access?.operations.update === true)
|
|
297
327
|
);
|
|
298
328
|
}
|
|
299
329
|
|
|
300
330
|
get validationFields() {
|
|
301
331
|
return (this.collection?.fields ?? []).filter(
|
|
302
|
-
(field) => !(this.uploadCollection &&
|
|
332
|
+
(field) => !(this.uploadCollection && isUploadMetadataField(field.name))
|
|
303
333
|
);
|
|
304
334
|
}
|
|
305
335
|
|
|
@@ -312,18 +342,8 @@ export class DocumentController {
|
|
|
312
342
|
|
|
313
343
|
get documentHeading() {
|
|
314
344
|
if (this.globalResource) return this.collectionSingularLabel;
|
|
315
|
-
if (this.uploadCollection)
|
|
316
|
-
|
|
317
|
-
if (typeof filename === "string" && filename.trim().length > 0) return filename;
|
|
318
|
-
if (this.selectedFile !== undefined) return this.selectedFile.name;
|
|
319
|
-
return this.creating
|
|
320
|
-
? this.options.runtime.i18n.t("documents:newLabel", {
|
|
321
|
-
label: this.collectionSingularLabel.toLocaleLowerCase(
|
|
322
|
-
this.options.runtime.i18n.language
|
|
323
|
-
),
|
|
324
|
-
})
|
|
325
|
-
: this.options.runtime.i18n.t("documents:untitledAsset");
|
|
326
|
-
}
|
|
345
|
+
if (this.uploadCollection)
|
|
346
|
+
return this.upload.filename || this.options.runtime.i18n.t("documents:untitled");
|
|
327
347
|
const value =
|
|
328
348
|
this.headingField === undefined ? undefined : this.form.get(this.headingField.path);
|
|
329
349
|
const title = typeof value === "string" ? value.trim() : "";
|
|
@@ -347,39 +367,6 @@ export class DocumentController {
|
|
|
347
367
|
return this.#documentString("mimeType");
|
|
348
368
|
}
|
|
349
369
|
|
|
350
|
-
get assetSize() {
|
|
351
|
-
const value = this.#documentNumber("filesize");
|
|
352
|
-
if (value === undefined) return undefined;
|
|
353
|
-
if (value < 1_024)
|
|
354
|
-
return this.options.runtime.i18n.formatNumber(value, { style: "unit", unit: "byte" });
|
|
355
|
-
if (value < 1_048_576)
|
|
356
|
-
return this.options.runtime.i18n.formatNumber(value / 1_024, {
|
|
357
|
-
maximumFractionDigits: 0,
|
|
358
|
-
style: "unit",
|
|
359
|
-
unit: "kilobyte",
|
|
360
|
-
});
|
|
361
|
-
return this.options.runtime.i18n.formatNumber(value / 1_048_576, {
|
|
362
|
-
maximumFractionDigits: value < 10_485_760 ? 1 : 0,
|
|
363
|
-
style: "unit",
|
|
364
|
-
unit: "megabyte",
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
get assetDimensions() {
|
|
369
|
-
const width = this.#documentNumber("width");
|
|
370
|
-
const height = this.#documentNumber("height");
|
|
371
|
-
return width !== undefined && height !== undefined
|
|
372
|
-
? `${this.options.runtime.i18n.formatNumber(width)} × ${this.options.runtime.i18n.formatNumber(height)}`
|
|
373
|
-
: undefined;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
revealFirstIssue = () => {
|
|
377
|
-
const issue = this.form.issues[0];
|
|
378
|
-
if (issue === undefined) return undefined;
|
|
379
|
-
this.documentView = "edit";
|
|
380
|
-
return issue.path;
|
|
381
|
-
};
|
|
382
|
-
|
|
383
370
|
checkpointDraft = () => {
|
|
384
371
|
const collection = this.collection;
|
|
385
372
|
if (collection === undefined) return;
|
|
@@ -397,166 +384,121 @@ export class DocumentController {
|
|
|
397
384
|
const collection = this.collection;
|
|
398
385
|
if (collection !== undefined) clearFormDraft(collection.id, this.documentID);
|
|
399
386
|
this.form.reset($state.snapshot(this.form.original));
|
|
400
|
-
this.
|
|
387
|
+
this.upload.reset(this.currentDocument);
|
|
401
388
|
};
|
|
402
389
|
|
|
403
|
-
|
|
404
|
-
const
|
|
405
|
-
if (
|
|
390
|
+
forceUnlock = async () => {
|
|
391
|
+
const { runtime, notifications } = this.options;
|
|
392
|
+
if (this.collection === undefined || this.currentDocument === undefined) return;
|
|
406
393
|
const request = new AbortController();
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
takeOverLock = async () => {
|
|
411
|
-
const documentID = this.documentID;
|
|
412
|
-
if (
|
|
413
|
-
documentID === undefined ||
|
|
414
|
-
this.collection?.capabilities.locking !== true ||
|
|
415
|
-
!this.canTakeOverLock
|
|
416
|
-
)
|
|
417
|
-
return false;
|
|
418
|
-
this.lockOperation = true;
|
|
394
|
+
this.#forceUnlockRequest?.abort();
|
|
395
|
+
this.#forceUnlockRequest = request;
|
|
396
|
+
this.unlockOperation = true;
|
|
419
397
|
try {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
documentID,
|
|
423
|
-
true
|
|
424
|
-
);
|
|
425
|
-
this.#applyLock(state);
|
|
426
|
-
this.options.notifications.success({
|
|
427
|
-
title: this.options.runtime.i18n.t("documents:lockTakenOver"),
|
|
398
|
+
await runtime.client.forceUnlock(this.collection.slug, this.currentDocument.id, {
|
|
399
|
+
signal: request.signal,
|
|
428
400
|
});
|
|
429
|
-
|
|
401
|
+
if (request.signal.aborted) return;
|
|
402
|
+
notifications.success({ title: runtime.i18n.t("documents:accountUnlocked") });
|
|
430
403
|
} catch (cause) {
|
|
431
|
-
|
|
432
|
-
|
|
404
|
+
if (request.signal.aborted) return;
|
|
405
|
+
notifications.error({
|
|
406
|
+
title: runtime.i18n.t("documents:accountUnlockFailed"),
|
|
433
407
|
message: cause instanceof Error ? cause.message : undefined,
|
|
434
408
|
});
|
|
435
|
-
return false;
|
|
436
409
|
} finally {
|
|
437
|
-
|
|
410
|
+
if (!request.signal.aborted) {
|
|
411
|
+
this.#forceUnlockRequest = undefined;
|
|
412
|
+
this.unlockOperation = false;
|
|
413
|
+
}
|
|
438
414
|
}
|
|
439
415
|
};
|
|
440
416
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
if (documentID === undefined || !this.canEditImage || this.imageOperation) return false;
|
|
451
|
-
const routeKey = this.#activeRouteKey;
|
|
452
|
-
const collectionSlug = this.collectionSlug;
|
|
417
|
+
copyFromLocale = async (source: string) => {
|
|
418
|
+
const { runtime, notifications } = this.options;
|
|
419
|
+
if (
|
|
420
|
+
this.contentLocale === undefined ||
|
|
421
|
+
source === this.contentLocale ||
|
|
422
|
+
this.currentDocument === undefined ||
|
|
423
|
+
this.copyLocaleOperation
|
|
424
|
+
)
|
|
425
|
+
return;
|
|
453
426
|
const request = new AbortController();
|
|
454
|
-
this
|
|
455
|
-
|
|
456
|
-
this
|
|
427
|
+
const destinationLocale = this.contentLocale;
|
|
428
|
+
const document = this.currentDocument;
|
|
429
|
+
this.#copyLocaleRequest?.abort();
|
|
430
|
+
this.#copyLocaleRequest = request;
|
|
431
|
+
this.copyLocaleOperation = true;
|
|
457
432
|
try {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
433
|
+
if (this.globalResource) {
|
|
434
|
+
await runtime.client.copyGlobalLocale(
|
|
435
|
+
this.collectionSlug,
|
|
436
|
+
{ from: source, to: destinationLocale },
|
|
437
|
+
{ revision: document._revision, signal: request.signal }
|
|
438
|
+
);
|
|
439
|
+
} else {
|
|
440
|
+
await runtime.client.copyLocale(
|
|
441
|
+
this.collectionSlug,
|
|
442
|
+
document.id,
|
|
443
|
+
{ from: source, to: destinationLocale },
|
|
444
|
+
{ revision: document._revision, signal: request.signal }
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
if (request.signal.aborted) return;
|
|
448
|
+
await this.refresh();
|
|
449
|
+
if (request.signal.aborted) return;
|
|
450
|
+
runtime.documentsChanged();
|
|
451
|
+
notifications.success({
|
|
452
|
+
title: runtime.i18n.t("documents:localeCopied"),
|
|
453
|
+
message: runtime.i18n.t("documents:localeCopiedDescription", {
|
|
454
|
+
source,
|
|
455
|
+
destination: destinationLocale,
|
|
456
|
+
}),
|
|
482
457
|
});
|
|
483
|
-
await Promise.allSettled([
|
|
484
|
-
this.#refreshVersions(documentID),
|
|
485
|
-
this.#refreshAccess(documentID),
|
|
486
|
-
]);
|
|
487
|
-
return true;
|
|
488
458
|
} catch (cause) {
|
|
489
|
-
if (request.signal.aborted
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
try {
|
|
493
|
-
const refreshed = await this.options.runtime.client.find(collectionSlug, documentID, {
|
|
494
|
-
signal: request.signal,
|
|
495
|
-
locale: this.contentLocale,
|
|
496
|
-
});
|
|
497
|
-
if (request.signal.aborted || this.#activeRouteKey !== routeKey) return false;
|
|
498
|
-
this.#applyImageDocument(refreshed);
|
|
499
|
-
this.imageOutcomeUncertain = false;
|
|
500
|
-
await Promise.allSettled([
|
|
501
|
-
this.#refreshVersions(documentID),
|
|
502
|
-
this.#refreshAccess(documentID),
|
|
503
|
-
]);
|
|
504
|
-
if (imageEditMatches(refreshed, input)) {
|
|
505
|
-
this.options.notifications.success({
|
|
506
|
-
title: this.options.runtime.i18n.t("uploads:imageSizesRegenerated"),
|
|
507
|
-
message: this.options.runtime.i18n.t("uploads:imageStateRecovered"),
|
|
508
|
-
});
|
|
509
|
-
return true;
|
|
510
|
-
}
|
|
511
|
-
this.options.notifications.error({
|
|
512
|
-
title: this.options.runtime.i18n.t("uploads:imageSizesNotRegenerated"),
|
|
513
|
-
message: this.options.runtime.i18n.t("uploads:serverStateRefreshed"),
|
|
514
|
-
});
|
|
515
|
-
return false;
|
|
516
|
-
} catch {
|
|
517
|
-
if (request.signal.aborted || this.#activeRouteKey !== routeKey) return false;
|
|
518
|
-
this.imageOutcomeUncertain = true;
|
|
519
|
-
this.options.notifications.error({
|
|
520
|
-
title: this.options.runtime.i18n.t("uploads:imageOutcomeUnknown"),
|
|
521
|
-
message: this.options.runtime.i18n.t("uploads:imageOutcomeUnknownDescription"),
|
|
522
|
-
});
|
|
523
|
-
return false;
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
this.options.notifications.error({
|
|
527
|
-
title: this.options.runtime.i18n.t("uploads:imageSizesNotRegenerated"),
|
|
459
|
+
if (request.signal.aborted) return;
|
|
460
|
+
notifications.error({
|
|
461
|
+
title: runtime.i18n.t("documents:localeNotCopied"),
|
|
528
462
|
message:
|
|
529
|
-
cause instanceof Error
|
|
530
|
-
? cause.message
|
|
531
|
-
: this.options.runtime.i18n.t("uploads:focalUpdateFailed"),
|
|
463
|
+
cause instanceof Error ? cause.message : runtime.i18n.t("documents:localeCopyFailed"),
|
|
532
464
|
});
|
|
533
|
-
return false;
|
|
534
465
|
} finally {
|
|
535
|
-
if (
|
|
536
|
-
this.#
|
|
537
|
-
this.
|
|
466
|
+
if (!request.signal.aborted) {
|
|
467
|
+
this.#copyLocaleRequest = undefined;
|
|
468
|
+
this.copyLocaleOperation = false;
|
|
538
469
|
}
|
|
539
470
|
}
|
|
540
471
|
};
|
|
541
472
|
|
|
473
|
+
refresh = async () => {
|
|
474
|
+
const documentID = this.documentID;
|
|
475
|
+
if (
|
|
476
|
+
documentID === undefined ||
|
|
477
|
+
this.collection === undefined ||
|
|
478
|
+
this.#saveRequest !== undefined ||
|
|
479
|
+
this.upload.busy ||
|
|
480
|
+
this.publicationOperation
|
|
481
|
+
)
|
|
482
|
+
return;
|
|
483
|
+
await this.#load(this.collection.slug, documentID);
|
|
484
|
+
};
|
|
485
|
+
|
|
542
486
|
save = async ({
|
|
543
487
|
silent = false,
|
|
544
488
|
password,
|
|
545
489
|
publish = false,
|
|
546
490
|
}: { silent?: boolean; password?: string; publish?: boolean } = {}) => {
|
|
547
491
|
if (!this.canSave) return false;
|
|
548
|
-
const publishingChanges =
|
|
549
|
-
publish && !this.creating && this.versionedCollection && this.currentStatus === "published";
|
|
492
|
+
const publishingChanges = publish && !this.creating && this.versionedCollection;
|
|
550
493
|
if (publishingChanges && !this.canPublish) return false;
|
|
551
|
-
const creatingUpload = this.creating && this.uploadCollection;
|
|
552
|
-
const routeKey = this.#activeRouteKey;
|
|
553
494
|
const collectionSlug = this.collectionSlug;
|
|
554
495
|
const documentID = this.documentID;
|
|
555
496
|
const request = new AbortController();
|
|
556
497
|
this.#saveRequest?.abort();
|
|
557
498
|
this.#saveRequest = request;
|
|
499
|
+
this.#cancelLoad();
|
|
500
|
+
const wasCreating = this.creating;
|
|
558
501
|
try {
|
|
559
|
-
const wasCreating = this.creating;
|
|
560
502
|
const saved = await this.form.submit(this.validationFields, this.creating, async (values) => {
|
|
561
503
|
if (this.globalResource) {
|
|
562
504
|
if (publishingChanges) {
|
|
@@ -573,6 +515,20 @@ export class DocumentController {
|
|
|
573
515
|
});
|
|
574
516
|
}
|
|
575
517
|
if (documentID !== undefined) {
|
|
518
|
+
if (this.uploadCollection) {
|
|
519
|
+
return this.options.runtime.client.updateUpload(
|
|
520
|
+
collectionSlug,
|
|
521
|
+
documentID,
|
|
522
|
+
{
|
|
523
|
+
data: values,
|
|
524
|
+
file: this.upload.file,
|
|
525
|
+
filename: this.upload.filename,
|
|
526
|
+
image: this.upload.image,
|
|
527
|
+
publish: publishingChanges,
|
|
528
|
+
},
|
|
529
|
+
{ revision: this.currentRevision, signal: request.signal, locale: this.contentLocale }
|
|
530
|
+
);
|
|
531
|
+
}
|
|
576
532
|
if (publishingChanges) {
|
|
577
533
|
return this.options.runtime.client.publishChanges(collectionSlug, documentID, values, {
|
|
578
534
|
revision: this.currentRevision,
|
|
@@ -606,15 +562,18 @@ export class DocumentController {
|
|
|
606
562
|
if (this.selectedFile === undefined)
|
|
607
563
|
throw new Error(this.options.runtime.i18n.t("uploads:chooseFileBeforeSaving"));
|
|
608
564
|
return this.options.runtime.client.upload(collectionSlug, this.selectedFile, {
|
|
565
|
+
filename: this.upload.filename,
|
|
566
|
+
image: this.upload.image,
|
|
567
|
+
publish,
|
|
609
568
|
data: values,
|
|
610
569
|
signal: request.signal,
|
|
611
570
|
locale: this.contentLocale,
|
|
612
571
|
});
|
|
613
572
|
});
|
|
614
|
-
if (request.signal.aborted
|
|
573
|
+
if (request.signal.aborted) return false;
|
|
615
574
|
this.saveOutcomeUncertain = false;
|
|
616
575
|
this.#applyDocument(saved);
|
|
617
|
-
|
|
576
|
+
this.lastSavedAt = Date.now();
|
|
618
577
|
this.options.runtime.documentsChanged();
|
|
619
578
|
clearFormDraft(this.collection?.id ?? this.collectionSlug, this.documentID);
|
|
620
579
|
if (!silent) {
|
|
@@ -632,18 +591,19 @@ export class DocumentController {
|
|
|
632
591
|
// Let the route clear any blocked departure now that the successful save has
|
|
633
592
|
// removed both form and upload dirtiness before replacing the create URL.
|
|
634
593
|
await tick();
|
|
594
|
+
if (request.signal.aborted) return false;
|
|
635
595
|
this.options.navigate(
|
|
636
596
|
withContentLocale(documentPath(this.collectionSlug, saved.id), this.contentLocale),
|
|
637
597
|
{ replace: true }
|
|
638
598
|
);
|
|
639
599
|
} else {
|
|
640
|
-
await
|
|
600
|
+
await this.#refreshAccess(saved.id);
|
|
641
601
|
}
|
|
642
|
-
return
|
|
602
|
+
return !request.signal.aborted;
|
|
643
603
|
} catch (cause) {
|
|
644
|
-
if (request.signal.aborted
|
|
604
|
+
if (request.signal.aborted) return false;
|
|
645
605
|
if (
|
|
646
|
-
|
|
606
|
+
this.uploadCollection &&
|
|
647
607
|
!(cause instanceof FormValidationError) &&
|
|
648
608
|
(!(cause instanceof RiduError) || cause.status >= 500)
|
|
649
609
|
) {
|
|
@@ -651,7 +611,11 @@ export class DocumentController {
|
|
|
651
611
|
if (!silent) {
|
|
652
612
|
this.options.notifications.error({
|
|
653
613
|
title: this.options.runtime.i18n.t("uploads:outcomeUnknown"),
|
|
654
|
-
message: this.options.runtime.i18n.t(
|
|
614
|
+
message: this.options.runtime.i18n.t(
|
|
615
|
+
wasCreating
|
|
616
|
+
? "uploads:outcomeUnknownDescription"
|
|
617
|
+
: "uploads:saveOutcomeUnknownDescription"
|
|
618
|
+
),
|
|
655
619
|
});
|
|
656
620
|
}
|
|
657
621
|
return false;
|
|
@@ -686,8 +650,19 @@ export class DocumentController {
|
|
|
686
650
|
};
|
|
687
651
|
|
|
688
652
|
changePublication = async (next: "draft" | "published") => {
|
|
689
|
-
|
|
690
|
-
|
|
653
|
+
const allowed = next === "published" ? this.canPublish : this.canUnpublish;
|
|
654
|
+
if (
|
|
655
|
+
this.documentID === undefined ||
|
|
656
|
+
!allowed ||
|
|
657
|
+
this.form.submitting ||
|
|
658
|
+
this.publicationOperation ||
|
|
659
|
+
this.upload.busy ||
|
|
660
|
+
this.saveOutcomeUncertain
|
|
661
|
+
)
|
|
662
|
+
return;
|
|
663
|
+
this.#cancelLoad();
|
|
664
|
+
this.publicationOperation = true;
|
|
665
|
+
const generation = this.#routeGeneration;
|
|
691
666
|
try {
|
|
692
667
|
const saved =
|
|
693
668
|
next === "published"
|
|
@@ -709,12 +684,12 @@ export class DocumentController {
|
|
|
709
684
|
revision: this.currentRevision,
|
|
710
685
|
locale: this.contentLocale,
|
|
711
686
|
});
|
|
687
|
+
if (generation !== this.#routeGeneration) return;
|
|
712
688
|
this.#applyDocument(saved);
|
|
689
|
+
this.lastSavedAt = Date.now();
|
|
713
690
|
this.options.runtime.documentsChanged();
|
|
714
|
-
await
|
|
715
|
-
|
|
716
|
-
this.#refreshAccess(this.documentID),
|
|
717
|
-
]);
|
|
691
|
+
await this.#refreshAccess(this.documentID);
|
|
692
|
+
if (generation !== this.#routeGeneration) return;
|
|
718
693
|
this.options.notifications.success({
|
|
719
694
|
title: this.options.runtime.i18n.t(
|
|
720
695
|
next === "published" ? "documents:publishedTitle" : "documents:unpublishedTitle"
|
|
@@ -726,6 +701,7 @@ export class DocumentController {
|
|
|
726
701
|
}),
|
|
727
702
|
});
|
|
728
703
|
} catch (cause) {
|
|
704
|
+
if (generation !== this.#routeGeneration) return;
|
|
729
705
|
this.options.notifications.error({
|
|
730
706
|
title: this.options.runtime.i18n.t("documents:statusNotChanged"),
|
|
731
707
|
message:
|
|
@@ -734,13 +710,15 @@ export class DocumentController {
|
|
|
734
710
|
: this.options.runtime.i18n.t("documents:statusChangeFailed"),
|
|
735
711
|
});
|
|
736
712
|
} finally {
|
|
737
|
-
this.
|
|
713
|
+
if (generation === this.#routeGeneration) this.publicationOperation = false;
|
|
738
714
|
}
|
|
739
715
|
};
|
|
740
716
|
|
|
741
717
|
duplicate = async () => {
|
|
742
|
-
if (this.documentID === undefined || this.globalResource || this.lockedByAnotherEditor)
|
|
718
|
+
if (this.documentID === undefined || this.globalResource || this.lock.lockedByAnotherEditor)
|
|
719
|
+
return;
|
|
743
720
|
this.duplicateOperation = true;
|
|
721
|
+
const generation = this.#routeGeneration;
|
|
744
722
|
try {
|
|
745
723
|
const duplicated = await this.options.runtime.client.duplicate(
|
|
746
724
|
this.collectionSlug,
|
|
@@ -748,6 +726,7 @@ export class DocumentController {
|
|
|
748
726
|
{},
|
|
749
727
|
{ locale: this.contentLocale }
|
|
750
728
|
);
|
|
729
|
+
if (generation !== this.#routeGeneration) return;
|
|
751
730
|
this.options.runtime.documentsChanged();
|
|
752
731
|
this.options.notifications.success({
|
|
753
732
|
title: this.options.runtime.i18n.t("documents:duplicatedLabel", {
|
|
@@ -759,6 +738,7 @@ export class DocumentController {
|
|
|
759
738
|
withContentLocale(documentPath(this.collectionSlug, duplicated.id), this.contentLocale)
|
|
760
739
|
);
|
|
761
740
|
} catch (cause) {
|
|
741
|
+
if (generation !== this.#routeGeneration) return;
|
|
762
742
|
this.options.notifications.error({
|
|
763
743
|
title: this.options.runtime.i18n.t("documents:notDuplicated"),
|
|
764
744
|
message:
|
|
@@ -767,57 +747,17 @@ export class DocumentController {
|
|
|
767
747
|
: this.options.runtime.i18n.t("documents:duplicateFailed"),
|
|
768
748
|
});
|
|
769
749
|
} finally {
|
|
770
|
-
this.duplicateOperation = false;
|
|
771
|
-
}
|
|
772
|
-
};
|
|
773
|
-
|
|
774
|
-
restore = async (version: AdminVersion) => {
|
|
775
|
-
if (this.documentID === undefined || this.lockedByAnotherEditor || !this.canRestoreVersion)
|
|
776
|
-
return;
|
|
777
|
-
this.versionOperation = true;
|
|
778
|
-
try {
|
|
779
|
-
const saved = this.globalResource
|
|
780
|
-
? await this.options.runtime.client.restoreGlobal(this.collectionSlug, version.Revision, {
|
|
781
|
-
revision: this.currentRevision,
|
|
782
|
-
locale: this.contentLocale,
|
|
783
|
-
})
|
|
784
|
-
: await this.options.runtime.client.restore(
|
|
785
|
-
this.collectionSlug,
|
|
786
|
-
this.documentID,
|
|
787
|
-
version.Revision,
|
|
788
|
-
{ revision: this.currentRevision, locale: this.contentLocale }
|
|
789
|
-
);
|
|
790
|
-
this.#applyDocument(saved);
|
|
791
|
-
this.options.runtime.documentsChanged();
|
|
792
|
-
await Promise.allSettled([
|
|
793
|
-
this.#refreshVersions(this.documentID),
|
|
794
|
-
this.#refreshAccess(this.documentID),
|
|
795
|
-
]);
|
|
796
|
-
this.options.notifications.success({
|
|
797
|
-
title: this.options.runtime.i18n.t("versions:revisionRestoredTitle"),
|
|
798
|
-
message: this.options.runtime.i18n.t("versions:revisionIsCurrent", {
|
|
799
|
-
revision: this.options.runtime.i18n.formatNumber(version.Revision),
|
|
800
|
-
}),
|
|
801
|
-
});
|
|
802
|
-
} catch (cause) {
|
|
803
|
-
this.options.notifications.error({
|
|
804
|
-
title: this.options.runtime.i18n.t("versions:revisionNotRestored"),
|
|
805
|
-
message:
|
|
806
|
-
cause instanceof Error
|
|
807
|
-
? cause.message
|
|
808
|
-
: this.options.runtime.i18n.t("versions:revisionRestoreFailed"),
|
|
809
|
-
});
|
|
810
|
-
} finally {
|
|
811
|
-
this.versionOperation = false;
|
|
812
|
-
this.restoreDialogOpen = false;
|
|
813
|
-
this.pendingRestore = undefined;
|
|
750
|
+
if (generation === this.#routeGeneration) this.duplicateOperation = false;
|
|
814
751
|
}
|
|
815
752
|
};
|
|
816
753
|
|
|
817
754
|
remove = async () => {
|
|
818
|
-
if (this.documentID === undefined || this.lockedByAnotherEditor) return;
|
|
755
|
+
if (this.documentID === undefined || this.lock.lockedByAnotherEditor) return;
|
|
756
|
+
this.#cancelLoad();
|
|
757
|
+
const generation = this.#routeGeneration;
|
|
819
758
|
try {
|
|
820
759
|
await this.options.runtime.client.delete(this.collectionSlug, this.documentID);
|
|
760
|
+
if (generation !== this.#routeGeneration) return;
|
|
821
761
|
this.options.runtime.documentsChanged();
|
|
822
762
|
this.deleteDialogOpen = false;
|
|
823
763
|
this.options.notifications.success({
|
|
@@ -834,6 +774,7 @@ export class DocumentController {
|
|
|
834
774
|
});
|
|
835
775
|
this.options.navigate(collectionPath(this.collectionSlug));
|
|
836
776
|
} catch (cause) {
|
|
777
|
+
if (generation !== this.#routeGeneration) return;
|
|
837
778
|
this.options.notifications.error({
|
|
838
779
|
title: this.options.runtime.i18n.t("documents:notDeleted"),
|
|
839
780
|
message:
|
|
@@ -844,64 +785,81 @@ export class DocumentController {
|
|
|
844
785
|
}
|
|
845
786
|
};
|
|
846
787
|
|
|
847
|
-
requestRestore = (version: AdminVersion) => {
|
|
848
|
-
this.pendingRestore = version;
|
|
849
|
-
this.restoreDialogOpen = true;
|
|
850
|
-
};
|
|
851
|
-
|
|
852
|
-
versionDate = (version: AdminVersion) => this.#formatDate(version.CreatedAt);
|
|
853
|
-
|
|
854
788
|
documentDate = (value: string | undefined) =>
|
|
855
|
-
value === undefined ? "—" :
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
this
|
|
861
|
-
this
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
if (this.#copyResetTimer !== undefined) window.clearTimeout(this.#copyResetTimer);
|
|
866
|
-
this.#copyResetTimer = window.setTimeout(() => {
|
|
867
|
-
this.copiedAssetURL = false;
|
|
868
|
-
this.#copyResetTimer = undefined;
|
|
869
|
-
}, 1_500);
|
|
870
|
-
};
|
|
789
|
+
value === undefined ? "—" : formatAdminDateTime(value, this.options.runtime.i18n);
|
|
790
|
+
|
|
791
|
+
#cancelRouteRequests() {
|
|
792
|
+
this.#forceUnlockRequest?.abort();
|
|
793
|
+
this.#copyLocaleRequest?.abort();
|
|
794
|
+
this.#forceUnlockRequest = undefined;
|
|
795
|
+
this.#copyLocaleRequest = undefined;
|
|
796
|
+
this.unlockOperation = false;
|
|
797
|
+
this.copyLocaleOperation = false;
|
|
798
|
+
}
|
|
871
799
|
|
|
872
|
-
#enterRoute(
|
|
873
|
-
|
|
874
|
-
|
|
800
|
+
#enterRoute(
|
|
801
|
+
slug: string,
|
|
802
|
+
documentID: string | undefined,
|
|
803
|
+
locale: string | undefined,
|
|
804
|
+
manifestRevision: number,
|
|
805
|
+
global: boolean,
|
|
806
|
+
editable: boolean,
|
|
807
|
+
prepared?: AdminDocumentDataV1 | AdminCreateDataV1
|
|
808
|
+
) {
|
|
809
|
+
const resourceKey = `${global ? "global" : "collection"}:${slug}:${documentID ?? "new"}`;
|
|
810
|
+
const ownerKey = `${resourceKey}:${locale ?? "default"}`;
|
|
811
|
+
const routeKey = `${ownerKey}:${editable ? "edit" : "read"}`;
|
|
812
|
+
const sameOwner = this.#activeRouteKey.startsWith(`${ownerKey}:`);
|
|
875
813
|
if (
|
|
876
814
|
this.#activeRouteKey.startsWith(`${resourceKey}:`) &&
|
|
877
815
|
this.#activeRouteKey !== routeKey &&
|
|
878
816
|
this.hasUnsavedChanges
|
|
879
817
|
) {
|
|
818
|
+
if (manifestRevision !== this.#appliedManifestRevision) {
|
|
819
|
+
this.#appliedManifestRevision = manifestRevision;
|
|
820
|
+
this.#applySchemaUpdate();
|
|
821
|
+
}
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
if (this.#activeRouteKey === routeKey && this.collection !== undefined) {
|
|
825
|
+
if (manifestRevision !== this.#appliedManifestRevision) {
|
|
826
|
+
this.#appliedManifestRevision = manifestRevision;
|
|
827
|
+
this.#applySchemaUpdate();
|
|
828
|
+
} else if (prepared !== undefined && !this.hasUnsavedChanges) {
|
|
829
|
+
this.#load(slug, documentID, false, prepared);
|
|
830
|
+
}
|
|
880
831
|
return;
|
|
881
832
|
}
|
|
833
|
+
this.#appliedManifestRevision = manifestRevision;
|
|
834
|
+
if (this.#activeRouteKey !== routeKey && !sameOwner) {
|
|
835
|
+
// A later visit to the same URL is a different owner for mutation completions.
|
|
836
|
+
this.#routeGeneration += 1;
|
|
837
|
+
this.#cancelRouteRequests();
|
|
838
|
+
this.publicationOperation = false;
|
|
839
|
+
this.duplicateOperation = false;
|
|
840
|
+
this.deleteDialogOpen = false;
|
|
841
|
+
this.currentDocument = undefined;
|
|
842
|
+
this.lastSavedAt = undefined;
|
|
843
|
+
this.upload.reset(this.currentDocument);
|
|
844
|
+
}
|
|
845
|
+
this.#cancelLoad();
|
|
882
846
|
this.#activeContentLocale = locale;
|
|
883
|
-
if (this.#activeRouteKey !== "" && this.#activeRouteKey !== routeKey) {
|
|
847
|
+
if (this.#activeRouteKey !== "" && this.#activeRouteKey !== routeKey && !sameOwner) {
|
|
884
848
|
this.#saveRequest?.abort();
|
|
885
849
|
this.#saveRequest = undefined;
|
|
886
|
-
this.#imageRequest?.abort();
|
|
887
|
-
this.#imageRequest = undefined;
|
|
888
|
-
this.imageOperation = false;
|
|
889
850
|
}
|
|
890
851
|
const collection = (
|
|
891
|
-
this.options.
|
|
892
|
-
? this.options.runtime.manifest?.globals
|
|
893
|
-
: this.options.runtime.manifest?.collections
|
|
852
|
+
global ? this.options.runtime.manifest?.globals : this.options.runtime.manifest?.collections
|
|
894
853
|
)?.find((item) => item.slug === slug);
|
|
895
854
|
if (collection === undefined) {
|
|
896
855
|
this.#activeRouteKey = routeKey;
|
|
897
|
-
this
|
|
856
|
+
this.lock.release();
|
|
898
857
|
this.collection = undefined;
|
|
899
858
|
this.collectionAvailable = false;
|
|
900
859
|
this.currentDocument = undefined;
|
|
901
|
-
this.
|
|
902
|
-
this.versions = [];
|
|
860
|
+
this.upload.reset(this.currentDocument);
|
|
903
861
|
this.form.reset({});
|
|
904
|
-
this.form.setResource({ collection: slug, id: documentID, global
|
|
862
|
+
this.form.setResource({ collection: slug, id: documentID, global });
|
|
905
863
|
this.loading = false;
|
|
906
864
|
this.error = this.options.runtime.i18n.t("documents:collectionUnavailable");
|
|
907
865
|
this.form.setAccess(undefined, documentID === undefined ? "create" : "update");
|
|
@@ -911,43 +869,24 @@ export class DocumentController {
|
|
|
911
869
|
this.form.setResource({
|
|
912
870
|
collection: collection.slug,
|
|
913
871
|
id: documentID,
|
|
914
|
-
global
|
|
872
|
+
global,
|
|
915
873
|
});
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
(this.#lockRoute.collection !== collection.slug || this.#lockRoute.documentID !== documentID)
|
|
919
|
-
) {
|
|
920
|
-
this.#releaseLock();
|
|
921
|
-
}
|
|
874
|
+
this.lock.releaseUnless(collection.slug, documentID);
|
|
875
|
+
if (!editable) this.lock.release();
|
|
922
876
|
this.collectionAvailable = true;
|
|
923
|
-
if (this.#activeRouteKey === routeKey) {
|
|
924
|
-
if (!this.loading || documentID === undefined) return;
|
|
925
|
-
const request = new AbortController();
|
|
926
|
-
this.#load(collection.slug, documentID, request.signal);
|
|
927
|
-
return () => request.abort();
|
|
928
|
-
}
|
|
929
|
-
|
|
930
877
|
this.#activeRouteKey = routeKey;
|
|
931
878
|
this.saveOutcomeUncertain = false;
|
|
932
|
-
this.imageOutcomeUncertain = false;
|
|
933
879
|
this.form.setAccess(undefined, documentID === undefined ? "create" : "update");
|
|
934
880
|
if (documentID === undefined) {
|
|
935
881
|
this.form.reset(initialFormValues(collection.fields), collection.fields);
|
|
936
882
|
this.form.setLocalization(locale);
|
|
937
|
-
this.versions = [];
|
|
938
883
|
this.currentDocument = undefined;
|
|
939
|
-
this.
|
|
940
|
-
this.currentStatus = this.draftsCollection ? "draft" : "published";
|
|
941
|
-
this.documentLock = undefined;
|
|
942
|
-
this.form.writeBlocked = false;
|
|
884
|
+
this.lock.release();
|
|
943
885
|
this.#restoreDraft();
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
return () => request.abort();
|
|
886
|
+
this.#load(collection.slug, undefined, false, prepared);
|
|
887
|
+
return;
|
|
947
888
|
}
|
|
948
|
-
|
|
949
|
-
this.#load(collection.slug, documentID, request.signal);
|
|
950
|
-
return () => request.abort();
|
|
889
|
+
this.#load(collection.slug, documentID, false, prepared);
|
|
951
890
|
}
|
|
952
891
|
|
|
953
892
|
#applySchemaUpdate() {
|
|
@@ -958,17 +897,32 @@ export class DocumentController {
|
|
|
958
897
|
? this.options.runtime.manifest?.globals
|
|
959
898
|
: this.options.runtime.manifest?.collections
|
|
960
899
|
)?.find((item) => item.id === previous.id);
|
|
900
|
+
this.#cancelLoad();
|
|
901
|
+
this.#routeGeneration += 1;
|
|
902
|
+
this.#cancelRouteRequests();
|
|
903
|
+
this.#saveRequest?.abort();
|
|
904
|
+
this.publicationOperation = false;
|
|
905
|
+
this.duplicateOperation = false;
|
|
906
|
+
this.lock.pause();
|
|
907
|
+
const result = this.form.reconcile(
|
|
908
|
+
previous.fields,
|
|
909
|
+
next?.fields ?? previous.fields,
|
|
910
|
+
this.creating
|
|
911
|
+
);
|
|
961
912
|
if (next === undefined) {
|
|
913
|
+
this.lock.release();
|
|
962
914
|
this.collectionAvailable = false;
|
|
915
|
+
this.form.writeBlocked = true;
|
|
916
|
+
this.form.setAccess(undefined, this.creating ? "create" : "update");
|
|
963
917
|
this.options.notifications.error({
|
|
964
918
|
title: this.options.runtime.i18n.t("documents:collectionRemoved"),
|
|
965
919
|
});
|
|
966
920
|
return;
|
|
967
921
|
}
|
|
968
922
|
|
|
969
|
-
const result = this.form.reconcile(previous.fields, next.fields, this.creating);
|
|
970
923
|
this.collection = next;
|
|
971
924
|
this.collectionAvailable = true;
|
|
925
|
+
if (!next.capabilities.locking || previous.slug !== next.slug) this.lock.release();
|
|
972
926
|
if (result.detached.length > 0) {
|
|
973
927
|
this.options.notifications.warning({
|
|
974
928
|
title: this.options.runtime.i18n.t("documents:schemaUpdatedWithDraft"),
|
|
@@ -978,6 +932,15 @@ export class DocumentController {
|
|
|
978
932
|
});
|
|
979
933
|
}
|
|
980
934
|
if (previous.slug !== next.slug) {
|
|
935
|
+
// The resulting URL is the same editor under its renamed resource, so its
|
|
936
|
+
// next route synchronization must not reload and replace the retained draft.
|
|
937
|
+
const documentID = this.globalResource ? next.slug : this.documentID;
|
|
938
|
+
this.#activeRouteKey = `${this.globalResource ? "global" : "collection"}:${next.slug}:${documentID ?? "new"}:${this.contentLocale ?? "default"}:${this.options.editable ? "edit" : "read"}`;
|
|
939
|
+
this.form.setResource({
|
|
940
|
+
collection: next.slug,
|
|
941
|
+
id: documentID,
|
|
942
|
+
global: this.globalResource,
|
|
943
|
+
});
|
|
981
944
|
this.options.navigate(
|
|
982
945
|
withContentLocale(
|
|
983
946
|
this.globalResource
|
|
@@ -990,13 +953,21 @@ export class DocumentController {
|
|
|
990
953
|
{ replace: true }
|
|
991
954
|
);
|
|
992
955
|
}
|
|
956
|
+
// Field access is keyed by path, so the old envelope is invalid after reconciliation.
|
|
957
|
+
this.form.setAccess(undefined, this.creating ? "create" : "update");
|
|
958
|
+
this.form.writeBlocked = true;
|
|
959
|
+
this.#load(
|
|
960
|
+
next.slug,
|
|
961
|
+
this.globalResource ? next.slug : this.documentID,
|
|
962
|
+
this.currentDocument !== undefined
|
|
963
|
+
);
|
|
993
964
|
}
|
|
994
965
|
|
|
995
966
|
#restoreDraft() {
|
|
996
967
|
const collection = this.collection;
|
|
997
|
-
if (collection === undefined) return;
|
|
968
|
+
if (collection === undefined) return false;
|
|
998
969
|
const checkpoint = takeFormDraft(collection.id, this.documentID);
|
|
999
|
-
if (checkpoint === undefined) return;
|
|
970
|
+
if (checkpoint === undefined) return false;
|
|
1000
971
|
const result = this.form.recover(
|
|
1001
972
|
{ values: checkpoint.values, original: checkpoint.original },
|
|
1002
973
|
checkpoint.collection.fields,
|
|
@@ -1017,53 +988,81 @@ export class DocumentController {
|
|
|
1017
988
|
if (result.restoredFields === 0 || result.detached.length > 0)
|
|
1018
989
|
this.options.notifications.warning({ title, message });
|
|
1019
990
|
else this.options.notifications.success({ title });
|
|
991
|
+
return result.restoredFields > 0;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
#cancelLoad() {
|
|
995
|
+
this.#loadRequest?.abort();
|
|
996
|
+
this.#loadRequest = undefined;
|
|
997
|
+
this.loading = false;
|
|
1020
998
|
}
|
|
1021
999
|
|
|
1022
|
-
async #load(
|
|
1000
|
+
async #load(
|
|
1001
|
+
slug: string,
|
|
1002
|
+
documentID?: string,
|
|
1003
|
+
preserveDocument = false,
|
|
1004
|
+
prepared?: AdminDocumentDataV1 | AdminCreateDataV1
|
|
1005
|
+
) {
|
|
1006
|
+
this.#loadRequest?.abort();
|
|
1007
|
+
this.#loadRequest = undefined;
|
|
1008
|
+
if (prepared !== undefined && this.#adoptPreparedLoad(prepared, slug, documentID)) return;
|
|
1009
|
+
const request = new AbortController();
|
|
1010
|
+
this.#loadRequest = request;
|
|
1011
|
+
const { signal } = request;
|
|
1012
|
+
const global = this.globalResource;
|
|
1013
|
+
const locale = this.contentLocale;
|
|
1014
|
+
// A refresh may replace the draft it started with, never edits made while it was pending.
|
|
1015
|
+
const revision = this.form.revision;
|
|
1016
|
+
const uploadRevision = this.upload.revision;
|
|
1017
|
+
const changeGeneration = this.#formChangeGeneration;
|
|
1023
1018
|
this.loading = true;
|
|
1024
1019
|
this.error = undefined;
|
|
1025
1020
|
try {
|
|
1026
1021
|
const [document, access] = await Promise.all([
|
|
1027
|
-
|
|
1028
|
-
?
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
})
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
locale: this.contentLocale,
|
|
1035
|
-
}),
|
|
1036
|
-
this.globalResource
|
|
1037
|
-
? this.options.runtime.client.globalAccess(slug, {
|
|
1038
|
-
signal,
|
|
1039
|
-
locale: this.contentLocale,
|
|
1040
|
-
})
|
|
1022
|
+
documentID === undefined || preserveDocument
|
|
1023
|
+
? undefined
|
|
1024
|
+
: global
|
|
1025
|
+
? this.options.runtime.client.global(slug, { signal, locale })
|
|
1026
|
+
: this.options.runtime.client.find(slug, documentID, { signal, locale }),
|
|
1027
|
+
global
|
|
1028
|
+
? this.options.runtime.client.globalAccess(slug, { signal, locale })
|
|
1041
1029
|
: this.options.runtime.client.collectionAccess(slug, {
|
|
1042
1030
|
id: documentID,
|
|
1031
|
+
...(documentID === undefined ? { data: $state.snapshot(this.form.values) } : {}),
|
|
1043
1032
|
signal,
|
|
1044
|
-
locale
|
|
1033
|
+
locale,
|
|
1045
1034
|
}),
|
|
1046
1035
|
]);
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
this.
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
locale: this.contentLocale,
|
|
1058
|
-
});
|
|
1059
|
-
} else {
|
|
1060
|
-
this.versions = [];
|
|
1036
|
+
if (signal.aborted) return;
|
|
1037
|
+
const formUnchanged =
|
|
1038
|
+
this.form.revision === revision &&
|
|
1039
|
+
this.upload.revision === uploadRevision &&
|
|
1040
|
+
this.#formChangeGeneration === changeGeneration;
|
|
1041
|
+
this.form.setAccess(access, documentID === undefined ? "create" : "update");
|
|
1042
|
+
if (document !== undefined && formUnchanged) {
|
|
1043
|
+
const uncertain = this.saveOutcomeUncertain;
|
|
1044
|
+
if (uncertain) clearFormDraft(this.collection?.id ?? slug, documentID);
|
|
1045
|
+
this.#applyDocument(document, !uncertain);
|
|
1061
1046
|
}
|
|
1062
|
-
if (
|
|
1063
|
-
|
|
1047
|
+
if (
|
|
1048
|
+
documentID !== undefined &&
|
|
1049
|
+
!global &&
|
|
1050
|
+
this.options.editable &&
|
|
1051
|
+
this.collection?.capabilities.locking === true
|
|
1052
|
+
) {
|
|
1053
|
+
// The form is complete before the lock request starts. Writes remain
|
|
1054
|
+
// disabled until the server confirms ownership.
|
|
1055
|
+
this.form.writeBlocked = true;
|
|
1056
|
+
this.loading = false;
|
|
1057
|
+
await tick();
|
|
1058
|
+
if (signal.aborted || this.#loadRequest !== request) return;
|
|
1059
|
+
await this.lock.acquire(
|
|
1060
|
+
slug,
|
|
1061
|
+
documentID,
|
|
1062
|
+
this.collection.documentLockSettings?.durationSeconds ?? 120
|
|
1063
|
+
);
|
|
1064
1064
|
} else {
|
|
1065
|
-
this.
|
|
1066
|
-
this.form.writeBlocked = false;
|
|
1065
|
+
this.lock.release();
|
|
1067
1066
|
}
|
|
1068
1067
|
} catch (cause) {
|
|
1069
1068
|
if (signal.aborted) return;
|
|
@@ -1072,187 +1071,101 @@ export class DocumentController {
|
|
|
1072
1071
|
? cause.message
|
|
1073
1072
|
: this.options.runtime.i18n.t("documents:loadFailed");
|
|
1074
1073
|
} finally {
|
|
1075
|
-
if (
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
async #acquireLock(slug: string, documentID: string, signal?: AbortSignal) {
|
|
1080
|
-
const state = await this.options.runtime.client.acquireDocumentLock(slug, documentID, false, {
|
|
1081
|
-
signal,
|
|
1082
|
-
});
|
|
1083
|
-
if (signal?.aborted) return;
|
|
1084
|
-
this.#lockRoute = { collection: slug, documentID };
|
|
1085
|
-
this.#applyLock(state);
|
|
1086
|
-
if (state.owned) this.#startLockRefresh(slug, documentID);
|
|
1087
|
-
}
|
|
1088
|
-
|
|
1089
|
-
#applyLock(state: DocumentLockEnvelope) {
|
|
1090
|
-
this.documentLock = state;
|
|
1091
|
-
this.form.writeBlocked = state.lock !== null && !state.owned;
|
|
1092
|
-
if (state.owned && this.#lockRoute !== undefined) {
|
|
1093
|
-
this.#startLockRefresh(this.#lockRoute.collection, this.#lockRoute.documentID);
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
|
-
#startLockRefresh(collection: string, documentID: string) {
|
|
1098
|
-
if (this.#lockRefreshTimer !== undefined) window.clearInterval(this.#lockRefreshTimer);
|
|
1099
|
-
const interval = Math.max(
|
|
1100
|
-
5,
|
|
1101
|
-
Math.floor((this.collection?.documentLockSettings?.durationSeconds ?? 120) / 3)
|
|
1102
|
-
);
|
|
1103
|
-
this.#lockRefreshTimer = window.setInterval(async () => {
|
|
1104
|
-
try {
|
|
1105
|
-
const state = await this.options.runtime.client.acquireDocumentLock(collection, documentID);
|
|
1106
|
-
this.#applyLock(state);
|
|
1107
|
-
} catch {
|
|
1108
|
-
// A failed heartbeat is reflected by the next explicit request or save conflict.
|
|
1074
|
+
if (this.#loadRequest === request) {
|
|
1075
|
+
this.#loadRequest = undefined;
|
|
1076
|
+
this.loading = false;
|
|
1109
1077
|
}
|
|
1110
|
-
}
|
|
1078
|
+
}
|
|
1111
1079
|
}
|
|
1112
1080
|
|
|
1113
|
-
#
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1081
|
+
#adoptPreparedLoad(
|
|
1082
|
+
prepared: AdminDocumentDataV1 | AdminCreateDataV1,
|
|
1083
|
+
slug: string,
|
|
1084
|
+
documentID?: string
|
|
1085
|
+
) {
|
|
1086
|
+
if (
|
|
1087
|
+
"values" in prepared &&
|
|
1088
|
+
!samePreparedCreateValues(prepared.values, $state.snapshot(this.form.values))
|
|
1089
|
+
)
|
|
1090
|
+
return false;
|
|
1091
|
+
const document = "document" in prepared ? prepared.document : undefined;
|
|
1092
|
+
const access = prepared.access;
|
|
1093
|
+
this.loading = false;
|
|
1094
|
+
this.error = document?.error?.message ?? access.error?.message;
|
|
1095
|
+
if (this.error !== undefined) {
|
|
1096
|
+
this.form.setAccess(undefined, documentID === undefined ? "create" : "update");
|
|
1097
|
+
return true;
|
|
1117
1098
|
}
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
this.
|
|
1125
|
-
|
|
1126
|
-
|
|
1099
|
+
this.form.setAccess(access.value, documentID === undefined ? "create" : "update");
|
|
1100
|
+
if (document?.value !== undefined) this.#applyDocument(document.value, true);
|
|
1101
|
+
if (
|
|
1102
|
+
documentID !== undefined &&
|
|
1103
|
+
!this.globalResource &&
|
|
1104
|
+
this.options.editable &&
|
|
1105
|
+
this.collection?.capabilities.locking === true
|
|
1106
|
+
) {
|
|
1107
|
+
this.form.writeBlocked = true;
|
|
1108
|
+
this.#acquirePreparedLock(this.#routeGeneration, slug, documentID);
|
|
1109
|
+
} else {
|
|
1110
|
+
this.lock.release();
|
|
1127
1111
|
}
|
|
1112
|
+
return true;
|
|
1128
1113
|
}
|
|
1129
1114
|
|
|
1130
|
-
async #
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
cause instanceof Error
|
|
1144
|
-
? cause.message
|
|
1145
|
-
: this.options.runtime.i18n.t("documents:accessEvaluationFailed");
|
|
1146
|
-
}
|
|
1147
|
-
} finally {
|
|
1148
|
-
if (!signal.aborted) this.loading = false;
|
|
1149
|
-
}
|
|
1115
|
+
async #acquirePreparedLock(generation: number, slug: string, documentID: string) {
|
|
1116
|
+
await tick();
|
|
1117
|
+
if (
|
|
1118
|
+
generation !== this.#routeGeneration ||
|
|
1119
|
+
this.documentID !== documentID ||
|
|
1120
|
+
!this.options.editable
|
|
1121
|
+
)
|
|
1122
|
+
return;
|
|
1123
|
+
await this.lock.acquire(
|
|
1124
|
+
slug,
|
|
1125
|
+
documentID,
|
|
1126
|
+
this.collection?.documentLockSettings?.durationSeconds ?? 120
|
|
1127
|
+
);
|
|
1150
1128
|
}
|
|
1151
1129
|
|
|
1152
1130
|
async #refreshAccess(documentID: string) {
|
|
1153
|
-
const
|
|
1131
|
+
const generation = this.#routeGeneration;
|
|
1154
1132
|
const collectionSlug = this.collectionSlug;
|
|
1155
|
-
const
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
if (this.#activeRouteKey === routeKey) this.form.setAccess(access, "update");
|
|
1164
|
-
}
|
|
1165
|
-
|
|
1166
|
-
async #refreshVersions(documentID: string) {
|
|
1167
|
-
const routeKey = this.#activeRouteKey;
|
|
1168
|
-
const collectionSlug = this.collectionSlug;
|
|
1169
|
-
if (this.versionedCollection && this.canReadVersions) {
|
|
1170
|
-
const versions = this.globalResource
|
|
1171
|
-
? await this.options.runtime.client.globalVersions(collectionSlug, {
|
|
1172
|
-
locale: this.contentLocale,
|
|
1173
|
-
})
|
|
1174
|
-
: await this.options.runtime.client.versions(collectionSlug, documentID, {
|
|
1175
|
-
locale: this.contentLocale,
|
|
1133
|
+
const global = this.globalResource;
|
|
1134
|
+
const locale = this.contentLocale;
|
|
1135
|
+
try {
|
|
1136
|
+
const access = global
|
|
1137
|
+
? await this.options.runtime.client.globalAccess(collectionSlug, { locale })
|
|
1138
|
+
: await this.options.runtime.client.collectionAccess(collectionSlug, {
|
|
1139
|
+
id: documentID,
|
|
1140
|
+
locale,
|
|
1176
1141
|
});
|
|
1177
|
-
if (this.#
|
|
1178
|
-
|
|
1179
|
-
|
|
1142
|
+
if (generation !== this.#routeGeneration) return;
|
|
1143
|
+
this.form.setAccess(access, "update");
|
|
1144
|
+
this.error = undefined;
|
|
1145
|
+
} catch (cause) {
|
|
1146
|
+
if (generation !== this.#routeGeneration) return;
|
|
1147
|
+
this.form.setAccess(undefined, "update");
|
|
1148
|
+
this.error =
|
|
1149
|
+
cause instanceof Error
|
|
1150
|
+
? cause.message
|
|
1151
|
+
: this.options.runtime.i18n.t("documents:accessEvaluationFailed");
|
|
1180
1152
|
}
|
|
1181
1153
|
}
|
|
1182
1154
|
|
|
1183
1155
|
#applyDocument(document: AdminDocument, recoverDraft = false) {
|
|
1156
|
+
this.saveOutcomeUncertain = false;
|
|
1184
1157
|
this.currentDocument = document;
|
|
1158
|
+
this.upload.reset(document);
|
|
1185
1159
|
this.form.reset(
|
|
1186
1160
|
documentFormValues(this.collection?.fields ?? [], document),
|
|
1187
1161
|
this.collection?.fields ?? []
|
|
1188
1162
|
);
|
|
1189
1163
|
this.form.setLocalization(this.contentLocale, document._localization?.sources);
|
|
1190
|
-
this.currentRevision = typeof document._revision === "number" ? document._revision : 0;
|
|
1191
|
-
this.currentStatus = document._status === "draft" ? "draft" : "published";
|
|
1192
1164
|
if (recoverDraft) this.#restoreDraft();
|
|
1193
1165
|
}
|
|
1194
1166
|
|
|
1195
|
-
#applyImageDocument(document: AdminDocument) {
|
|
1196
|
-
this.currentDocument = document;
|
|
1197
|
-
this.form.setLocalization(this.contentLocale, document._localization?.sources);
|
|
1198
|
-
for (const name of uploadMetadataFields) {
|
|
1199
|
-
this.form.values[name] = document[name];
|
|
1200
|
-
this.form.original[name] = document[name];
|
|
1201
|
-
}
|
|
1202
|
-
this.currentRevision = typeof document._revision === "number" ? document._revision : 0;
|
|
1203
|
-
this.currentStatus = document._status === "draft" ? "draft" : "published";
|
|
1204
|
-
}
|
|
1205
|
-
|
|
1206
1167
|
#documentString(name: string) {
|
|
1207
1168
|
const value = this.currentDocument?.[name];
|
|
1208
1169
|
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
1209
1170
|
}
|
|
1210
|
-
|
|
1211
|
-
#documentNumber(name: string) {
|
|
1212
|
-
const value = this.currentDocument?.[name];
|
|
1213
|
-
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
1214
|
-
}
|
|
1215
|
-
|
|
1216
|
-
#formatDate(value: string) {
|
|
1217
|
-
const date = new Date(value);
|
|
1218
|
-
if (Number.isNaN(date.valueOf())) return value;
|
|
1219
|
-
return this.options.runtime.i18n.formatDate(date, {
|
|
1220
|
-
day: "2-digit",
|
|
1221
|
-
month: "short",
|
|
1222
|
-
year: "numeric",
|
|
1223
|
-
});
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
#fieldForIssue(path: string) {
|
|
1227
|
-
return this.documentFields.find(
|
|
1228
|
-
(field) => path === field.path || path.startsWith(`${field.name}.`)
|
|
1229
|
-
);
|
|
1230
|
-
}
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
function imageEditMatches(
|
|
1234
|
-
document: AdminDocument,
|
|
1235
|
-
input: {
|
|
1236
|
-
focalX: number;
|
|
1237
|
-
focalY: number;
|
|
1238
|
-
cropX: number;
|
|
1239
|
-
cropY: number;
|
|
1240
|
-
cropWidth: number;
|
|
1241
|
-
cropHeight: number;
|
|
1242
|
-
}
|
|
1243
|
-
) {
|
|
1244
|
-
return (
|
|
1245
|
-
document.focalX === input.focalX &&
|
|
1246
|
-
document.focalY === input.focalY &&
|
|
1247
|
-
document.cropX === input.cropX &&
|
|
1248
|
-
document.cropY === input.cropY &&
|
|
1249
|
-
document.cropWidth === input.cropWidth &&
|
|
1250
|
-
document.cropHeight === input.cropHeight
|
|
1251
|
-
);
|
|
1252
|
-
}
|
|
1253
|
-
|
|
1254
|
-
function withContentLocale(path: string, locale: string | undefined) {
|
|
1255
|
-
if (locale === undefined) return path;
|
|
1256
|
-
const search = new URLSearchParams({ locale });
|
|
1257
|
-
return `${path}?${search.toString()}`;
|
|
1258
1171
|
}
|