@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,52 +1,23 @@
|
|
|
1
|
-
<script lang="ts" module>
|
|
2
|
-
import { resolveBlockTypes } from "@riducms/protocol";
|
|
3
|
-
|
|
4
|
-
import type { SchemaField } from "@riducms/protocol";
|
|
5
|
-
|
|
6
|
-
function fieldUsesLocalization(field: SchemaField): boolean {
|
|
7
|
-
if (field.localized === true) return true;
|
|
8
|
-
if (field.nested?.fields.some(fieldUsesLocalization)) return true;
|
|
9
|
-
return (
|
|
10
|
-
resolveBlockTypes(field.blocks).some((block) => block.fields.some(fieldUsesLocalization)) ===
|
|
11
|
-
true
|
|
12
|
-
);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function withContentLocale(path: string, locale: string | undefined) {
|
|
16
|
-
if (locale === undefined) return path;
|
|
17
|
-
return `${path}?${new URLSearchParams({ locale }).toString()}`;
|
|
18
|
-
}
|
|
19
|
-
</script>
|
|
20
|
-
|
|
21
1
|
<script lang="ts">
|
|
22
2
|
import { focusFieldIssue } from "@admin/core/forms/field-issue-focus";
|
|
23
3
|
import type { AdminDocumentExtensionHost } from "@riducms/plugin";
|
|
24
4
|
import { Link, useBlocker, useLocation, useNavigate, useParams } from "@hvniel/svelte-router";
|
|
25
|
-
import { tick
|
|
26
|
-
import
|
|
27
|
-
import DownloadIcon from "~icons/lucide/download";
|
|
28
|
-
import FileUpIcon from "~icons/lucide/file-up";
|
|
29
|
-
import Trash2Icon from "~icons/lucide/trash-2";
|
|
30
|
-
import KeyRoundIcon from "~icons/lucide/key-round";
|
|
31
|
-
import MonitorUpIcon from "~icons/lucide/monitor-up";
|
|
32
|
-
import MoreVerticalIcon from "~icons/lucide/more-vertical";
|
|
5
|
+
import { tick } from "svelte";
|
|
6
|
+
import type { Attachment } from "svelte/attachments";
|
|
33
7
|
import LockKeyholeIcon from "~icons/lucide/lock-keyhole";
|
|
34
8
|
|
|
35
9
|
import { Banner } from "@admin/components/ui/banner";
|
|
36
|
-
import { Button, buttonVariants } from "@admin/components/ui/button";
|
|
37
10
|
import {
|
|
11
|
+
Button,
|
|
38
12
|
Dialog,
|
|
39
13
|
DialogContent,
|
|
40
14
|
DialogDescription,
|
|
41
15
|
DialogFooter,
|
|
42
16
|
DialogHeader,
|
|
43
17
|
DialogTitle,
|
|
44
|
-
} from "@
|
|
45
|
-
import { Popover, PopoverContent, PopoverTrigger } from "@admin/components/ui/popover";
|
|
46
|
-
import { Select, SelectContent, SelectItem, SelectTrigger } from "@admin/components/ui/select";
|
|
18
|
+
} from "@riducms/ui";
|
|
47
19
|
import { Skeleton } from "@admin/components/ui/skeleton";
|
|
48
20
|
import {
|
|
49
|
-
collectionPath,
|
|
50
21
|
createDocumentAPIPath,
|
|
51
22
|
createDocumentPath,
|
|
52
23
|
documentIDFromAdminPath,
|
|
@@ -56,41 +27,54 @@
|
|
|
56
27
|
globalAPIPath,
|
|
57
28
|
globalPath,
|
|
58
29
|
globalVersionsPath,
|
|
30
|
+
withContentLocale,
|
|
59
31
|
} from "@admin/core/routing/admin-paths";
|
|
60
32
|
import { getNotificationCenter } from "@admin/core/notifications/notification-center.svelte";
|
|
33
|
+
import { registerAdminScrollPage } from "@admin/core/routing/admin-scroll.svelte";
|
|
34
|
+
import { MediaQuery } from "svelte/reactivity";
|
|
61
35
|
import { getAdminRuntime } from "@admin/core/runtime/admin-runtime.svelte";
|
|
36
|
+
import { getAdminBootstrapCoordinator } from "@admin/core/bootstrap/admin-bootstrap";
|
|
37
|
+
import { fieldsUseLocalization } from "@admin/core/schema/field-localization";
|
|
62
38
|
import { DocumentController } from "@admin/features/documents/document-controller.svelte";
|
|
63
39
|
import {
|
|
64
40
|
documentRouteIsDirty,
|
|
41
|
+
validateNewUserCredentials,
|
|
65
42
|
saveAuthCreateWithClearedCredentials,
|
|
66
43
|
type AuthCreateCredentials,
|
|
67
|
-
} from "@admin/features/documents/document-
|
|
44
|
+
} from "@admin/features/documents/document-credentials";
|
|
68
45
|
import LivePreviewPanel from "@admin/features/documents/live-preview-panel.svelte";
|
|
69
46
|
import UploadDocumentPreview from "@admin/features/documents/upload-document-preview-loader.svelte";
|
|
70
47
|
import DocumentFieldSections from "@admin/features/documents/document-field-sections.svelte";
|
|
71
48
|
import DocumentAPIView from "@admin/features/documents/document-api-view-loader.svelte";
|
|
72
49
|
|
|
50
|
+
import { getBreadcrumbs } from "@admin/features/navigation/breadcrumb-context.svelte";
|
|
51
|
+
import DocumentActionBar from "@admin/features/documents/document-action-bar.svelte";
|
|
52
|
+
import DocumentOperationDialogs from "@admin/features/documents/document-operation-dialogs.svelte";
|
|
53
|
+
import DocumentCredentials from "@admin/features/documents/document-credentials.svelte";
|
|
54
|
+
|
|
55
|
+
import "@admin/features/documents/document.scss";
|
|
56
|
+
|
|
73
57
|
const runtime = getAdminRuntime();
|
|
58
|
+
const bootstrap = getAdminBootstrapCoordinator();
|
|
74
59
|
const notifications = getNotificationCenter();
|
|
75
60
|
const navigate = useNavigate();
|
|
76
|
-
const params =
|
|
77
|
-
const location =
|
|
78
|
-
const
|
|
79
|
-
|
|
61
|
+
const params = useParams<"collection" | "document" | "global">();
|
|
62
|
+
const location = useLocation();
|
|
63
|
+
const preparedRoute = $derived(
|
|
64
|
+
bootstrap.routeData(location.current.pathname, location.current.search)
|
|
65
|
+
);
|
|
66
|
+
const globalResource = $derived(params.current.global !== undefined);
|
|
67
|
+
const slug = $derived(params.current.global ?? params.current.collection ?? "");
|
|
80
68
|
let routeDocumentID = $state(
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
69
|
+
params.current.document === undefined
|
|
70
|
+
? undefined
|
|
71
|
+
: documentIDFromAdminPath(location.current.pathname)
|
|
84
72
|
);
|
|
85
|
-
$effect(() => {
|
|
86
|
-
if (params.document === undefined)
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const decoded = documentIDFromAdminPath(location.pathname);
|
|
91
|
-
// A parent route can remain mounted for one render after navigation has
|
|
92
|
-
// moved to the collection list. Retain its exact ID until unmount so the
|
|
93
|
-
// document controller does not transiently enter create mode.
|
|
73
|
+
$effect.pre(() => {
|
|
74
|
+
if (params.current.document === undefined) return;
|
|
75
|
+
const decoded = documentIDFromAdminPath(location.current.pathname);
|
|
76
|
+
// Parent route params can clear one render before unmount. Retain the exact
|
|
77
|
+
// document owner so the outgoing editor cannot transiently enter create mode.
|
|
94
78
|
if (decoded !== undefined) routeDocumentID = decoded;
|
|
95
79
|
});
|
|
96
80
|
const localization = $derived(runtime.manifest?.application.localization);
|
|
@@ -99,19 +83,16 @@
|
|
|
99
83
|
(candidate) => candidate.slug === slug
|
|
100
84
|
)
|
|
101
85
|
);
|
|
102
|
-
const localeEnabled = $derived(resourceSchema?.fields
|
|
103
|
-
const requestedLocale = $derived(new URLSearchParams(location.search).get("locale"));
|
|
104
|
-
const activeLocale = $derived(
|
|
105
|
-
localization?.locales.some((locale) => locale.code === requestedLocale)
|
|
106
|
-
? (requestedLocale ?? localization?.defaultLocale)
|
|
107
|
-
: (runtime.contentLocale ?? localization?.defaultLocale)
|
|
108
|
-
);
|
|
86
|
+
const localeEnabled = $derived(fieldsUseLocalization(resourceSchema?.fields ?? []));
|
|
87
|
+
const requestedLocale = $derived(new URLSearchParams(location.current.search).get("locale"));
|
|
88
|
+
const activeLocale = $derived(runtime.resolveContentLocale(requestedLocale));
|
|
109
89
|
const activeLocaleConfig = $derived(
|
|
110
90
|
localeEnabled ? localization?.locales.find((locale) => locale.code === activeLocale) : undefined
|
|
111
91
|
);
|
|
112
92
|
const routeDocumentView = $derived<"edit" | "api">(
|
|
113
|
-
location.pathname.endsWith("/api") ? "api" : "edit"
|
|
93
|
+
location.current.pathname.endsWith("/api") ? "api" : "edit"
|
|
114
94
|
);
|
|
95
|
+
let documentView = $derived<string>(routeDocumentView);
|
|
115
96
|
const editPath = $derived(
|
|
116
97
|
withContentLocale(
|
|
117
98
|
globalResource
|
|
@@ -151,81 +132,30 @@
|
|
|
151
132
|
get global() {
|
|
152
133
|
return globalResource;
|
|
153
134
|
},
|
|
154
|
-
get view() {
|
|
155
|
-
return routeDocumentView;
|
|
156
|
-
},
|
|
157
135
|
get locale() {
|
|
158
136
|
return activeLocale;
|
|
159
137
|
},
|
|
138
|
+
get editable() {
|
|
139
|
+
return routeDocumentView === "edit";
|
|
140
|
+
},
|
|
141
|
+
get prepared() {
|
|
142
|
+
if (preparedRoute?.kind === "collection-create") return preparedRoute.create;
|
|
143
|
+
if (
|
|
144
|
+
preparedRoute?.kind === "collection-document" ||
|
|
145
|
+
preparedRoute?.kind === "global-document" ||
|
|
146
|
+
preparedRoute?.kind === "collection-api" ||
|
|
147
|
+
preparedRoute?.kind === "global-api"
|
|
148
|
+
)
|
|
149
|
+
return preparedRoute.document;
|
|
150
|
+
return undefined;
|
|
151
|
+
},
|
|
160
152
|
});
|
|
161
153
|
const form = controller.form;
|
|
162
|
-
const {
|
|
163
|
-
assetFilename,
|
|
164
|
-
assetURL,
|
|
165
|
-
canDelete,
|
|
166
|
-
canDuplicate,
|
|
167
|
-
canEditImage,
|
|
168
|
-
canPublish,
|
|
169
|
-
canReadVersions,
|
|
170
|
-
canSave,
|
|
171
|
-
canTakeOverLock,
|
|
172
|
-
canUnpublish,
|
|
173
|
-
collection,
|
|
174
|
-
collectionSlug,
|
|
175
|
-
copiedAssetURL,
|
|
176
|
-
creating,
|
|
177
|
-
currentDocument,
|
|
178
|
-
currentStatus,
|
|
179
|
-
draftsCollection,
|
|
180
|
-
documentFields,
|
|
181
|
-
documentHeading,
|
|
182
|
-
documentID,
|
|
183
|
-
documentView,
|
|
184
|
-
duplicateOperation,
|
|
185
|
-
error,
|
|
186
|
-
imageOperation,
|
|
187
|
-
imageOutcomeUncertain,
|
|
188
|
-
loading,
|
|
189
|
-
lockedByAnotherEditor,
|
|
190
|
-
hasUnsavedChanges,
|
|
191
|
-
lockOperation,
|
|
192
|
-
lockOwnerLabel,
|
|
193
|
-
lockUpdatedAt,
|
|
194
|
-
pendingRestore,
|
|
195
|
-
saveOutcomeUncertain,
|
|
196
|
-
selectedFile,
|
|
197
|
-
uploadCollection,
|
|
198
|
-
versionOperation,
|
|
199
|
-
versionedCollection,
|
|
200
|
-
versions,
|
|
201
|
-
} = $derived(controller);
|
|
202
|
-
const {
|
|
203
|
-
changePublication,
|
|
204
|
-
copyAssetURL,
|
|
205
|
-
duplicate,
|
|
206
|
-
documentDate,
|
|
207
|
-
remove,
|
|
208
|
-
requestRestore,
|
|
209
|
-
restore,
|
|
210
|
-
save,
|
|
211
|
-
takeOverLock,
|
|
212
|
-
updateUploadImage,
|
|
213
|
-
versionDate,
|
|
214
|
-
} = controller;
|
|
215
|
-
const customDocumentActions = $derived(
|
|
216
|
-
globalResource
|
|
217
|
-
? []
|
|
218
|
-
: runtime.documentActions.filter(
|
|
219
|
-
(action) =>
|
|
220
|
-
(action.collection === undefined || action.collection === collectionSlug) &&
|
|
221
|
-
(action.requires === undefined || form.access?.operations[action.requires] === true)
|
|
222
|
-
)
|
|
223
|
-
);
|
|
224
154
|
const customDocumentViews = $derived(
|
|
225
|
-
currentDocument === undefined
|
|
155
|
+
controller.currentDocument === undefined
|
|
226
156
|
? []
|
|
227
|
-
: runtime.documentViews.filter(
|
|
228
|
-
(view) => view.collection === undefined || view.collection === collectionSlug
|
|
157
|
+
: runtime.config.extensions.documentViews.filter(
|
|
158
|
+
(view) => view.collection === undefined || view.collection === controller.collectionSlug
|
|
229
159
|
)
|
|
230
160
|
);
|
|
231
161
|
const customDocumentView = $derived(
|
|
@@ -235,65 +165,65 @@
|
|
|
235
165
|
refresh: controller.refresh,
|
|
236
166
|
notify: (tone, title, message) => notifications[tone]({ title, message }),
|
|
237
167
|
};
|
|
238
|
-
let
|
|
239
|
-
let newUserPassword = $state("");
|
|
240
|
-
let newUserPasswordConfirmation = $state("");
|
|
168
|
+
let credentials = $state<AuthCreateCredentials>({ password: "", passwordConfirmation: "" });
|
|
241
169
|
let credentialIssue = $state<string>();
|
|
242
170
|
let livePreviewOpen = $state(false);
|
|
243
|
-
let
|
|
244
|
-
let
|
|
171
|
+
let viewport: HTMLElement | null = null;
|
|
172
|
+
let fieldsViewport: HTMLElement | null = null;
|
|
173
|
+
const attachViewport: Attachment<HTMLElement> = (element) => {
|
|
174
|
+
viewport = element;
|
|
175
|
+
return () => {
|
|
176
|
+
if (viewport === element) viewport = null;
|
|
177
|
+
};
|
|
178
|
+
};
|
|
179
|
+
const attachFieldsViewport: Attachment<HTMLElement> = (element) => {
|
|
180
|
+
fieldsViewport = element;
|
|
181
|
+
return () => {
|
|
182
|
+
if (fieldsViewport === element) fieldsViewport = null;
|
|
183
|
+
};
|
|
184
|
+
};
|
|
185
|
+
const splitPreview = new MediaQuery("(min-width: 1240px)");
|
|
186
|
+
registerAdminScrollPage({
|
|
187
|
+
ready: () => !controller.loading,
|
|
188
|
+
target: () =>
|
|
189
|
+
documentView !== "api" && livePreviewOpen && splitPreview.current ? fieldsViewport : viewport,
|
|
190
|
+
});
|
|
245
191
|
const livePreviewRouteKey = $derived(
|
|
246
|
-
`${globalResource ? "global" : "collection"}:${collectionSlug}:${documentID ?? collectionSlug}`
|
|
192
|
+
`${globalResource ? "global" : "collection"}:${controller.collectionSlug}:${controller.documentID ?? controller.collectionSlug}`
|
|
247
193
|
);
|
|
248
194
|
let previousLivePreviewRouteKey: string | undefined;
|
|
249
|
-
|
|
250
|
-
const livePreview = $derived(collection?.admin.livePreview);
|
|
195
|
+
const livePreview = $derived(controller.collection?.admin.livePreview);
|
|
251
196
|
$effect(() => {
|
|
252
|
-
const state = location.state as { openLivePreview?: boolean } | null;
|
|
197
|
+
const state = location.current.state as { openLivePreview?: boolean } | null;
|
|
253
198
|
if (routeDocumentView !== "edit" || state?.openLivePreview !== true) return;
|
|
254
199
|
livePreviewOpen = true;
|
|
255
200
|
// API and edit views have separate route instances. Consume the transient
|
|
256
201
|
// intent on arrival so Back or reload cannot reopen a dismissed preview.
|
|
257
202
|
const { openLivePreview: _, ...remainingState } = state;
|
|
258
|
-
navigate(`${location.pathname}${location.search}${location.hash}`, {
|
|
203
|
+
navigate(`${location.current.pathname}${location.current.search}${location.current.hash}`, {
|
|
259
204
|
replace: true,
|
|
260
205
|
state: remainingState,
|
|
261
206
|
});
|
|
262
207
|
});
|
|
263
|
-
const creatingAuthUser = $derived(
|
|
264
|
-
creating && !globalResource && collection?.capabilities.auth === true
|
|
265
|
-
);
|
|
266
208
|
const dirty = $derived(
|
|
267
209
|
documentRouteIsDirty({
|
|
268
|
-
documentValuesDirty: hasUnsavedChanges,
|
|
269
|
-
creatingAuthUser,
|
|
270
|
-
password:
|
|
271
|
-
passwordConfirmation:
|
|
210
|
+
documentValuesDirty: controller.hasUnsavedChanges,
|
|
211
|
+
creatingAuthUser: controller.creatingAuthUser,
|
|
212
|
+
password: credentials.password,
|
|
213
|
+
passwordConfirmation: credentials.passwordConfirmation,
|
|
272
214
|
})
|
|
273
215
|
);
|
|
274
216
|
const navigationBlocker = useBlocker(() => dirty || form.submitting);
|
|
275
|
-
|
|
276
|
-
!globalResource &&
|
|
277
|
-
!creating &&
|
|
278
|
-
collection?.capabilities.auth === true &&
|
|
279
|
-
(collection.authSettings?.maxLoginAttempts ?? 0) > 0 &&
|
|
280
|
-
form.access?.operations.update === true
|
|
281
|
-
);
|
|
282
|
-
$effect(() => {
|
|
283
|
-
if (activeLocale === undefined || requestedLocale === activeLocale) return;
|
|
284
|
-
const next = new URLSearchParams(location.search);
|
|
285
|
-
next.set("locale", activeLocale);
|
|
286
|
-
navigate(`${location.pathname}?${next.toString()}${location.hash}`, { replace: true });
|
|
287
|
-
});
|
|
217
|
+
|
|
288
218
|
$effect(() => {
|
|
289
219
|
return runtime.registerContentLocaleBlocker(
|
|
290
220
|
() =>
|
|
291
221
|
dirty ||
|
|
292
222
|
form.submitting ||
|
|
293
|
-
controller.
|
|
294
|
-
controller.
|
|
223
|
+
controller.publicationOperation ||
|
|
224
|
+
controller.upload.busy ||
|
|
295
225
|
controller.duplicateOperation ||
|
|
296
|
-
copyLocaleOperation
|
|
226
|
+
controller.copyLocaleOperation
|
|
297
227
|
);
|
|
298
228
|
});
|
|
299
229
|
$effect(() => {
|
|
@@ -322,127 +252,69 @@
|
|
|
322
252
|
}
|
|
323
253
|
});
|
|
324
254
|
|
|
325
|
-
async function forceUnlock() {
|
|
326
|
-
if (collection === undefined || currentDocument === undefined) return;
|
|
327
|
-
unlockOperation = true;
|
|
328
|
-
try {
|
|
329
|
-
await runtime.client.forceUnlock(collection.slug, currentDocument.id);
|
|
330
|
-
notifications.success({ title: runtime.i18n.t("documents:accountUnlocked") });
|
|
331
|
-
} catch (cause) {
|
|
332
|
-
notifications.error({
|
|
333
|
-
title: runtime.i18n.t("documents:accountUnlockFailed"),
|
|
334
|
-
message: cause instanceof Error ? cause.message : undefined,
|
|
335
|
-
});
|
|
336
|
-
} finally {
|
|
337
|
-
unlockOperation = false;
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
255
|
function toggleLivePreview() {
|
|
342
256
|
if (!livePreviewOpen && routeDocumentView === "api") {
|
|
343
257
|
navigate(editPath, { replace: true, state: { openLivePreview: true } });
|
|
344
258
|
return;
|
|
345
259
|
}
|
|
346
260
|
livePreviewOpen = !livePreviewOpen;
|
|
347
|
-
if (livePreviewOpen)
|
|
348
|
-
controller.documentView = "edit";
|
|
349
|
-
}
|
|
261
|
+
if (livePreviewOpen) documentView = "edit";
|
|
350
262
|
}
|
|
351
263
|
|
|
352
264
|
function selectCustomDocumentView(view: string) {
|
|
353
|
-
|
|
265
|
+
documentView = view;
|
|
354
266
|
if (routeDocumentView === "api") navigate(editPath, { replace: true });
|
|
355
267
|
}
|
|
356
268
|
|
|
357
|
-
async function copyFromLocale(source: string) {
|
|
358
|
-
if (
|
|
359
|
-
activeLocale === undefined ||
|
|
360
|
-
source === activeLocale ||
|
|
361
|
-
currentDocument === undefined ||
|
|
362
|
-
copyLocaleOperation
|
|
363
|
-
)
|
|
364
|
-
return;
|
|
365
|
-
copyLocaleOperation = true;
|
|
366
|
-
try {
|
|
367
|
-
if (globalResource) {
|
|
368
|
-
await runtime.client.copyGlobalLocale(
|
|
369
|
-
slug,
|
|
370
|
-
{ from: source, to: activeLocale },
|
|
371
|
-
{ revision: currentDocument._revision }
|
|
372
|
-
);
|
|
373
|
-
} else {
|
|
374
|
-
await runtime.client.copyLocale(
|
|
375
|
-
slug,
|
|
376
|
-
currentDocument.id,
|
|
377
|
-
{ from: source, to: activeLocale },
|
|
378
|
-
{ revision: currentDocument._revision }
|
|
379
|
-
);
|
|
380
|
-
}
|
|
381
|
-
await controller.refresh();
|
|
382
|
-
runtime.documentsChanged();
|
|
383
|
-
notifications.success({
|
|
384
|
-
title: runtime.i18n.t("documents:localeCopied"),
|
|
385
|
-
message: runtime.i18n.t("documents:localeCopiedDescription", {
|
|
386
|
-
source,
|
|
387
|
-
destination: activeLocale,
|
|
388
|
-
}),
|
|
389
|
-
});
|
|
390
|
-
} catch (cause) {
|
|
391
|
-
notifications.error({
|
|
392
|
-
title: runtime.i18n.t("documents:localeNotCopied"),
|
|
393
|
-
message:
|
|
394
|
-
cause instanceof Error ? cause.message : runtime.i18n.t("documents:localeCopyFailed"),
|
|
395
|
-
});
|
|
396
|
-
} finally {
|
|
397
|
-
copyLocaleOperation = false;
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
async function handleTakeover() {
|
|
402
|
-
await takeOverLock();
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
const lockKey = $derived(
|
|
406
|
-
lockedByAnotherEditor
|
|
407
|
-
? `${collectionSlug}:${controller.documentID ?? ""}:${lockOwnerLabel ?? ""}:${lockUpdatedAt ?? ""}`
|
|
408
|
-
: undefined
|
|
409
|
-
);
|
|
410
|
-
|
|
411
269
|
async function submitDocument(event: Event, publish: boolean) {
|
|
412
270
|
event.preventDefault();
|
|
413
|
-
if (creatingAuthUser) {
|
|
414
|
-
credentialIssue =
|
|
271
|
+
if (controller.creatingAuthUser) {
|
|
272
|
+
credentialIssue = validateNewUserCredentials(
|
|
273
|
+
credentials,
|
|
274
|
+
controller.collection?.authSettings,
|
|
275
|
+
runtime.i18n
|
|
276
|
+
);
|
|
415
277
|
if (credentialIssue !== undefined) {
|
|
416
278
|
document.getElementById("ridu-new-user-password")?.focus();
|
|
417
279
|
return;
|
|
418
280
|
}
|
|
419
281
|
}
|
|
420
|
-
const saved = creatingAuthUser
|
|
282
|
+
const saved = controller.creatingAuthUser
|
|
421
283
|
? await saveAuthCreateWithClearedCredentials(
|
|
422
284
|
{
|
|
423
|
-
password:
|
|
424
|
-
passwordConfirmation:
|
|
285
|
+
password: credentials.password,
|
|
286
|
+
passwordConfirmation: credentials.passwordConfirmation,
|
|
425
287
|
},
|
|
426
288
|
replaceNewUserCredentials,
|
|
427
|
-
(password) => save({ password, publish })
|
|
289
|
+
(password) => controller.save({ password, publish })
|
|
428
290
|
)
|
|
429
|
-
: await save({ publish });
|
|
291
|
+
: await controller.save({ publish });
|
|
430
292
|
if (saved) {
|
|
431
293
|
resetNewUserCredentials();
|
|
432
294
|
return;
|
|
433
295
|
}
|
|
434
|
-
const issuePath =
|
|
296
|
+
const issuePath = form.issues[0]?.path;
|
|
435
297
|
if (issuePath === undefined) return;
|
|
298
|
+
documentView = "edit";
|
|
436
299
|
await tick();
|
|
437
|
-
await focusFieldIssue(issuePath);
|
|
300
|
+
if (fieldsViewport) await focusFieldIssue(issuePath, fieldsViewport);
|
|
438
301
|
}
|
|
439
302
|
|
|
440
|
-
|
|
441
|
-
|
|
303
|
+
function handleSave(event: Event) {
|
|
304
|
+
return submitDocument(
|
|
305
|
+
event,
|
|
306
|
+
!controller.creating &&
|
|
307
|
+
controller.versionedCollection &&
|
|
308
|
+
controller.currentStatus === "published"
|
|
309
|
+
);
|
|
442
310
|
}
|
|
443
311
|
|
|
444
|
-
|
|
445
|
-
|
|
312
|
+
function handlePublish(event: Event) {
|
|
313
|
+
if (!controller.creating && controller.currentStatus === "draft" && !dirty) {
|
|
314
|
+
event.preventDefault();
|
|
315
|
+
return controller.changePublication("published");
|
|
316
|
+
}
|
|
317
|
+
return submitDocument(event, true);
|
|
446
318
|
}
|
|
447
319
|
|
|
448
320
|
function cancelNavigation() {
|
|
@@ -456,512 +328,254 @@
|
|
|
456
328
|
navigationBlocker.proceed();
|
|
457
329
|
}
|
|
458
330
|
|
|
331
|
+
const breadcrumbs = getBreadcrumbs();
|
|
332
|
+
$effect(() => {
|
|
333
|
+
if (!breadcrumbs) return;
|
|
334
|
+
const entry = { pathname: location.current.pathname, label: controller.documentHeading };
|
|
335
|
+
breadcrumbs.document = entry;
|
|
336
|
+
return () => {
|
|
337
|
+
if (breadcrumbs.document === entry) breadcrumbs.document = undefined;
|
|
338
|
+
};
|
|
339
|
+
});
|
|
340
|
+
|
|
459
341
|
function resetNewUserCredentials() {
|
|
460
342
|
replaceNewUserCredentials({ password: "", passwordConfirmation: "" });
|
|
461
343
|
}
|
|
462
344
|
|
|
463
|
-
function replaceNewUserCredentials(
|
|
464
|
-
|
|
465
|
-
newUserPasswordConfirmation = credentials.passwordConfirmation;
|
|
345
|
+
function replaceNewUserCredentials(next: AuthCreateCredentials) {
|
|
346
|
+
credentials = next;
|
|
466
347
|
credentialIssue = undefined;
|
|
467
348
|
}
|
|
468
|
-
|
|
469
|
-
function validateNewUserPassword() {
|
|
470
|
-
if (!creatingAuthUser || collection?.authSettings === undefined) return undefined;
|
|
471
|
-
if (newUserPassword === "") return runtime.i18n.t("documents:enterNewAccountPassword");
|
|
472
|
-
if (Array.from(newUserPassword).length < collection.authSettings.passwordMinLength) {
|
|
473
|
-
return runtime.i18n.t("documents:passwordTooShort", {
|
|
474
|
-
minimum: runtime.i18n.formatNumber(collection.authSettings.passwordMinLength),
|
|
475
|
-
});
|
|
476
|
-
}
|
|
477
|
-
if (
|
|
478
|
-
new TextEncoder().encode(newUserPassword).length > collection.authSettings.passwordMaxBytes
|
|
479
|
-
) {
|
|
480
|
-
return runtime.i18n.t("documents:passwordTooLong", {
|
|
481
|
-
maximum: runtime.i18n.formatNumber(collection.authSettings.passwordMaxBytes),
|
|
482
|
-
});
|
|
483
|
-
}
|
|
484
|
-
if (newUserPassword !== newUserPasswordConfirmation)
|
|
485
|
-
return runtime.i18n.t("documents:passwordsDoNotMatch");
|
|
486
|
-
return undefined;
|
|
487
|
-
}
|
|
488
349
|
</script>
|
|
489
350
|
|
|
490
351
|
<section
|
|
491
|
-
|
|
352
|
+
{@attach attachViewport}
|
|
353
|
+
data-slot="document-viewport"
|
|
354
|
+
class={["ridu-document", documentView === "api" && "ridu-document--api"]}
|
|
492
355
|
>
|
|
493
|
-
<header class="
|
|
494
|
-
<div
|
|
495
|
-
class="flex min-h-[88px] flex-col justify-center gap-3 px-5 py-3 sm:px-8 lg:flex-row lg:items-center lg:justify-between lg:px-[60px]"
|
|
496
|
-
>
|
|
356
|
+
<header class="ridu-document-header">
|
|
357
|
+
<div class="ridu-document-heading">
|
|
497
358
|
<h1
|
|
498
|
-
class="
|
|
499
|
-
aria-busy={loading &&
|
|
359
|
+
class="ridu-document-title"
|
|
360
|
+
aria-busy={controller.loading &&
|
|
361
|
+
controller.currentDocument === undefined &&
|
|
362
|
+
!controller.creating}
|
|
500
363
|
>
|
|
501
|
-
{documentHeading}
|
|
364
|
+
{controller.documentHeading}
|
|
502
365
|
</h1>
|
|
503
|
-
|
|
504
|
-
class="
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
class={[
|
|
510
|
-
"mt-2 flex h-8 items-center px-3 text-[12.5px] text-foreground-muted lg:mt-0",
|
|
511
|
-
documentView === "edit" && "bg-control text-foreground-strong",
|
|
512
|
-
]}
|
|
513
|
-
onclick={() => (controller.documentView = "edit")}
|
|
514
|
-
>
|
|
515
|
-
{runtime.i18n.t("documents:edit")}
|
|
516
|
-
</Link>
|
|
517
|
-
<Link
|
|
518
|
-
to={apiPath}
|
|
519
|
-
class={[
|
|
520
|
-
"mt-2 flex h-8 items-center px-3 text-[12.5px] text-foreground-muted lg:mt-0",
|
|
521
|
-
documentView === "api" && "bg-control text-foreground-strong",
|
|
522
|
-
]}
|
|
523
|
-
>
|
|
524
|
-
{runtime.i18n.t("documents:api")}
|
|
525
|
-
</Link>
|
|
526
|
-
{#each customDocumentViews as view (view.key)}<button
|
|
527
|
-
type="button"
|
|
528
|
-
class={[
|
|
529
|
-
"mt-2 h-8 shrink-0 px-3 text-[12.5px] text-foreground-muted lg:mt-0",
|
|
530
|
-
documentView === view.key && "bg-control text-foreground-strong",
|
|
531
|
-
]}
|
|
532
|
-
onclick={() => selectCustomDocumentView(view.key)}
|
|
366
|
+
{#if !controller.creating || routeDocumentView === "api"}
|
|
367
|
+
<nav class="ridu-document-tabs" aria-label={runtime.i18n.t("documents:documentViews")}>
|
|
368
|
+
<Link
|
|
369
|
+
to={editPath}
|
|
370
|
+
class={["ridu-document-tab", documentView === "edit" && "ridu-document-tab--active"]}
|
|
371
|
+
onclick={() => (documentView = "edit")}
|
|
533
372
|
>
|
|
534
|
-
{
|
|
535
|
-
</
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
373
|
+
{runtime.i18n.t("documents:edit")}
|
|
374
|
+
</Link>
|
|
375
|
+
|
|
376
|
+
{#each customDocumentViews as view (view.key)}
|
|
377
|
+
<button
|
|
378
|
+
type="button"
|
|
379
|
+
class={[
|
|
380
|
+
"ridu-document-tab",
|
|
381
|
+
documentView === view.key && "ridu-document-tab--active",
|
|
382
|
+
]}
|
|
383
|
+
onclick={() => selectCustomDocumentView(view.key)}
|
|
384
|
+
>
|
|
385
|
+
{view.labelKey === undefined ? view.label : runtime.i18n.t(view.labelKey)}
|
|
386
|
+
</button>
|
|
387
|
+
{/each}
|
|
388
|
+
{#if !controller.creating && controller.versionedCollection && controller.canReadVersions}
|
|
389
|
+
<Link class="ridu-document-tab" to={versionHistoryPath}>
|
|
390
|
+
{runtime.i18n.t("documents:versions")}
|
|
391
|
+
</Link>
|
|
392
|
+
{/if}
|
|
393
|
+
<Link
|
|
394
|
+
to={apiPath}
|
|
395
|
+
class={["ridu-document-tab", documentView === "api" && "ridu-document-tab--active"]}
|
|
539
396
|
>
|
|
540
|
-
{runtime.i18n.t("documents:
|
|
541
|
-
</Link>
|
|
542
|
-
|
|
397
|
+
{runtime.i18n.t("documents:api")}
|
|
398
|
+
</Link>
|
|
399
|
+
</nav>
|
|
400
|
+
{/if}
|
|
543
401
|
</div>
|
|
402
|
+
{#if documentView !== "api" && controller.collection?.admin.description}
|
|
403
|
+
<p class="ridu-document-description">
|
|
404
|
+
{runtime.i18n.text(
|
|
405
|
+
controller.collection.admin.description,
|
|
406
|
+
controller.collection.admin.descriptionTranslations
|
|
407
|
+
)}
|
|
408
|
+
</p>
|
|
409
|
+
{/if}
|
|
544
410
|
</header>
|
|
545
411
|
|
|
412
|
+
{#if documentView !== "api"}
|
|
413
|
+
<!-- Editor-owned overlays and extension actions must not survive a route or locale identity change. -->
|
|
414
|
+
{#key `${livePreviewRouteKey}:${activeLocale ?? ""}`}
|
|
415
|
+
<DocumentActionBar
|
|
416
|
+
{controller}
|
|
417
|
+
{extensionHost}
|
|
418
|
+
{localeEnabled}
|
|
419
|
+
{activeLocale}
|
|
420
|
+
{livePreviewOpen}
|
|
421
|
+
onTogglePreview={toggleLivePreview}
|
|
422
|
+
onSave={handleSave}
|
|
423
|
+
onPublish={handlePublish}
|
|
424
|
+
/>
|
|
425
|
+
{/key}
|
|
426
|
+
{/if}
|
|
427
|
+
|
|
546
428
|
<div
|
|
547
|
-
class=
|
|
429
|
+
class={[
|
|
430
|
+
"ridu-document-body",
|
|
431
|
+
{ "ridu-document-body--preview": documentView !== "api" && livePreviewOpen },
|
|
432
|
+
]}
|
|
548
433
|
>
|
|
549
434
|
<div
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
<div class="flex items-center gap-2.5">
|
|
554
|
-
<Select
|
|
555
|
-
type="single"
|
|
556
|
-
value=""
|
|
557
|
-
disabled={form.dirty || loading || form.submitting || copyLocaleOperation}
|
|
558
|
-
onValueChange={copyFromLocale}
|
|
559
|
-
>
|
|
560
|
-
<SelectTrigger
|
|
561
|
-
size="compact"
|
|
562
|
-
class="min-w-28"
|
|
563
|
-
aria-label={runtime.i18n.t("documents:copyFromLocale")}
|
|
564
|
-
>
|
|
565
|
-
<span>
|
|
566
|
-
{copyLocaleOperation
|
|
567
|
-
? runtime.i18n.t("documents:copying")
|
|
568
|
-
: runtime.i18n.t("documents:copyFrom")}
|
|
569
|
-
</span>
|
|
570
|
-
</SelectTrigger>
|
|
571
|
-
<SelectContent>
|
|
572
|
-
{#each localization.locales.filter((locale) => locale.code !== activeLocale) as locale (locale.code)}
|
|
573
|
-
<SelectItem value={locale.code} label={locale.label}>
|
|
574
|
-
<span>{locale.label}</span>
|
|
575
|
-
<span class="ms-1 text-foreground-faint">{locale.code}</span>
|
|
576
|
-
</SelectItem>
|
|
577
|
-
{/each}
|
|
578
|
-
</SelectContent>
|
|
579
|
-
</Select>
|
|
580
|
-
</div>
|
|
581
|
-
{/if}
|
|
582
|
-
<span>
|
|
583
|
-
{runtime.i18n.t("documents:status")}:
|
|
584
|
-
<strong class="font-semibold text-foreground-strong">
|
|
585
|
-
{versionedCollection
|
|
586
|
-
? runtime.i18n.t(currentStatus === "draft" ? "documents:draft" : "documents:published")
|
|
587
|
-
: creating
|
|
588
|
-
? runtime.i18n.t("documents:draft")
|
|
589
|
-
: runtime.i18n.t("documents:saved")}
|
|
590
|
-
</strong>
|
|
591
|
-
</span>
|
|
592
|
-
{#if !creating && currentDocument !== undefined}
|
|
593
|
-
<span class="hidden sm:inline">
|
|
594
|
-
{runtime.i18n.t("documents:lastModifiedLabel")}:
|
|
595
|
-
<strong class="font-semibold text-foreground-strong">
|
|
596
|
-
{documentDate(currentDocument.updatedAt)}
|
|
597
|
-
</strong>
|
|
598
|
-
</span>
|
|
599
|
-
<span class="hidden md:inline">
|
|
600
|
-
{runtime.i18n.t("documents:createdLabelPlain")}:
|
|
601
|
-
<strong class="font-semibold text-foreground-strong">
|
|
602
|
-
{documentDate(currentDocument.createdAt)}
|
|
603
|
-
</strong>
|
|
604
|
-
</span>
|
|
605
|
-
{/if}
|
|
606
|
-
</div>
|
|
607
|
-
|
|
608
|
-
<div
|
|
609
|
-
class="flex min-h-12 items-center gap-2 border-t border-control-border bg-background px-5 sm:px-8 lg:px-[60px] xl:col-start-1 xl:row-start-1 xl:min-h-14 xl:w-fit xl:justify-self-end xl:border-t-0 xl:pe-[60px] xl:ps-0"
|
|
435
|
+
{@attach attachFieldsViewport}
|
|
436
|
+
data-slot="document-fields-viewport"
|
|
437
|
+
class="ridu-document-editor"
|
|
610
438
|
>
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
{/if}
|
|
622
|
-
{#if canTakeOverLock}<Button
|
|
623
|
-
variant="outline"
|
|
624
|
-
size="sm"
|
|
625
|
-
disabled={lockOperation}
|
|
626
|
-
onclick={handleTakeover}
|
|
627
|
-
>
|
|
628
|
-
<LockKeyholeIcon class="size-3.5" aria-hidden="true" />{lockOperation
|
|
629
|
-
? runtime.i18n.t("documents:takingOver")
|
|
630
|
-
: runtime.i18n.t("documents:takeOver")}
|
|
631
|
-
</Button>{/if}
|
|
632
|
-
{#if creating && draftsCollection && canPublish && !uploadCollection && !creatingAuthUser}
|
|
633
|
-
<Button
|
|
634
|
-
variant="outline"
|
|
635
|
-
size="sm"
|
|
636
|
-
disabled={!canSave}
|
|
637
|
-
aria-busy={form.submitting}
|
|
638
|
-
onclick={handleSave}
|
|
639
|
-
>
|
|
640
|
-
{runtime.i18n.t("documents:saveDraft")}
|
|
641
|
-
</Button>
|
|
642
|
-
<Button size="sm" disabled={!canSave} aria-busy={form.submitting} onclick={handlePublish}>
|
|
643
|
-
{runtime.i18n.t("documents:publish")}
|
|
644
|
-
</Button>
|
|
645
|
-
{:else if creating && versionedCollection && !draftsCollection}
|
|
646
|
-
<Button
|
|
647
|
-
size="sm"
|
|
648
|
-
disabled={!canSave || !canPublish}
|
|
649
|
-
aria-busy={form.submitting}
|
|
650
|
-
onclick={handlePublish}
|
|
651
|
-
>
|
|
652
|
-
{runtime.i18n.t("documents:publish")}
|
|
653
|
-
</Button>
|
|
654
|
-
{:else}
|
|
655
|
-
<Button
|
|
656
|
-
size="sm"
|
|
657
|
-
disabled={!canSave ||
|
|
658
|
-
(versionedCollection && currentStatus === "published" && !canPublish)}
|
|
659
|
-
aria-busy={form.submitting}
|
|
660
|
-
onclick={handleSave}
|
|
661
|
-
>
|
|
662
|
-
{creating && draftsCollection
|
|
663
|
-
? runtime.i18n.t("documents:saveDraft")
|
|
664
|
-
: versionedCollection
|
|
665
|
-
? currentStatus === "published"
|
|
666
|
-
? runtime.i18n.t("documents:publishChanges")
|
|
667
|
-
: runtime.i18n.t("documents:saveDraft")
|
|
668
|
-
: runtime.i18n.t("documents:save")}
|
|
669
|
-
</Button>
|
|
670
|
-
{/if}
|
|
671
|
-
{#if !creating && versionedCollection && currentStatus === "draft" && canPublish}
|
|
672
|
-
<Button
|
|
673
|
-
variant="outline"
|
|
674
|
-
size="sm"
|
|
675
|
-
disabled={versionOperation || form.dirty || form.submitting}
|
|
676
|
-
onclick={() => changePublication("published")}
|
|
677
|
-
>
|
|
678
|
-
{runtime.i18n.t("documents:publish")}
|
|
679
|
-
</Button>
|
|
680
|
-
{/if}
|
|
681
|
-
<div class="ms-auto flex items-center gap-2">
|
|
682
|
-
{#if collection !== undefined && currentDocument !== undefined && !lockedByAnotherEditor}{#each customDocumentActions as action (action.key)}<action.component
|
|
683
|
-
{collection}
|
|
684
|
-
document={currentDocument}
|
|
685
|
-
host={extensionHost}
|
|
686
|
-
i18n={runtime.i18n}
|
|
687
|
-
/>{/each}{/if}
|
|
688
|
-
{#if (!globalResource && !creating && canDuplicate) || (!globalResource && !creating && uploadCollection && assetURL !== undefined) || (!creating && versionedCollection && currentStatus === "published" && canUnpublish) || (!globalResource && !creating && canDelete) || canForceUnlock}
|
|
689
|
-
<Popover bind:open={moreActionsOpen}>
|
|
690
|
-
<PopoverTrigger
|
|
691
|
-
class={buttonVariants({ variant: "outline", size: "sm", class: "size-9 p-0" })}
|
|
692
|
-
aria-label={runtime.i18n.t("documents:moreActions")}
|
|
439
|
+
<div class="ridu-document-fields">
|
|
440
|
+
{#if controller.lock.error !== undefined}
|
|
441
|
+
<Banner class="ridu-document-notice" tone="destructive">
|
|
442
|
+
<span class="ridu-document-notice__message">{controller.lock.error}</span>
|
|
443
|
+
<Button
|
|
444
|
+
type="button"
|
|
445
|
+
variant="outline"
|
|
446
|
+
size="xs"
|
|
447
|
+
disabled={controller.lock.operation}
|
|
448
|
+
onclick={controller.lock.retry}
|
|
693
449
|
>
|
|
694
|
-
|
|
695
|
-
</
|
|
696
|
-
|
|
697
|
-
{#if !globalResource && !creating && canDuplicate}<Button
|
|
698
|
-
variant="ghost"
|
|
699
|
-
class="w-full justify-start"
|
|
700
|
-
disabled={duplicateOperation}
|
|
701
|
-
onclick={() => {
|
|
702
|
-
moreActionsOpen = false;
|
|
703
|
-
duplicate();
|
|
704
|
-
}}
|
|
705
|
-
>
|
|
706
|
-
<CopyIcon class="size-3.5" />{duplicateOperation
|
|
707
|
-
? runtime.i18n.t("documents:duplicating")
|
|
708
|
-
: runtime.i18n.t("documents:duplicate")}
|
|
709
|
-
</Button>{/if}
|
|
710
|
-
{#if !globalResource && !creating && uploadCollection && assetURL !== undefined}<Button
|
|
711
|
-
variant="ghost"
|
|
712
|
-
class="w-full justify-start"
|
|
713
|
-
href={assetURL}
|
|
714
|
-
download={assetFilename}
|
|
715
|
-
>
|
|
716
|
-
<DownloadIcon class="size-3.5" />
|
|
717
|
-
{runtime.i18n.t("documents:download")}
|
|
718
|
-
</Button>{/if}
|
|
719
|
-
{#if !creating && versionedCollection && currentStatus === "published" && canUnpublish}<Button
|
|
720
|
-
variant="ghost"
|
|
721
|
-
class="w-full justify-start"
|
|
722
|
-
disabled={versionOperation || form.dirty || form.submitting}
|
|
723
|
-
onclick={() => {
|
|
724
|
-
moreActionsOpen = false;
|
|
725
|
-
changePublication("draft");
|
|
726
|
-
}}
|
|
727
|
-
>
|
|
728
|
-
{runtime.i18n.t("documents:unpublish")}
|
|
729
|
-
</Button>{/if}
|
|
730
|
-
{#if !globalResource && !creating && canDelete}<Button
|
|
731
|
-
variant="ghost"
|
|
732
|
-
class="w-full justify-start text-destructive hover:bg-destructive/7 hover:text-destructive"
|
|
733
|
-
onclick={() => {
|
|
734
|
-
moreActionsOpen = false;
|
|
735
|
-
controller.deleteDialogOpen = true;
|
|
736
|
-
}}
|
|
737
|
-
>
|
|
738
|
-
<Trash2Icon class="size-3.5" />
|
|
739
|
-
{runtime.i18n.t(
|
|
740
|
-
uploadCollection ? "documents:deleteAsset" : "documents:deleteDocument"
|
|
741
|
-
)}
|
|
742
|
-
</Button>{/if}
|
|
743
|
-
{#if canForceUnlock}<Button
|
|
744
|
-
variant="ghost"
|
|
745
|
-
class="w-full justify-start"
|
|
746
|
-
disabled={unlockOperation}
|
|
747
|
-
onclick={() => {
|
|
748
|
-
moreActionsOpen = false;
|
|
749
|
-
forceUnlock();
|
|
750
|
-
}}
|
|
751
|
-
>
|
|
752
|
-
<KeyRoundIcon class="size-3.5" aria-hidden="true" />{unlockOperation
|
|
753
|
-
? runtime.i18n.t("documents:unlocking")
|
|
754
|
-
: runtime.i18n.t("documents:forceUnlock")}
|
|
755
|
-
</Button>{/if}
|
|
756
|
-
</PopoverContent>
|
|
757
|
-
</Popover>
|
|
450
|
+
{runtime.i18n.t("general:retry")}
|
|
451
|
+
</Button>
|
|
452
|
+
</Banner>
|
|
758
453
|
{/if}
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
<div
|
|
764
|
-
class={[
|
|
765
|
-
"flex min-h-0 flex-1 flex-col",
|
|
766
|
-
livePreviewOpen && "min-[1240px]:flex-row min-[1240px]:overflow-hidden",
|
|
767
|
-
]}
|
|
768
|
-
>
|
|
769
|
-
<div class={["min-w-0 flex-1", livePreviewOpen && "min-[1240px]:overflow-y-auto"]}>
|
|
770
|
-
<div class="w-full px-5 py-8 sm:px-8 sm:py-9 lg:px-[60px]">
|
|
771
|
-
{#if lockedByAnotherEditor}
|
|
772
|
-
<Banner class="mb-6" tone="warning">
|
|
773
|
-
<LockKeyholeIcon class="mt-0.5 size-4 shrink-0" aria-hidden="true" />
|
|
454
|
+
{#if controller.lock.lockedByAnotherEditor}
|
|
455
|
+
<Banner class="ridu-document-notice" tone="warning">
|
|
456
|
+
<LockKeyholeIcon class="ridu-document-notice__icon" aria-hidden="true" />
|
|
774
457
|
<span>
|
|
775
458
|
{runtime.i18n.t("documents:lockedReadOnly", {
|
|
776
|
-
owner:
|
|
459
|
+
owner: controller.lock.ownerLabel ?? runtime.i18n.t("documents:anotherEditor"),
|
|
777
460
|
})}
|
|
778
461
|
</span>
|
|
779
462
|
</Banner>
|
|
780
463
|
{/if}
|
|
781
|
-
{#if error !== undefined}
|
|
782
|
-
<Banner class="
|
|
783
|
-
<span class="
|
|
784
|
-
{error}
|
|
785
|
-
</Banner>
|
|
786
|
-
{/if}
|
|
787
|
-
|
|
788
|
-
{#if saveOutcomeUncertain}
|
|
789
|
-
<Banner class="mb-6" tone="warning">
|
|
790
|
-
{runtime.i18n.t("uploads:outcomeUnknownDescription")}
|
|
464
|
+
{#if documentView !== "api" && controller.error !== undefined}
|
|
465
|
+
<Banner class="ridu-document-notice" tone="destructive">
|
|
466
|
+
<span class="ridu-document-notice__error-marker" aria-hidden="true"></span>
|
|
467
|
+
{controller.error}
|
|
791
468
|
</Banner>
|
|
792
469
|
{/if}
|
|
793
470
|
|
|
794
|
-
{#if
|
|
795
|
-
<Banner class="
|
|
796
|
-
{runtime.i18n.t(
|
|
471
|
+
{#if controller.saveOutcomeUncertain}
|
|
472
|
+
<Banner class="ridu-document-notice" tone="warning">
|
|
473
|
+
{runtime.i18n.t(
|
|
474
|
+
controller.creating
|
|
475
|
+
? "uploads:outcomeUnknownDescription"
|
|
476
|
+
: "uploads:saveOutcomeUnknownDescription"
|
|
477
|
+
)}
|
|
478
|
+
{#if !controller.creating}
|
|
479
|
+
<Button
|
|
480
|
+
variant="outline"
|
|
481
|
+
size="sm"
|
|
482
|
+
disabled={controller.loading}
|
|
483
|
+
onclick={controller.refresh}
|
|
484
|
+
>
|
|
485
|
+
{runtime.i18n.t("uploads:reloadSaved")}
|
|
486
|
+
</Button>
|
|
487
|
+
{/if}
|
|
797
488
|
</Banner>
|
|
798
489
|
{/if}
|
|
799
490
|
|
|
800
|
-
{#if
|
|
801
|
-
<div class="grid gap-5" aria-label={runtime.i18n.t("documents:loadingDocument")}>
|
|
802
|
-
<Skeleton class="h-12 w-2/3" />
|
|
803
|
-
<Skeleton class="mt-5 h-10.5" />
|
|
804
|
-
<Skeleton class="h-28" />
|
|
805
|
-
</div>
|
|
806
|
-
{:else if !creating && currentDocument === undefined}
|
|
807
|
-
<!-- A failed read has no editable document; the recovery/error banner remains visible. -->
|
|
808
|
-
{:else if documentView === "api"}
|
|
491
|
+
{#if documentView === "api"}
|
|
809
492
|
<DocumentAPIView
|
|
810
493
|
resourceSlug={slug}
|
|
811
|
-
resourceLabel={collection === undefined
|
|
812
|
-
? runtime.i18n.t("documents:document")
|
|
813
|
-
: runtime.i18n.text(
|
|
814
|
-
collection.labels.singular,
|
|
815
|
-
collection.labels.singularTranslations
|
|
816
|
-
)}
|
|
817
494
|
documentID={routeDocumentID}
|
|
818
495
|
{globalResource}
|
|
496
|
+
contentLocale={activeLocale}
|
|
819
497
|
fallbackValue={form.values}
|
|
498
|
+
prepared={preparedRoute?.kind === "collection-api" ||
|
|
499
|
+
preparedRoute?.kind === "global-api"
|
|
500
|
+
? preparedRoute.document.document
|
|
501
|
+
: undefined}
|
|
820
502
|
/>
|
|
821
|
-
{:else if
|
|
503
|
+
{:else if controller.loading}
|
|
504
|
+
<div
|
|
505
|
+
class="ridu-document-loading"
|
|
506
|
+
data-ridu-loading-surface="document"
|
|
507
|
+
aria-label={runtime.i18n.t("documents:loadingDocument")}
|
|
508
|
+
>
|
|
509
|
+
<Skeleton class="ridu-document-loading__heading" />
|
|
510
|
+
<Skeleton class="ridu-document-loading__input" />
|
|
511
|
+
<Skeleton class="ridu-document-loading__textarea" />
|
|
512
|
+
</div>
|
|
513
|
+
{:else if !controller.creating && controller.currentDocument === undefined}
|
|
514
|
+
<!-- A failed read has no editable document; the recovery/error banner remains visible. -->
|
|
515
|
+
{:else if customDocumentView !== undefined && controller.collection !== undefined && controller.currentDocument !== undefined}
|
|
822
516
|
<customDocumentView.component
|
|
823
|
-
{collection}
|
|
824
|
-
document={currentDocument}
|
|
517
|
+
collection={controller.collection}
|
|
518
|
+
document={controller.currentDocument}
|
|
825
519
|
host={extensionHost}
|
|
826
520
|
i18n={runtime.i18n}
|
|
827
521
|
/>
|
|
828
|
-
{:else if uploadCollection && !creating && currentDocument !== undefined}
|
|
829
|
-
{#key currentDocument.id}
|
|
830
|
-
<UploadDocumentPreview
|
|
831
|
-
document={currentDocument}
|
|
832
|
-
editable={canEditImage}
|
|
833
|
-
updating={imageOperation}
|
|
834
|
-
onUpdate={updateUploadImage}
|
|
835
|
-
/>
|
|
836
|
-
{/key}
|
|
837
|
-
|
|
838
|
-
<form class="mt-8" dir={activeLocaleConfig?.rtl ? "rtl" : "ltr"} onsubmit={handleSave}>
|
|
839
|
-
<fieldset class="contents" disabled={form.submitting || imageOperation}>
|
|
840
|
-
<DocumentFieldSections fields={documentFields} {form} stacked={livePreviewOpen} />
|
|
841
|
-
<button class="sr-only" type="submit">{runtime.i18n.t("documents:save")}</button>
|
|
842
|
-
</fieldset>
|
|
843
|
-
</form>
|
|
844
522
|
{:else}
|
|
845
523
|
<form
|
|
846
|
-
class="
|
|
524
|
+
class="ridu-document-form"
|
|
847
525
|
dir={activeLocaleConfig?.rtl ? "rtl" : "ltr"}
|
|
526
|
+
novalidate
|
|
848
527
|
onsubmit={handleSave}
|
|
849
528
|
>
|
|
850
|
-
<fieldset class="
|
|
851
|
-
{#if
|
|
852
|
-
|
|
853
|
-
<
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
<label
|
|
860
|
-
class="grid min-h-42 cursor-pointer place-items-center rounded-[4px] border border-dashed border-control-border bg-control px-5 py-7 text-center transition-colors hover:border-primary/55 hover:bg-primary/[0.035]"
|
|
861
|
-
>
|
|
862
|
-
<input
|
|
863
|
-
class="sr-only"
|
|
864
|
-
type="file"
|
|
865
|
-
accept={collection?.uploadSettings?.mimeTypes.join(",")}
|
|
866
|
-
required
|
|
867
|
-
disabled={form.access?.operations.create !== true}
|
|
868
|
-
bind:files={controller.selectedFiles}
|
|
869
|
-
/>
|
|
870
|
-
<span>
|
|
871
|
-
<FileUpIcon class="mx-auto size-5 text-foreground-faint" />
|
|
872
|
-
<span class="mt-3 block text-[13.5px] font-medium text-foreground-muted">
|
|
873
|
-
{selectedFile?.name ?? runtime.i18n.t("uploads:chooseFileToUpload")}
|
|
874
|
-
</span>
|
|
875
|
-
<span class="mt-1 block text-[11.5px] text-foreground-faint">
|
|
876
|
-
{runtime.i18n.t("uploads:maximumSize", {
|
|
877
|
-
size: runtime.i18n.formatNumber(
|
|
878
|
-
(collection?.uploadSettings?.maxFileSize ?? 0) / 1_048_576,
|
|
879
|
-
{ maximumFractionDigits: 0, style: "unit", unit: "megabyte" }
|
|
880
|
-
),
|
|
881
|
-
})}
|
|
882
|
-
</span>
|
|
883
|
-
</span>
|
|
884
|
-
</label>
|
|
885
|
-
</div>
|
|
529
|
+
<fieldset class="ridu-document-form__fieldset" disabled={form.submitting}>
|
|
530
|
+
{#if controller.uploadCollection && controller.collection?.uploadSettings}
|
|
531
|
+
{#key JSON.stringify([globalResource, slug, routeDocumentID, activeLocale])}
|
|
532
|
+
<UploadDocumentPreview
|
|
533
|
+
draft={controller.upload}
|
|
534
|
+
settings={controller.collection.uploadSettings}
|
|
535
|
+
editable={controller.canEditUpload}
|
|
536
|
+
/>
|
|
537
|
+
{/key}
|
|
886
538
|
{/if}
|
|
887
539
|
|
|
888
|
-
{#if creatingAuthUser}
|
|
889
|
-
<
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
</legend>
|
|
895
|
-
<p class="text-[11.5px] leading-5 text-foreground-faint">
|
|
896
|
-
{runtime.i18n.t("documents:credentialsDescription")}
|
|
897
|
-
</p>
|
|
898
|
-
<div class="grid gap-4 sm:grid-cols-2">
|
|
899
|
-
<label class="grid gap-2" for="ridu-new-user-password">
|
|
900
|
-
<span class="ridu-field-label">
|
|
901
|
-
{runtime.i18n.t("documents:password")}
|
|
902
|
-
<span class="ridu-field-required" aria-hidden="true">*</span>
|
|
903
|
-
</span>
|
|
904
|
-
<input
|
|
905
|
-
id="ridu-new-user-password"
|
|
906
|
-
type="password"
|
|
907
|
-
autocomplete="new-password"
|
|
908
|
-
minlength={collection?.authSettings?.passwordMinLength}
|
|
909
|
-
required
|
|
910
|
-
aria-invalid={credentialIssue !== undefined}
|
|
911
|
-
aria-describedby="ridu-new-user-password-help"
|
|
912
|
-
bind:value={newUserPassword}
|
|
913
|
-
oninput={() => (credentialIssue = undefined)}
|
|
914
|
-
/>
|
|
915
|
-
</label>
|
|
916
|
-
<label class="grid gap-2" for="ridu-new-user-password-confirmation">
|
|
917
|
-
<span class="ridu-field-label">
|
|
918
|
-
{runtime.i18n.t("documents:confirmPassword")}
|
|
919
|
-
<span class="ridu-field-required" aria-hidden="true">*</span>
|
|
920
|
-
</span>
|
|
921
|
-
<input
|
|
922
|
-
id="ridu-new-user-password-confirmation"
|
|
923
|
-
type="password"
|
|
924
|
-
autocomplete="new-password"
|
|
925
|
-
required
|
|
926
|
-
aria-invalid={credentialIssue !== undefined}
|
|
927
|
-
aria-describedby="ridu-new-user-password-help"
|
|
928
|
-
bind:value={newUserPasswordConfirmation}
|
|
929
|
-
oninput={() => (credentialIssue = undefined)}
|
|
930
|
-
/>
|
|
931
|
-
</label>
|
|
932
|
-
</div>
|
|
933
|
-
<p
|
|
934
|
-
id="ridu-new-user-password-help"
|
|
935
|
-
class={credentialIssue === undefined ? "ridu-field-help" : "ridu-field-error"}
|
|
936
|
-
role={credentialIssue === undefined ? undefined : "alert"}
|
|
937
|
-
>
|
|
938
|
-
{credentialIssue ??
|
|
939
|
-
runtime.i18n.t("documents:passwordMinimum", {
|
|
940
|
-
minimum: runtime.i18n.formatNumber(
|
|
941
|
-
collection?.authSettings?.passwordMinLength ?? 8
|
|
942
|
-
),
|
|
943
|
-
})}
|
|
944
|
-
</p>
|
|
945
|
-
</fieldset>
|
|
540
|
+
{#if controller.creatingAuthUser}
|
|
541
|
+
<DocumentCredentials
|
|
542
|
+
bind:credentials
|
|
543
|
+
bind:issue={credentialIssue}
|
|
544
|
+
minimumLength={controller.collection?.authSettings?.passwordMinLength}
|
|
545
|
+
/>
|
|
946
546
|
{/if}
|
|
947
547
|
|
|
948
|
-
<DocumentFieldSections
|
|
949
|
-
|
|
548
|
+
<DocumentFieldSections
|
|
549
|
+
fields={controller.documentFields}
|
|
550
|
+
{form}
|
|
551
|
+
stacked={livePreviewOpen}
|
|
552
|
+
/>
|
|
553
|
+
<button
|
|
554
|
+
class="ridu-document-form__submit"
|
|
555
|
+
type="submit"
|
|
556
|
+
aria-busy={form.submitting}
|
|
557
|
+
disabled={!controller.canSave ||
|
|
558
|
+
(!controller.creating &&
|
|
559
|
+
controller.versionedCollection &&
|
|
560
|
+
controller.currentStatus === "published" &&
|
|
561
|
+
!controller.canPublish)}
|
|
562
|
+
>
|
|
563
|
+
{runtime.i18n.t("documents:save")}
|
|
564
|
+
</button>
|
|
950
565
|
</fieldset>
|
|
951
566
|
</form>
|
|
952
567
|
{/if}
|
|
953
568
|
</div>
|
|
954
569
|
</div>
|
|
955
570
|
|
|
956
|
-
{#if livePreviewOpen && livePreview !== undefined && currentDocument !== undefined}
|
|
571
|
+
{#if documentView !== "api" && livePreviewOpen && livePreview !== undefined && controller.currentDocument !== undefined}
|
|
957
572
|
{#key livePreviewRouteKey}
|
|
958
573
|
<LivePreviewPanel
|
|
959
574
|
preview={livePreview}
|
|
960
|
-
collection={collectionSlug}
|
|
961
|
-
documentID={documentID ?? collectionSlug}
|
|
575
|
+
collection={controller.collectionSlug}
|
|
576
|
+
documentID={controller.documentID ?? controller.collectionSlug}
|
|
962
577
|
resource={globalResource ? "global" : "collection"}
|
|
963
578
|
values={form.values}
|
|
964
|
-
onclose={() => (livePreviewOpen = false)}
|
|
965
579
|
/>
|
|
966
580
|
{/key}
|
|
967
581
|
{/if}
|
|
@@ -974,15 +588,12 @@
|
|
|
974
588
|
if (!open) cancelNavigation();
|
|
975
589
|
}}
|
|
976
590
|
>
|
|
977
|
-
<DialogContent>
|
|
591
|
+
<DialogContent variant="confirmation">
|
|
978
592
|
<DialogHeader>
|
|
979
|
-
<
|
|
980
|
-
{runtime.i18n.t("documents:unsavedChanges")}
|
|
981
|
-
</p>
|
|
982
|
-
<DialogTitle class="text-[28px] font-semibold leading-tight tracking-[-0.02em]">
|
|
593
|
+
<DialogTitle>
|
|
983
594
|
{runtime.i18n.t("documents:leaveWithoutSavingQuestion")}
|
|
984
595
|
</DialogTitle>
|
|
985
|
-
<DialogDescription
|
|
596
|
+
<DialogDescription>
|
|
986
597
|
{runtime.i18n.t("documents:leaveWithoutSavingDescription")}
|
|
987
598
|
</DialogDescription>
|
|
988
599
|
</DialogHeader>
|
|
@@ -990,128 +601,11 @@
|
|
|
990
601
|
<Button variant="outline" onclick={cancelNavigation}>
|
|
991
602
|
{runtime.i18n.t("documents:keepEditing")}
|
|
992
603
|
</Button>
|
|
993
|
-
<Button
|
|
604
|
+
<Button disabled={form.submitting} onclick={discardAndNavigate}>
|
|
994
605
|
{runtime.i18n.t("documents:leaveWithoutSaving")}
|
|
995
606
|
</Button>
|
|
996
607
|
</DialogFooter>
|
|
997
608
|
</DialogContent>
|
|
998
609
|
</Dialog>
|
|
999
610
|
|
|
1000
|
-
{
|
|
1001
|
-
{const deleteConfirmation = $derived(
|
|
1002
|
-
collection?.capabilities.trash === true
|
|
1003
|
-
? {
|
|
1004
|
-
title: runtime.i18n.t("documents:moveToTrashQuestion"),
|
|
1005
|
-
description: runtime.i18n.t("documents:moveToTrashDescription", {
|
|
1006
|
-
label: documentHeading,
|
|
1007
|
-
}),
|
|
1008
|
-
action: runtime.i18n.t("documents:moveToTrash"),
|
|
1009
|
-
}
|
|
1010
|
-
: {
|
|
1011
|
-
title: runtime.i18n.t("documents:deleteQuestion"),
|
|
1012
|
-
description: runtime.i18n.t("documents:deleteDescription", {
|
|
1013
|
-
label: documentHeading,
|
|
1014
|
-
}),
|
|
1015
|
-
action: runtime.i18n.t("documents:deleteDocument"),
|
|
1016
|
-
}
|
|
1017
|
-
)}
|
|
1018
|
-
|
|
1019
|
-
<Dialog bind:open={controller.deleteDialogOpen}>
|
|
1020
|
-
<DialogContent>
|
|
1021
|
-
<DialogHeader>
|
|
1022
|
-
<p class="font-mono text-[10px] tracking-[0.13em] text-destructive uppercase">
|
|
1023
|
-
{runtime.i18n.t("documents:destructiveAction")}
|
|
1024
|
-
</p>
|
|
1025
|
-
<DialogTitle class="text-[28px] font-semibold leading-tight tracking-[-0.02em]">
|
|
1026
|
-
{deleteConfirmation.title}
|
|
1027
|
-
</DialogTitle>
|
|
1028
|
-
<DialogDescription class="mt-1 text-[13.5px] leading-5 text-foreground-muted">
|
|
1029
|
-
{deleteConfirmation.description}
|
|
1030
|
-
</DialogDescription>
|
|
1031
|
-
</DialogHeader>
|
|
1032
|
-
<DialogFooter>
|
|
1033
|
-
<Button variant="outline" onclick={() => (controller.deleteDialogOpen = false)}>
|
|
1034
|
-
{runtime.i18n.t("documents:cancel")}
|
|
1035
|
-
</Button>
|
|
1036
|
-
<Button variant="destructive" onclick={remove}>
|
|
1037
|
-
{deleteConfirmation.action}
|
|
1038
|
-
</Button>
|
|
1039
|
-
</DialogFooter>
|
|
1040
|
-
</DialogContent>
|
|
1041
|
-
</Dialog>
|
|
1042
|
-
{/if}
|
|
1043
|
-
|
|
1044
|
-
<Dialog
|
|
1045
|
-
open={lockKey !== undefined && dismissedLock !== lockKey}
|
|
1046
|
-
onOpenChange={(open) => {
|
|
1047
|
-
if (!open) dismissedLock = lockKey;
|
|
1048
|
-
}}
|
|
1049
|
-
>
|
|
1050
|
-
<DialogContent>
|
|
1051
|
-
<DialogHeader>
|
|
1052
|
-
<p class="font-mono text-[10px] tracking-[0.13em] text-warning uppercase">
|
|
1053
|
-
{runtime.i18n.t("documents:documentLocked")}
|
|
1054
|
-
</p>
|
|
1055
|
-
<DialogTitle class="text-[28px] font-semibold leading-tight tracking-[-0.02em]">
|
|
1056
|
-
{runtime.i18n.t("documents:currentlyEditing", {
|
|
1057
|
-
owner: lockOwnerLabel ?? runtime.i18n.t("documents:anotherEditor"),
|
|
1058
|
-
})}
|
|
1059
|
-
</DialogTitle>
|
|
1060
|
-
<DialogDescription class="mt-1 text-[13.5px] leading-5 text-foreground-muted">
|
|
1061
|
-
{lockUpdatedAt === undefined
|
|
1062
|
-
? runtime.i18n.t("documents:editingLeaseActive")
|
|
1063
|
-
: runtime.i18n.t("documents:editedSince", { date: documentDate(lockUpdatedAt) })}
|
|
1064
|
-
{canTakeOverLock
|
|
1065
|
-
? runtime.i18n.t("documents:lockOptionsWithTakeover")
|
|
1066
|
-
: runtime.i18n.t("documents:lockOptionsReadOnly")}
|
|
1067
|
-
</DialogDescription>
|
|
1068
|
-
</DialogHeader>
|
|
1069
|
-
<DialogFooter>
|
|
1070
|
-
<Link class={buttonVariants({ variant: "ghost" })} to={collectionPath(collectionSlug)}>
|
|
1071
|
-
{runtime.i18n.t("documents:goBack")}
|
|
1072
|
-
</Link>
|
|
1073
|
-
<Button variant="outline" onclick={() => (dismissedLock = lockKey)}>
|
|
1074
|
-
{runtime.i18n.t("documents:viewReadOnly")}
|
|
1075
|
-
</Button>
|
|
1076
|
-
{#if canTakeOverLock}
|
|
1077
|
-
<Button disabled={lockOperation} onclick={handleTakeover}>
|
|
1078
|
-
{lockOperation
|
|
1079
|
-
? runtime.i18n.t("documents:takingOver")
|
|
1080
|
-
: runtime.i18n.t("documents:takeOver")}
|
|
1081
|
-
</Button>
|
|
1082
|
-
{/if}
|
|
1083
|
-
</DialogFooter>
|
|
1084
|
-
</DialogContent>
|
|
1085
|
-
</Dialog>
|
|
1086
|
-
|
|
1087
|
-
<Dialog bind:open={controller.restoreDialogOpen}>
|
|
1088
|
-
<DialogContent>
|
|
1089
|
-
<DialogHeader>
|
|
1090
|
-
<p class="font-mono text-[10px] tracking-[0.13em] text-warning uppercase">
|
|
1091
|
-
{runtime.i18n.t("versions:restore")}
|
|
1092
|
-
</p>
|
|
1093
|
-
<DialogTitle class="text-[28px] font-semibold leading-tight tracking-[-0.02em]">
|
|
1094
|
-
{runtime.i18n.t("versions:restoreRevisionQuestion", {
|
|
1095
|
-
revision:
|
|
1096
|
-
pendingRestore === undefined ? "—" : runtime.i18n.formatNumber(pendingRestore.Revision),
|
|
1097
|
-
})}
|
|
1098
|
-
</DialogTitle>
|
|
1099
|
-
<DialogDescription class="mt-1 text-[13.5px] leading-5 text-foreground-muted">
|
|
1100
|
-
{runtime.i18n.t("versions:restoreDescription")}
|
|
1101
|
-
</DialogDescription>
|
|
1102
|
-
</DialogHeader>
|
|
1103
|
-
<DialogFooter>
|
|
1104
|
-
<Button variant="outline" onclick={() => (controller.restoreDialogOpen = false)}>
|
|
1105
|
-
{runtime.i18n.t("versions:cancel")}
|
|
1106
|
-
</Button>
|
|
1107
|
-
<Button
|
|
1108
|
-
disabled={pendingRestore === undefined || versionOperation || !controller.canRestoreVersion}
|
|
1109
|
-
onclick={() => pendingRestore !== undefined && restore(pendingRestore)}
|
|
1110
|
-
>
|
|
1111
|
-
{versionOperation
|
|
1112
|
-
? runtime.i18n.t("versions:restoring")
|
|
1113
|
-
: runtime.i18n.t("versions:restore")}
|
|
1114
|
-
</Button>
|
|
1115
|
-
</DialogFooter>
|
|
1116
|
-
</DialogContent>
|
|
1117
|
-
</Dialog>
|
|
611
|
+
<DocumentOperationDialogs {controller} />
|