@riducms/admin 0.2.3 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THIRD_PARTY_NOTICES.md +60 -37
- package/package.json +29 -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 +131 -135
- 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 +159 -0
- package/src/features/{auth → account}/account-security-route.svelte +140 -42
- package/src/features/api-reference/api-reference-code.svelte +114 -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 +30 -60
- package/src/features/auth/create-first-user-route.svelte +83 -122
- package/src/features/auth/forgot-password-route.svelte +32 -24
- package/src/features/auth/login-route.svelte +69 -117
- 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 +28 -23
- package/src/features/auth/reset-password-route.svelte +111 -41
- package/src/features/auth/verify-email-route.svelte +52 -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 +531 -617
- 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 +336 -239
- 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 +148 -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,122 @@ 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
|
-
this.
|
|
422
|
-
|
|
423
|
-
true
|
|
398
|
+
await runtime.client.auth.forceUnlock(
|
|
399
|
+
{ collection: this.collection.slug, id: this.currentDocument.id },
|
|
400
|
+
{ signal: request.signal }
|
|
424
401
|
);
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
title: this.options.runtime.i18n.t("documents:lockTakenOver"),
|
|
428
|
-
});
|
|
429
|
-
return state.owned;
|
|
402
|
+
if (request.signal.aborted) return;
|
|
403
|
+
notifications.success({ title: runtime.i18n.t("documents:accountUnlocked") });
|
|
430
404
|
} catch (cause) {
|
|
431
|
-
|
|
432
|
-
|
|
405
|
+
if (request.signal.aborted) return;
|
|
406
|
+
notifications.error({
|
|
407
|
+
title: runtime.i18n.t("documents:accountUnlockFailed"),
|
|
433
408
|
message: cause instanceof Error ? cause.message : undefined,
|
|
434
409
|
});
|
|
435
|
-
return false;
|
|
436
410
|
} finally {
|
|
437
|
-
|
|
411
|
+
if (!request.signal.aborted) {
|
|
412
|
+
this.#forceUnlockRequest = undefined;
|
|
413
|
+
this.unlockOperation = false;
|
|
414
|
+
}
|
|
438
415
|
}
|
|
439
416
|
};
|
|
440
417
|
|
|
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;
|
|
418
|
+
copyFromLocale = async (source: string) => {
|
|
419
|
+
const { runtime, notifications } = this.options;
|
|
420
|
+
if (
|
|
421
|
+
this.contentLocale === undefined ||
|
|
422
|
+
source === this.contentLocale ||
|
|
423
|
+
this.currentDocument === undefined ||
|
|
424
|
+
this.copyLocaleOperation
|
|
425
|
+
)
|
|
426
|
+
return;
|
|
453
427
|
const request = new AbortController();
|
|
454
|
-
this
|
|
455
|
-
|
|
456
|
-
this
|
|
428
|
+
const destinationLocale = this.contentLocale;
|
|
429
|
+
const document = this.currentDocument;
|
|
430
|
+
this.#copyLocaleRequest?.abort();
|
|
431
|
+
this.#copyLocaleRequest = request;
|
|
432
|
+
this.copyLocaleOperation = true;
|
|
457
433
|
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
|
-
|
|
434
|
+
if (this.globalResource) {
|
|
435
|
+
await runtime.client.copyGlobalLocale(
|
|
436
|
+
this.collectionSlug,
|
|
437
|
+
{ from: source, to: destinationLocale },
|
|
438
|
+
{ revision: document._revision, signal: request.signal }
|
|
439
|
+
);
|
|
440
|
+
} else {
|
|
441
|
+
await runtime.client.copyLocale(
|
|
442
|
+
this.collectionSlug,
|
|
443
|
+
document.id,
|
|
444
|
+
{ from: source, to: destinationLocale },
|
|
445
|
+
{ revision: document._revision, signal: request.signal }
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
if (request.signal.aborted) return;
|
|
449
|
+
await this.refresh();
|
|
450
|
+
if (request.signal.aborted) return;
|
|
451
|
+
runtime.documentsChanged();
|
|
452
|
+
notifications.success({
|
|
453
|
+
title: runtime.i18n.t("documents:localeCopied"),
|
|
454
|
+
message: runtime.i18n.t("documents:localeCopiedDescription", {
|
|
455
|
+
source,
|
|
456
|
+
destination: destinationLocale,
|
|
457
|
+
}),
|
|
482
458
|
});
|
|
483
|
-
await Promise.allSettled([
|
|
484
|
-
this.#refreshVersions(documentID),
|
|
485
|
-
this.#refreshAccess(documentID),
|
|
486
|
-
]);
|
|
487
|
-
return true;
|
|
488
459
|
} 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"),
|
|
460
|
+
if (request.signal.aborted) return;
|
|
461
|
+
notifications.error({
|
|
462
|
+
title: runtime.i18n.t("documents:localeNotCopied"),
|
|
528
463
|
message:
|
|
529
|
-
cause instanceof Error
|
|
530
|
-
? cause.message
|
|
531
|
-
: this.options.runtime.i18n.t("uploads:focalUpdateFailed"),
|
|
464
|
+
cause instanceof Error ? cause.message : runtime.i18n.t("documents:localeCopyFailed"),
|
|
532
465
|
});
|
|
533
|
-
return false;
|
|
534
466
|
} finally {
|
|
535
|
-
if (
|
|
536
|
-
this.#
|
|
537
|
-
this.
|
|
467
|
+
if (!request.signal.aborted) {
|
|
468
|
+
this.#copyLocaleRequest = undefined;
|
|
469
|
+
this.copyLocaleOperation = false;
|
|
538
470
|
}
|
|
539
471
|
}
|
|
540
472
|
};
|
|
541
473
|
|
|
474
|
+
refresh = async () => {
|
|
475
|
+
const documentID = this.documentID;
|
|
476
|
+
if (
|
|
477
|
+
documentID === undefined ||
|
|
478
|
+
this.collection === undefined ||
|
|
479
|
+
this.#saveRequest !== undefined ||
|
|
480
|
+
this.upload.busy ||
|
|
481
|
+
this.publicationOperation
|
|
482
|
+
)
|
|
483
|
+
return;
|
|
484
|
+
await this.#load(this.collection.slug, documentID);
|
|
485
|
+
};
|
|
486
|
+
|
|
542
487
|
save = async ({
|
|
543
488
|
silent = false,
|
|
544
489
|
password,
|
|
545
490
|
publish = false,
|
|
546
491
|
}: { silent?: boolean; password?: string; publish?: boolean } = {}) => {
|
|
547
492
|
if (!this.canSave) return false;
|
|
548
|
-
const publishingChanges =
|
|
549
|
-
publish && !this.creating && this.versionedCollection && this.currentStatus === "published";
|
|
493
|
+
const publishingChanges = publish && !this.creating && this.versionedCollection;
|
|
550
494
|
if (publishingChanges && !this.canPublish) return false;
|
|
551
|
-
const creatingUpload = this.creating && this.uploadCollection;
|
|
552
|
-
const routeKey = this.#activeRouteKey;
|
|
553
495
|
const collectionSlug = this.collectionSlug;
|
|
554
496
|
const documentID = this.documentID;
|
|
555
497
|
const request = new AbortController();
|
|
556
498
|
this.#saveRequest?.abort();
|
|
557
499
|
this.#saveRequest = request;
|
|
500
|
+
this.#cancelLoad();
|
|
501
|
+
const wasCreating = this.creating;
|
|
558
502
|
try {
|
|
559
|
-
const wasCreating = this.creating;
|
|
560
503
|
const saved = await this.form.submit(this.validationFields, this.creating, async (values) => {
|
|
561
504
|
if (this.globalResource) {
|
|
562
505
|
if (publishingChanges) {
|
|
@@ -573,6 +516,20 @@ export class DocumentController {
|
|
|
573
516
|
});
|
|
574
517
|
}
|
|
575
518
|
if (documentID !== undefined) {
|
|
519
|
+
if (this.uploadCollection) {
|
|
520
|
+
return this.options.runtime.client.updateUpload(
|
|
521
|
+
collectionSlug,
|
|
522
|
+
documentID,
|
|
523
|
+
{
|
|
524
|
+
data: values,
|
|
525
|
+
file: this.upload.file,
|
|
526
|
+
filename: this.upload.filename,
|
|
527
|
+
image: this.upload.image,
|
|
528
|
+
publish: publishingChanges,
|
|
529
|
+
},
|
|
530
|
+
{ revision: this.currentRevision, signal: request.signal, locale: this.contentLocale }
|
|
531
|
+
);
|
|
532
|
+
}
|
|
576
533
|
if (publishingChanges) {
|
|
577
534
|
return this.options.runtime.client.publishChanges(collectionSlug, documentID, values, {
|
|
578
535
|
revision: this.currentRevision,
|
|
@@ -589,10 +546,10 @@ export class DocumentController {
|
|
|
589
546
|
if (this.collection?.capabilities.auth) {
|
|
590
547
|
if (password === undefined)
|
|
591
548
|
throw new Error(this.options.runtime.i18n.t("documents:enterNewAccountPassword"));
|
|
592
|
-
return this.options.runtime.client.
|
|
593
|
-
|
|
594
|
-
locale: this.contentLocale
|
|
595
|
-
|
|
549
|
+
return this.options.runtime.client.auth.createUser(
|
|
550
|
+
{ collection: collectionSlug, data: values, password },
|
|
551
|
+
{ signal: request.signal, locale: this.contentLocale }
|
|
552
|
+
);
|
|
596
553
|
}
|
|
597
554
|
if (!this.uploadCollection) {
|
|
598
555
|
return this.options.runtime.client.create(collectionSlug, values, {
|
|
@@ -606,15 +563,18 @@ export class DocumentController {
|
|
|
606
563
|
if (this.selectedFile === undefined)
|
|
607
564
|
throw new Error(this.options.runtime.i18n.t("uploads:chooseFileBeforeSaving"));
|
|
608
565
|
return this.options.runtime.client.upload(collectionSlug, this.selectedFile, {
|
|
566
|
+
filename: this.upload.filename,
|
|
567
|
+
image: this.upload.image,
|
|
568
|
+
publish,
|
|
609
569
|
data: values,
|
|
610
570
|
signal: request.signal,
|
|
611
571
|
locale: this.contentLocale,
|
|
612
572
|
});
|
|
613
573
|
});
|
|
614
|
-
if (request.signal.aborted
|
|
574
|
+
if (request.signal.aborted) return false;
|
|
615
575
|
this.saveOutcomeUncertain = false;
|
|
616
576
|
this.#applyDocument(saved);
|
|
617
|
-
|
|
577
|
+
this.lastSavedAt = Date.now();
|
|
618
578
|
this.options.runtime.documentsChanged();
|
|
619
579
|
clearFormDraft(this.collection?.id ?? this.collectionSlug, this.documentID);
|
|
620
580
|
if (!silent) {
|
|
@@ -632,18 +592,19 @@ export class DocumentController {
|
|
|
632
592
|
// Let the route clear any blocked departure now that the successful save has
|
|
633
593
|
// removed both form and upload dirtiness before replacing the create URL.
|
|
634
594
|
await tick();
|
|
595
|
+
if (request.signal.aborted) return false;
|
|
635
596
|
this.options.navigate(
|
|
636
597
|
withContentLocale(documentPath(this.collectionSlug, saved.id), this.contentLocale),
|
|
637
598
|
{ replace: true }
|
|
638
599
|
);
|
|
639
600
|
} else {
|
|
640
|
-
await
|
|
601
|
+
await this.#refreshAccess(saved.id);
|
|
641
602
|
}
|
|
642
|
-
return
|
|
603
|
+
return !request.signal.aborted;
|
|
643
604
|
} catch (cause) {
|
|
644
|
-
if (request.signal.aborted
|
|
605
|
+
if (request.signal.aborted) return false;
|
|
645
606
|
if (
|
|
646
|
-
|
|
607
|
+
this.uploadCollection &&
|
|
647
608
|
!(cause instanceof FormValidationError) &&
|
|
648
609
|
(!(cause instanceof RiduError) || cause.status >= 500)
|
|
649
610
|
) {
|
|
@@ -651,7 +612,11 @@ export class DocumentController {
|
|
|
651
612
|
if (!silent) {
|
|
652
613
|
this.options.notifications.error({
|
|
653
614
|
title: this.options.runtime.i18n.t("uploads:outcomeUnknown"),
|
|
654
|
-
message: this.options.runtime.i18n.t(
|
|
615
|
+
message: this.options.runtime.i18n.t(
|
|
616
|
+
wasCreating
|
|
617
|
+
? "uploads:outcomeUnknownDescription"
|
|
618
|
+
: "uploads:saveOutcomeUnknownDescription"
|
|
619
|
+
),
|
|
655
620
|
});
|
|
656
621
|
}
|
|
657
622
|
return false;
|
|
@@ -686,8 +651,19 @@ export class DocumentController {
|
|
|
686
651
|
};
|
|
687
652
|
|
|
688
653
|
changePublication = async (next: "draft" | "published") => {
|
|
689
|
-
|
|
690
|
-
|
|
654
|
+
const allowed = next === "published" ? this.canPublish : this.canUnpublish;
|
|
655
|
+
if (
|
|
656
|
+
this.documentID === undefined ||
|
|
657
|
+
!allowed ||
|
|
658
|
+
this.form.submitting ||
|
|
659
|
+
this.publicationOperation ||
|
|
660
|
+
this.upload.busy ||
|
|
661
|
+
this.saveOutcomeUncertain
|
|
662
|
+
)
|
|
663
|
+
return;
|
|
664
|
+
this.#cancelLoad();
|
|
665
|
+
this.publicationOperation = true;
|
|
666
|
+
const generation = this.#routeGeneration;
|
|
691
667
|
try {
|
|
692
668
|
const saved =
|
|
693
669
|
next === "published"
|
|
@@ -709,12 +685,12 @@ export class DocumentController {
|
|
|
709
685
|
revision: this.currentRevision,
|
|
710
686
|
locale: this.contentLocale,
|
|
711
687
|
});
|
|
688
|
+
if (generation !== this.#routeGeneration) return;
|
|
712
689
|
this.#applyDocument(saved);
|
|
690
|
+
this.lastSavedAt = Date.now();
|
|
713
691
|
this.options.runtime.documentsChanged();
|
|
714
|
-
await
|
|
715
|
-
|
|
716
|
-
this.#refreshAccess(this.documentID),
|
|
717
|
-
]);
|
|
692
|
+
await this.#refreshAccess(this.documentID);
|
|
693
|
+
if (generation !== this.#routeGeneration) return;
|
|
718
694
|
this.options.notifications.success({
|
|
719
695
|
title: this.options.runtime.i18n.t(
|
|
720
696
|
next === "published" ? "documents:publishedTitle" : "documents:unpublishedTitle"
|
|
@@ -726,6 +702,7 @@ export class DocumentController {
|
|
|
726
702
|
}),
|
|
727
703
|
});
|
|
728
704
|
} catch (cause) {
|
|
705
|
+
if (generation !== this.#routeGeneration) return;
|
|
729
706
|
this.options.notifications.error({
|
|
730
707
|
title: this.options.runtime.i18n.t("documents:statusNotChanged"),
|
|
731
708
|
message:
|
|
@@ -734,13 +711,15 @@ export class DocumentController {
|
|
|
734
711
|
: this.options.runtime.i18n.t("documents:statusChangeFailed"),
|
|
735
712
|
});
|
|
736
713
|
} finally {
|
|
737
|
-
this.
|
|
714
|
+
if (generation === this.#routeGeneration) this.publicationOperation = false;
|
|
738
715
|
}
|
|
739
716
|
};
|
|
740
717
|
|
|
741
718
|
duplicate = async () => {
|
|
742
|
-
if (this.documentID === undefined || this.globalResource || this.lockedByAnotherEditor)
|
|
719
|
+
if (this.documentID === undefined || this.globalResource || this.lock.lockedByAnotherEditor)
|
|
720
|
+
return;
|
|
743
721
|
this.duplicateOperation = true;
|
|
722
|
+
const generation = this.#routeGeneration;
|
|
744
723
|
try {
|
|
745
724
|
const duplicated = await this.options.runtime.client.duplicate(
|
|
746
725
|
this.collectionSlug,
|
|
@@ -748,6 +727,7 @@ export class DocumentController {
|
|
|
748
727
|
{},
|
|
749
728
|
{ locale: this.contentLocale }
|
|
750
729
|
);
|
|
730
|
+
if (generation !== this.#routeGeneration) return;
|
|
751
731
|
this.options.runtime.documentsChanged();
|
|
752
732
|
this.options.notifications.success({
|
|
753
733
|
title: this.options.runtime.i18n.t("documents:duplicatedLabel", {
|
|
@@ -759,6 +739,7 @@ export class DocumentController {
|
|
|
759
739
|
withContentLocale(documentPath(this.collectionSlug, duplicated.id), this.contentLocale)
|
|
760
740
|
);
|
|
761
741
|
} catch (cause) {
|
|
742
|
+
if (generation !== this.#routeGeneration) return;
|
|
762
743
|
this.options.notifications.error({
|
|
763
744
|
title: this.options.runtime.i18n.t("documents:notDuplicated"),
|
|
764
745
|
message:
|
|
@@ -767,57 +748,17 @@ export class DocumentController {
|
|
|
767
748
|
: this.options.runtime.i18n.t("documents:duplicateFailed"),
|
|
768
749
|
});
|
|
769
750
|
} 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;
|
|
751
|
+
if (generation === this.#routeGeneration) this.duplicateOperation = false;
|
|
814
752
|
}
|
|
815
753
|
};
|
|
816
754
|
|
|
817
755
|
remove = async () => {
|
|
818
|
-
if (this.documentID === undefined || this.lockedByAnotherEditor) return;
|
|
756
|
+
if (this.documentID === undefined || this.lock.lockedByAnotherEditor) return;
|
|
757
|
+
this.#cancelLoad();
|
|
758
|
+
const generation = this.#routeGeneration;
|
|
819
759
|
try {
|
|
820
760
|
await this.options.runtime.client.delete(this.collectionSlug, this.documentID);
|
|
761
|
+
if (generation !== this.#routeGeneration) return;
|
|
821
762
|
this.options.runtime.documentsChanged();
|
|
822
763
|
this.deleteDialogOpen = false;
|
|
823
764
|
this.options.notifications.success({
|
|
@@ -834,6 +775,7 @@ export class DocumentController {
|
|
|
834
775
|
});
|
|
835
776
|
this.options.navigate(collectionPath(this.collectionSlug));
|
|
836
777
|
} catch (cause) {
|
|
778
|
+
if (generation !== this.#routeGeneration) return;
|
|
837
779
|
this.options.notifications.error({
|
|
838
780
|
title: this.options.runtime.i18n.t("documents:notDeleted"),
|
|
839
781
|
message:
|
|
@@ -844,64 +786,81 @@ export class DocumentController {
|
|
|
844
786
|
}
|
|
845
787
|
};
|
|
846
788
|
|
|
847
|
-
requestRestore = (version: AdminVersion) => {
|
|
848
|
-
this.pendingRestore = version;
|
|
849
|
-
this.restoreDialogOpen = true;
|
|
850
|
-
};
|
|
851
|
-
|
|
852
|
-
versionDate = (version: AdminVersion) => this.#formatDate(version.CreatedAt);
|
|
853
|
-
|
|
854
789
|
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
|
-
};
|
|
790
|
+
value === undefined ? "—" : formatAdminDateTime(value, this.options.runtime.i18n);
|
|
791
|
+
|
|
792
|
+
#cancelRouteRequests() {
|
|
793
|
+
this.#forceUnlockRequest?.abort();
|
|
794
|
+
this.#copyLocaleRequest?.abort();
|
|
795
|
+
this.#forceUnlockRequest = undefined;
|
|
796
|
+
this.#copyLocaleRequest = undefined;
|
|
797
|
+
this.unlockOperation = false;
|
|
798
|
+
this.copyLocaleOperation = false;
|
|
799
|
+
}
|
|
871
800
|
|
|
872
|
-
#enterRoute(
|
|
873
|
-
|
|
874
|
-
|
|
801
|
+
#enterRoute(
|
|
802
|
+
slug: string,
|
|
803
|
+
documentID: string | undefined,
|
|
804
|
+
locale: string | undefined,
|
|
805
|
+
manifestRevision: number,
|
|
806
|
+
global: boolean,
|
|
807
|
+
editable: boolean,
|
|
808
|
+
prepared?: AdminDocumentDataV1 | AdminCreateDataV1
|
|
809
|
+
) {
|
|
810
|
+
const resourceKey = `${global ? "global" : "collection"}:${slug}:${documentID ?? "new"}`;
|
|
811
|
+
const ownerKey = `${resourceKey}:${locale ?? "default"}`;
|
|
812
|
+
const routeKey = `${ownerKey}:${editable ? "edit" : "read"}`;
|
|
813
|
+
const sameOwner = this.#activeRouteKey.startsWith(`${ownerKey}:`);
|
|
875
814
|
if (
|
|
876
815
|
this.#activeRouteKey.startsWith(`${resourceKey}:`) &&
|
|
877
816
|
this.#activeRouteKey !== routeKey &&
|
|
878
817
|
this.hasUnsavedChanges
|
|
879
818
|
) {
|
|
819
|
+
if (manifestRevision !== this.#appliedManifestRevision) {
|
|
820
|
+
this.#appliedManifestRevision = manifestRevision;
|
|
821
|
+
this.#applySchemaUpdate();
|
|
822
|
+
}
|
|
880
823
|
return;
|
|
881
824
|
}
|
|
825
|
+
if (this.#activeRouteKey === routeKey && this.collection !== undefined) {
|
|
826
|
+
if (manifestRevision !== this.#appliedManifestRevision) {
|
|
827
|
+
this.#appliedManifestRevision = manifestRevision;
|
|
828
|
+
this.#applySchemaUpdate();
|
|
829
|
+
} else if (prepared !== undefined && !this.hasUnsavedChanges) {
|
|
830
|
+
this.#load(slug, documentID, false, prepared);
|
|
831
|
+
}
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
this.#appliedManifestRevision = manifestRevision;
|
|
835
|
+
if (this.#activeRouteKey !== routeKey && !sameOwner) {
|
|
836
|
+
// A later visit to the same URL is a different owner for mutation completions.
|
|
837
|
+
this.#routeGeneration += 1;
|
|
838
|
+
this.#cancelRouteRequests();
|
|
839
|
+
this.publicationOperation = false;
|
|
840
|
+
this.duplicateOperation = false;
|
|
841
|
+
this.deleteDialogOpen = false;
|
|
842
|
+
this.currentDocument = undefined;
|
|
843
|
+
this.lastSavedAt = undefined;
|
|
844
|
+
this.upload.reset(this.currentDocument);
|
|
845
|
+
}
|
|
846
|
+
this.#cancelLoad();
|
|
882
847
|
this.#activeContentLocale = locale;
|
|
883
|
-
if (this.#activeRouteKey !== "" && this.#activeRouteKey !== routeKey) {
|
|
848
|
+
if (this.#activeRouteKey !== "" && this.#activeRouteKey !== routeKey && !sameOwner) {
|
|
884
849
|
this.#saveRequest?.abort();
|
|
885
850
|
this.#saveRequest = undefined;
|
|
886
|
-
this.#imageRequest?.abort();
|
|
887
|
-
this.#imageRequest = undefined;
|
|
888
|
-
this.imageOperation = false;
|
|
889
851
|
}
|
|
890
852
|
const collection = (
|
|
891
|
-
this.options.
|
|
892
|
-
? this.options.runtime.manifest?.globals
|
|
893
|
-
: this.options.runtime.manifest?.collections
|
|
853
|
+
global ? this.options.runtime.manifest?.globals : this.options.runtime.manifest?.collections
|
|
894
854
|
)?.find((item) => item.slug === slug);
|
|
895
855
|
if (collection === undefined) {
|
|
896
856
|
this.#activeRouteKey = routeKey;
|
|
897
|
-
this
|
|
857
|
+
this.lock.release();
|
|
898
858
|
this.collection = undefined;
|
|
899
859
|
this.collectionAvailable = false;
|
|
900
860
|
this.currentDocument = undefined;
|
|
901
|
-
this.
|
|
902
|
-
this.versions = [];
|
|
861
|
+
this.upload.reset(this.currentDocument);
|
|
903
862
|
this.form.reset({});
|
|
904
|
-
this.form.setResource({ collection: slug, id: documentID, global
|
|
863
|
+
this.form.setResource({ collection: slug, id: documentID, global });
|
|
905
864
|
this.loading = false;
|
|
906
865
|
this.error = this.options.runtime.i18n.t("documents:collectionUnavailable");
|
|
907
866
|
this.form.setAccess(undefined, documentID === undefined ? "create" : "update");
|
|
@@ -911,43 +870,24 @@ export class DocumentController {
|
|
|
911
870
|
this.form.setResource({
|
|
912
871
|
collection: collection.slug,
|
|
913
872
|
id: documentID,
|
|
914
|
-
global
|
|
873
|
+
global,
|
|
915
874
|
});
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
(this.#lockRoute.collection !== collection.slug || this.#lockRoute.documentID !== documentID)
|
|
919
|
-
) {
|
|
920
|
-
this.#releaseLock();
|
|
921
|
-
}
|
|
875
|
+
this.lock.releaseUnless(collection.slug, documentID);
|
|
876
|
+
if (!editable) this.lock.release();
|
|
922
877
|
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
878
|
this.#activeRouteKey = routeKey;
|
|
931
879
|
this.saveOutcomeUncertain = false;
|
|
932
|
-
this.imageOutcomeUncertain = false;
|
|
933
880
|
this.form.setAccess(undefined, documentID === undefined ? "create" : "update");
|
|
934
881
|
if (documentID === undefined) {
|
|
935
882
|
this.form.reset(initialFormValues(collection.fields), collection.fields);
|
|
936
883
|
this.form.setLocalization(locale);
|
|
937
|
-
this.versions = [];
|
|
938
884
|
this.currentDocument = undefined;
|
|
939
|
-
this.
|
|
940
|
-
this.currentStatus = this.draftsCollection ? "draft" : "published";
|
|
941
|
-
this.documentLock = undefined;
|
|
942
|
-
this.form.writeBlocked = false;
|
|
885
|
+
this.lock.release();
|
|
943
886
|
this.#restoreDraft();
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
return () => request.abort();
|
|
887
|
+
this.#load(collection.slug, undefined, false, prepared);
|
|
888
|
+
return;
|
|
947
889
|
}
|
|
948
|
-
|
|
949
|
-
this.#load(collection.slug, documentID, request.signal);
|
|
950
|
-
return () => request.abort();
|
|
890
|
+
this.#load(collection.slug, documentID, false, prepared);
|
|
951
891
|
}
|
|
952
892
|
|
|
953
893
|
#applySchemaUpdate() {
|
|
@@ -958,17 +898,32 @@ export class DocumentController {
|
|
|
958
898
|
? this.options.runtime.manifest?.globals
|
|
959
899
|
: this.options.runtime.manifest?.collections
|
|
960
900
|
)?.find((item) => item.id === previous.id);
|
|
901
|
+
this.#cancelLoad();
|
|
902
|
+
this.#routeGeneration += 1;
|
|
903
|
+
this.#cancelRouteRequests();
|
|
904
|
+
this.#saveRequest?.abort();
|
|
905
|
+
this.publicationOperation = false;
|
|
906
|
+
this.duplicateOperation = false;
|
|
907
|
+
this.lock.pause();
|
|
908
|
+
const result = this.form.reconcile(
|
|
909
|
+
previous.fields,
|
|
910
|
+
next?.fields ?? previous.fields,
|
|
911
|
+
this.creating
|
|
912
|
+
);
|
|
961
913
|
if (next === undefined) {
|
|
914
|
+
this.lock.release();
|
|
962
915
|
this.collectionAvailable = false;
|
|
916
|
+
this.form.writeBlocked = true;
|
|
917
|
+
this.form.setAccess(undefined, this.creating ? "create" : "update");
|
|
963
918
|
this.options.notifications.error({
|
|
964
919
|
title: this.options.runtime.i18n.t("documents:collectionRemoved"),
|
|
965
920
|
});
|
|
966
921
|
return;
|
|
967
922
|
}
|
|
968
923
|
|
|
969
|
-
const result = this.form.reconcile(previous.fields, next.fields, this.creating);
|
|
970
924
|
this.collection = next;
|
|
971
925
|
this.collectionAvailable = true;
|
|
926
|
+
if (!next.capabilities.locking || previous.slug !== next.slug) this.lock.release();
|
|
972
927
|
if (result.detached.length > 0) {
|
|
973
928
|
this.options.notifications.warning({
|
|
974
929
|
title: this.options.runtime.i18n.t("documents:schemaUpdatedWithDraft"),
|
|
@@ -978,6 +933,15 @@ export class DocumentController {
|
|
|
978
933
|
});
|
|
979
934
|
}
|
|
980
935
|
if (previous.slug !== next.slug) {
|
|
936
|
+
// The resulting URL is the same editor under its renamed resource, so its
|
|
937
|
+
// next route synchronization must not reload and replace the retained draft.
|
|
938
|
+
const documentID = this.globalResource ? next.slug : this.documentID;
|
|
939
|
+
this.#activeRouteKey = `${this.globalResource ? "global" : "collection"}:${next.slug}:${documentID ?? "new"}:${this.contentLocale ?? "default"}:${this.options.editable ? "edit" : "read"}`;
|
|
940
|
+
this.form.setResource({
|
|
941
|
+
collection: next.slug,
|
|
942
|
+
id: documentID,
|
|
943
|
+
global: this.globalResource,
|
|
944
|
+
});
|
|
981
945
|
this.options.navigate(
|
|
982
946
|
withContentLocale(
|
|
983
947
|
this.globalResource
|
|
@@ -990,13 +954,21 @@ export class DocumentController {
|
|
|
990
954
|
{ replace: true }
|
|
991
955
|
);
|
|
992
956
|
}
|
|
957
|
+
// Field access is keyed by path, so the old envelope is invalid after reconciliation.
|
|
958
|
+
this.form.setAccess(undefined, this.creating ? "create" : "update");
|
|
959
|
+
this.form.writeBlocked = true;
|
|
960
|
+
this.#load(
|
|
961
|
+
next.slug,
|
|
962
|
+
this.globalResource ? next.slug : this.documentID,
|
|
963
|
+
this.currentDocument !== undefined
|
|
964
|
+
);
|
|
993
965
|
}
|
|
994
966
|
|
|
995
967
|
#restoreDraft() {
|
|
996
968
|
const collection = this.collection;
|
|
997
|
-
if (collection === undefined) return;
|
|
969
|
+
if (collection === undefined) return false;
|
|
998
970
|
const checkpoint = takeFormDraft(collection.id, this.documentID);
|
|
999
|
-
if (checkpoint === undefined) return;
|
|
971
|
+
if (checkpoint === undefined) return false;
|
|
1000
972
|
const result = this.form.recover(
|
|
1001
973
|
{ values: checkpoint.values, original: checkpoint.original },
|
|
1002
974
|
checkpoint.collection.fields,
|
|
@@ -1017,53 +989,81 @@ export class DocumentController {
|
|
|
1017
989
|
if (result.restoredFields === 0 || result.detached.length > 0)
|
|
1018
990
|
this.options.notifications.warning({ title, message });
|
|
1019
991
|
else this.options.notifications.success({ title });
|
|
992
|
+
return result.restoredFields > 0;
|
|
1020
993
|
}
|
|
1021
994
|
|
|
1022
|
-
|
|
995
|
+
#cancelLoad() {
|
|
996
|
+
this.#loadRequest?.abort();
|
|
997
|
+
this.#loadRequest = undefined;
|
|
998
|
+
this.loading = false;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
async #load(
|
|
1002
|
+
slug: string,
|
|
1003
|
+
documentID?: string,
|
|
1004
|
+
preserveDocument = false,
|
|
1005
|
+
prepared?: AdminDocumentDataV1 | AdminCreateDataV1
|
|
1006
|
+
) {
|
|
1007
|
+
this.#loadRequest?.abort();
|
|
1008
|
+
this.#loadRequest = undefined;
|
|
1009
|
+
if (prepared !== undefined && this.#adoptPreparedLoad(prepared, slug, documentID)) return;
|
|
1010
|
+
const request = new AbortController();
|
|
1011
|
+
this.#loadRequest = request;
|
|
1012
|
+
const { signal } = request;
|
|
1013
|
+
const global = this.globalResource;
|
|
1014
|
+
const locale = this.contentLocale;
|
|
1015
|
+
// A refresh may replace the draft it started with, never edits made while it was pending.
|
|
1016
|
+
const revision = this.form.revision;
|
|
1017
|
+
const uploadRevision = this.upload.revision;
|
|
1018
|
+
const changeGeneration = this.#formChangeGeneration;
|
|
1023
1019
|
this.loading = true;
|
|
1024
1020
|
this.error = undefined;
|
|
1025
1021
|
try {
|
|
1026
1022
|
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
|
-
})
|
|
1023
|
+
documentID === undefined || preserveDocument
|
|
1024
|
+
? undefined
|
|
1025
|
+
: global
|
|
1026
|
+
? this.options.runtime.client.global(slug, { signal, locale })
|
|
1027
|
+
: this.options.runtime.client.find(slug, documentID, { signal, locale }),
|
|
1028
|
+
global
|
|
1029
|
+
? this.options.runtime.client.globalAccess(slug, { signal, locale })
|
|
1041
1030
|
: this.options.runtime.client.collectionAccess(slug, {
|
|
1042
1031
|
id: documentID,
|
|
1032
|
+
...(documentID === undefined ? { data: $state.snapshot(this.form.values) } : {}),
|
|
1043
1033
|
signal,
|
|
1044
|
-
locale
|
|
1034
|
+
locale,
|
|
1045
1035
|
}),
|
|
1046
1036
|
]);
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
this.
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
locale: this.contentLocale,
|
|
1058
|
-
});
|
|
1059
|
-
} else {
|
|
1060
|
-
this.versions = [];
|
|
1037
|
+
if (signal.aborted) return;
|
|
1038
|
+
const formUnchanged =
|
|
1039
|
+
this.form.revision === revision &&
|
|
1040
|
+
this.upload.revision === uploadRevision &&
|
|
1041
|
+
this.#formChangeGeneration === changeGeneration;
|
|
1042
|
+
this.form.setAccess(access, documentID === undefined ? "create" : "update");
|
|
1043
|
+
if (document !== undefined && formUnchanged) {
|
|
1044
|
+
const uncertain = this.saveOutcomeUncertain;
|
|
1045
|
+
if (uncertain) clearFormDraft(this.collection?.id ?? slug, documentID);
|
|
1046
|
+
this.#applyDocument(document, !uncertain);
|
|
1061
1047
|
}
|
|
1062
|
-
if (
|
|
1063
|
-
|
|
1048
|
+
if (
|
|
1049
|
+
documentID !== undefined &&
|
|
1050
|
+
!global &&
|
|
1051
|
+
this.options.editable &&
|
|
1052
|
+
this.collection?.capabilities.locking === true
|
|
1053
|
+
) {
|
|
1054
|
+
// The form is complete before the lock request starts. Writes remain
|
|
1055
|
+
// disabled until the server confirms ownership.
|
|
1056
|
+
this.form.writeBlocked = true;
|
|
1057
|
+
this.loading = false;
|
|
1058
|
+
await tick();
|
|
1059
|
+
if (signal.aborted || this.#loadRequest !== request) return;
|
|
1060
|
+
await this.lock.acquire(
|
|
1061
|
+
slug,
|
|
1062
|
+
documentID,
|
|
1063
|
+
this.collection.documentLockSettings?.durationSeconds ?? 120
|
|
1064
|
+
);
|
|
1064
1065
|
} else {
|
|
1065
|
-
this.
|
|
1066
|
-
this.form.writeBlocked = false;
|
|
1066
|
+
this.lock.release();
|
|
1067
1067
|
}
|
|
1068
1068
|
} catch (cause) {
|
|
1069
1069
|
if (signal.aborted) return;
|
|
@@ -1072,187 +1072,101 @@ export class DocumentController {
|
|
|
1072
1072
|
? cause.message
|
|
1073
1073
|
: this.options.runtime.i18n.t("documents:loadFailed");
|
|
1074
1074
|
} 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.
|
|
1075
|
+
if (this.#loadRequest === request) {
|
|
1076
|
+
this.#loadRequest = undefined;
|
|
1077
|
+
this.loading = false;
|
|
1109
1078
|
}
|
|
1110
|
-
}
|
|
1079
|
+
}
|
|
1111
1080
|
}
|
|
1112
1081
|
|
|
1113
|
-
#
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1082
|
+
#adoptPreparedLoad(
|
|
1083
|
+
prepared: AdminDocumentDataV1 | AdminCreateDataV1,
|
|
1084
|
+
slug: string,
|
|
1085
|
+
documentID?: string
|
|
1086
|
+
) {
|
|
1087
|
+
if (
|
|
1088
|
+
"values" in prepared &&
|
|
1089
|
+
!samePreparedCreateValues(prepared.values, $state.snapshot(this.form.values))
|
|
1090
|
+
)
|
|
1091
|
+
return false;
|
|
1092
|
+
const document = "document" in prepared ? prepared.document : undefined;
|
|
1093
|
+
const access = prepared.access;
|
|
1094
|
+
this.loading = false;
|
|
1095
|
+
this.error = document?.error?.message ?? access.error?.message;
|
|
1096
|
+
if (this.error !== undefined) {
|
|
1097
|
+
this.form.setAccess(undefined, documentID === undefined ? "create" : "update");
|
|
1098
|
+
return true;
|
|
1117
1099
|
}
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
this.
|
|
1125
|
-
|
|
1126
|
-
|
|
1100
|
+
this.form.setAccess(access.value, documentID === undefined ? "create" : "update");
|
|
1101
|
+
if (document?.value !== undefined) this.#applyDocument(document.value, true);
|
|
1102
|
+
if (
|
|
1103
|
+
documentID !== undefined &&
|
|
1104
|
+
!this.globalResource &&
|
|
1105
|
+
this.options.editable &&
|
|
1106
|
+
this.collection?.capabilities.locking === true
|
|
1107
|
+
) {
|
|
1108
|
+
this.form.writeBlocked = true;
|
|
1109
|
+
this.#acquirePreparedLock(this.#routeGeneration, slug, documentID);
|
|
1110
|
+
} else {
|
|
1111
|
+
this.lock.release();
|
|
1127
1112
|
}
|
|
1113
|
+
return true;
|
|
1128
1114
|
}
|
|
1129
1115
|
|
|
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
|
-
}
|
|
1116
|
+
async #acquirePreparedLock(generation: number, slug: string, documentID: string) {
|
|
1117
|
+
await tick();
|
|
1118
|
+
if (
|
|
1119
|
+
generation !== this.#routeGeneration ||
|
|
1120
|
+
this.documentID !== documentID ||
|
|
1121
|
+
!this.options.editable
|
|
1122
|
+
)
|
|
1123
|
+
return;
|
|
1124
|
+
await this.lock.acquire(
|
|
1125
|
+
slug,
|
|
1126
|
+
documentID,
|
|
1127
|
+
this.collection?.documentLockSettings?.durationSeconds ?? 120
|
|
1128
|
+
);
|
|
1150
1129
|
}
|
|
1151
1130
|
|
|
1152
1131
|
async #refreshAccess(documentID: string) {
|
|
1153
|
-
const
|
|
1132
|
+
const generation = this.#routeGeneration;
|
|
1154
1133
|
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,
|
|
1134
|
+
const global = this.globalResource;
|
|
1135
|
+
const locale = this.contentLocale;
|
|
1136
|
+
try {
|
|
1137
|
+
const access = global
|
|
1138
|
+
? await this.options.runtime.client.globalAccess(collectionSlug, { locale })
|
|
1139
|
+
: await this.options.runtime.client.collectionAccess(collectionSlug, {
|
|
1140
|
+
id: documentID,
|
|
1141
|
+
locale,
|
|
1176
1142
|
});
|
|
1177
|
-
if (this.#
|
|
1178
|
-
|
|
1179
|
-
|
|
1143
|
+
if (generation !== this.#routeGeneration) return;
|
|
1144
|
+
this.form.setAccess(access, "update");
|
|
1145
|
+
this.error = undefined;
|
|
1146
|
+
} catch (cause) {
|
|
1147
|
+
if (generation !== this.#routeGeneration) return;
|
|
1148
|
+
this.form.setAccess(undefined, "update");
|
|
1149
|
+
this.error =
|
|
1150
|
+
cause instanceof Error
|
|
1151
|
+
? cause.message
|
|
1152
|
+
: this.options.runtime.i18n.t("documents:accessEvaluationFailed");
|
|
1180
1153
|
}
|
|
1181
1154
|
}
|
|
1182
1155
|
|
|
1183
1156
|
#applyDocument(document: AdminDocument, recoverDraft = false) {
|
|
1157
|
+
this.saveOutcomeUncertain = false;
|
|
1184
1158
|
this.currentDocument = document;
|
|
1159
|
+
this.upload.reset(document);
|
|
1185
1160
|
this.form.reset(
|
|
1186
1161
|
documentFormValues(this.collection?.fields ?? [], document),
|
|
1187
1162
|
this.collection?.fields ?? []
|
|
1188
1163
|
);
|
|
1189
1164
|
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
1165
|
if (recoverDraft) this.#restoreDraft();
|
|
1193
1166
|
}
|
|
1194
1167
|
|
|
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
1168
|
#documentString(name: string) {
|
|
1207
1169
|
const value = this.currentDocument?.[name];
|
|
1208
1170
|
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
1209
1171
|
}
|
|
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
1172
|
}
|