@aphexcms/cms-core 2.0.9 → 2.0.11
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/LICENSE +21 -0
- package/dist/api/api-keys.d.ts +8 -7
- package/dist/api/api-keys.d.ts.map +1 -1
- package/dist/api/assets.d.ts +7 -22
- package/dist/api/assets.d.ts.map +1 -1
- package/dist/api/assets.js +1 -10
- package/dist/api/documents.d.ts +17 -16
- package/dist/api/documents.d.ts.map +1 -1
- package/dist/api/documents.js +2 -2
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +1 -0
- package/dist/api/organizations.d.ts +15 -35
- package/dist/api/organizations.d.ts.map +1 -1
- package/dist/api/roles.d.ts +22 -0
- package/dist/api/roles.d.ts.map +1 -0
- package/dist/api/roles.js +26 -0
- package/dist/api/schemas/api-keys.d.ts +62 -0
- package/dist/api/schemas/api-keys.d.ts.map +1 -0
- package/dist/api/schemas/api-keys.js +28 -0
- package/dist/api/schemas/assets.d.ts +174 -0
- package/dist/api/schemas/assets.d.ts.map +1 -0
- package/dist/api/schemas/assets.js +99 -0
- package/dist/api/schemas/documents.d.ts +325 -0
- package/dist/api/schemas/documents.d.ts.map +1 -0
- package/dist/api/schemas/documents.js +161 -0
- package/dist/api/schemas/organizations.d.ts +41 -0
- package/dist/api/schemas/organizations.d.ts.map +1 -0
- package/dist/api/schemas/organizations.js +56 -0
- package/dist/api/schemas/roles.d.ts +77 -0
- package/dist/api/schemas/roles.d.ts.map +1 -0
- package/dist/api/schemas/roles.js +42 -0
- package/dist/api/schemas/user.d.ts +10 -0
- package/dist/api/schemas/user.d.ts.map +1 -0
- package/dist/api/schemas/user.js +11 -0
- package/dist/api/user.d.ts +4 -6
- package/dist/api/user.d.ts.map +1 -1
- package/dist/app-augment.d.ts +32 -0
- package/dist/app-augment.d.ts.map +1 -0
- package/dist/app-augment.js +18 -0
- package/dist/auth/auth-hooks.d.ts +2 -1
- package/dist/auth/auth-hooks.d.ts.map +1 -1
- package/dist/auth/auth-hooks.js +22 -2
- package/dist/auth/provider.d.ts +5 -0
- package/dist/auth/provider.d.ts.map +1 -1
- package/dist/cli/index.js +0 -0
- package/dist/client/index.d.ts +4 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +7 -0
- package/dist/components/AdminApp.svelte +306 -142
- package/dist/components/AdminApp.svelte.d.ts +8 -0
- package/dist/components/AdminApp.svelte.d.ts.map +1 -1
- package/dist/components/admin/DocumentEditor.svelte +653 -365
- package/dist/components/admin/DocumentEditor.svelte.d.ts +2 -0
- package/dist/components/admin/DocumentEditor.svelte.d.ts.map +1 -1
- package/dist/components/admin/DocumentVersionPanel.svelte +57 -44
- package/dist/components/admin/DocumentVersionPanel.svelte.d.ts.map +1 -1
- package/dist/components/admin/MediaBrowser.svelte +70 -30
- package/dist/components/admin/MediaBrowser.svelte.d.ts.map +1 -1
- package/dist/components/admin/ObjectModal.svelte +70 -95
- package/dist/components/admin/ObjectModal.svelte.d.ts +1 -2
- package/dist/components/admin/ObjectModal.svelte.d.ts.map +1 -1
- package/dist/components/admin/PermissionsDebug.svelte +119 -0
- package/dist/components/admin/PermissionsDebug.svelte.d.ts +4 -0
- package/dist/components/admin/PermissionsDebug.svelte.d.ts.map +1 -0
- package/dist/components/admin/SchemaField.svelte +3 -1
- package/dist/components/admin/SchemaField.svelte.d.ts.map +1 -1
- package/dist/components/admin/confirm-dialog/ConfirmDialogHost.svelte +35 -0
- package/dist/components/admin/confirm-dialog/ConfirmDialogHost.svelte.d.ts +19 -0
- package/dist/components/admin/confirm-dialog/ConfirmDialogHost.svelte.d.ts.map +1 -0
- package/dist/components/admin/confirm-dialog/confirm-dialog.svelte.d.ts +16 -0
- package/dist/components/admin/confirm-dialog/confirm-dialog.svelte.d.ts.map +1 -0
- package/dist/components/admin/confirm-dialog/confirm-dialog.svelte.js +29 -0
- package/dist/components/admin/fields/ArrayField.svelte +104 -108
- package/dist/components/admin/fields/ArrayField.svelte.d.ts.map +1 -1
- package/dist/components/admin/fields/FileField.svelte +22 -17
- package/dist/components/admin/fields/FileField.svelte.d.ts.map +1 -1
- package/dist/components/admin/fields/ImageField.svelte +21 -15
- package/dist/components/admin/fields/ImageField.svelte.d.ts.map +1 -1
- package/dist/components/admin/fields/ReferenceField.svelte +142 -112
- package/dist/components/admin/fields/ReferenceField.svelte.d.ts.map +1 -1
- package/dist/components/admin/fields/TextareaField.svelte +4 -3
- package/dist/components/admin/fields/TextareaField.svelte.d.ts +2 -2
- package/dist/components/admin/fields/TextareaField.svelte.d.ts.map +1 -1
- package/dist/components/layout/Sidebar.svelte +21 -11
- package/dist/components/layout/Sidebar.svelte.d.ts.map +1 -1
- package/dist/components/layout/sidebar/NavMain.svelte +1 -0
- package/dist/db/interfaces/document.d.ts.map +1 -1
- package/dist/db/interfaces/index.d.ts +3 -1
- package/dist/db/interfaces/index.d.ts.map +1 -1
- package/dist/db/interfaces/organization.d.ts +1 -1
- package/dist/db/interfaces/organization.d.ts.map +1 -1
- package/dist/db/interfaces/role.d.ts +25 -0
- package/dist/db/interfaces/role.d.ts.map +1 -0
- package/dist/db/interfaces/role.js +1 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +5 -0
- package/dist/field-access.d.ts +30 -0
- package/dist/field-access.d.ts.map +1 -0
- package/dist/field-access.js +79 -0
- package/dist/hooks.d.ts +2 -0
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +23 -13
- package/dist/lib/api/api-keys.d.ts +8 -7
- package/dist/lib/api/api-keys.d.ts.map +1 -1
- package/dist/lib/api/api-keys.js.map +1 -1
- package/dist/lib/api/assets.d.ts +7 -22
- package/dist/lib/api/assets.d.ts.map +1 -1
- package/dist/lib/api/assets.js +1 -10
- package/dist/lib/api/assets.js.map +1 -1
- package/dist/lib/api/documents.d.ts +17 -16
- package/dist/lib/api/documents.d.ts.map +1 -1
- package/dist/lib/api/documents.js +2 -2
- package/dist/lib/api/documents.js.map +1 -1
- package/dist/lib/api/index.d.ts +1 -0
- package/dist/lib/api/index.d.ts.map +1 -1
- package/dist/lib/api/index.js +1 -0
- package/dist/lib/api/index.js.map +1 -1
- package/dist/lib/api/organizations.d.ts +15 -35
- package/dist/lib/api/organizations.d.ts.map +1 -1
- package/dist/lib/api/organizations.js.map +1 -1
- package/dist/lib/api/roles.d.ts +22 -0
- package/dist/lib/api/roles.d.ts.map +1 -0
- package/dist/lib/api/roles.js +27 -0
- package/dist/lib/api/roles.js.map +1 -0
- package/dist/lib/api/schemas/api-keys.d.ts +62 -0
- package/dist/lib/api/schemas/api-keys.d.ts.map +1 -0
- package/dist/lib/api/schemas/api-keys.js +29 -0
- package/dist/lib/api/schemas/api-keys.js.map +1 -0
- package/dist/lib/api/schemas/assets.d.ts +174 -0
- package/dist/lib/api/schemas/assets.d.ts.map +1 -0
- package/dist/lib/api/schemas/assets.js +100 -0
- package/dist/lib/api/schemas/assets.js.map +1 -0
- package/dist/lib/api/schemas/documents.d.ts +325 -0
- package/dist/lib/api/schemas/documents.d.ts.map +1 -0
- package/dist/lib/api/schemas/documents.js +162 -0
- package/dist/lib/api/schemas/documents.js.map +1 -0
- package/dist/lib/api/schemas/organizations.d.ts +41 -0
- package/dist/lib/api/schemas/organizations.d.ts.map +1 -0
- package/dist/lib/api/schemas/organizations.js +57 -0
- package/dist/lib/api/schemas/organizations.js.map +1 -0
- package/dist/lib/api/schemas/roles.d.ts +77 -0
- package/dist/lib/api/schemas/roles.d.ts.map +1 -0
- package/dist/lib/api/schemas/roles.js +43 -0
- package/dist/lib/api/schemas/roles.js.map +1 -0
- package/dist/lib/api/schemas/user.d.ts +10 -0
- package/dist/lib/api/schemas/user.d.ts.map +1 -0
- package/dist/lib/api/schemas/user.js +12 -0
- package/dist/lib/api/schemas/user.js.map +1 -0
- package/dist/lib/api/user.d.ts +4 -6
- package/dist/lib/api/user.d.ts.map +1 -1
- package/dist/lib/api/user.js.map +1 -1
- package/dist/lib/app-augment.d.ts +32 -0
- package/dist/lib/app-augment.d.ts.map +1 -0
- package/dist/lib/app-augment.js +19 -0
- package/dist/lib/app-augment.js.map +1 -0
- package/dist/lib/auth/auth-hooks.d.ts +2 -1
- package/dist/lib/auth/auth-hooks.d.ts.map +1 -1
- package/dist/lib/auth/auth-hooks.js +22 -2
- package/dist/lib/auth/auth-hooks.js.map +1 -1
- package/dist/lib/auth/provider.d.ts +5 -0
- package/dist/lib/auth/provider.d.ts.map +1 -1
- package/dist/lib/client/index.d.ts +4 -0
- package/dist/lib/client/index.d.ts.map +1 -1
- package/dist/lib/client/index.js +7 -0
- package/dist/lib/client/index.js.map +1 -1
- package/dist/lib/components/admin/confirm-dialog/confirm-dialog.svelte.d.ts +16 -0
- package/dist/lib/components/admin/confirm-dialog/confirm-dialog.svelte.d.ts.map +1 -0
- package/dist/lib/components/admin/confirm-dialog/confirm-dialog.svelte.js +30 -0
- package/dist/lib/components/admin/confirm-dialog/confirm-dialog.svelte.js.map +1 -0
- package/dist/lib/db/interfaces/document.d.ts.map +1 -1
- package/dist/lib/db/interfaces/index.d.ts +3 -1
- package/dist/lib/db/interfaces/index.d.ts.map +1 -1
- package/dist/lib/db/interfaces/organization.d.ts +1 -1
- package/dist/lib/db/interfaces/organization.d.ts.map +1 -1
- package/dist/lib/db/interfaces/role.d.ts +25 -0
- package/dist/lib/db/interfaces/role.d.ts.map +1 -0
- package/dist/lib/db/interfaces/role.js +2 -0
- package/dist/lib/db/interfaces/role.js.map +1 -0
- package/dist/lib/engine.d.ts.map +1 -1
- package/dist/lib/engine.js +5 -0
- package/dist/lib/engine.js.map +1 -1
- package/dist/lib/field-access.d.ts +30 -0
- package/dist/lib/field-access.d.ts.map +1 -0
- package/dist/lib/field-access.js +80 -0
- package/dist/lib/field-access.js.map +1 -0
- package/dist/lib/hooks.d.ts +2 -0
- package/dist/lib/hooks.d.ts.map +1 -1
- package/dist/lib/hooks.js +23 -13
- package/dist/lib/hooks.js.map +1 -1
- package/dist/lib/local-api/collection-api.d.ts +2 -0
- package/dist/lib/local-api/collection-api.d.ts.map +1 -1
- package/dist/lib/local-api/collection-api.js +98 -30
- package/dist/lib/local-api/collection-api.js.map +1 -1
- package/dist/lib/local-api/index.d.ts.map +1 -1
- package/dist/lib/local-api/index.js.map +1 -1
- package/dist/lib/local-api/permissions.d.ts +26 -26
- package/dist/lib/local-api/permissions.d.ts.map +1 -1
- package/dist/lib/local-api/permissions.js +122 -80
- package/dist/lib/local-api/permissions.js.map +1 -1
- package/dist/lib/permissions-context.svelte.d.ts +44 -0
- package/dist/lib/permissions-context.svelte.d.ts.map +1 -0
- package/dist/lib/permissions-context.svelte.js +80 -0
- package/dist/lib/permissions-context.svelte.js.map +1 -0
- package/dist/lib/routes/assets-bulk.d.ts.map +1 -1
- package/dist/lib/routes/assets-bulk.js +10 -3
- package/dist/lib/routes/assets-bulk.js.map +1 -1
- package/dist/lib/routes/assets-by-id.d.ts.map +1 -1
- package/dist/lib/routes/assets-by-id.js +11 -1
- package/dist/lib/routes/assets-by-id.js.map +1 -1
- package/dist/lib/routes/assets-references-counts.d.ts.map +1 -1
- package/dist/lib/routes/assets-references-counts.js +12 -2
- package/dist/lib/routes/assets-references-counts.js.map +1 -1
- package/dist/lib/routes/assets.d.ts.map +1 -1
- package/dist/lib/routes/assets.js +14 -13
- package/dist/lib/routes/assets.js.map +1 -1
- package/dist/lib/routes/document-versions.d.ts.map +1 -1
- package/dist/lib/routes/document-versions.js +16 -2
- package/dist/lib/routes/document-versions.js.map +1 -1
- package/dist/lib/routes/documents-by-id.d.ts.map +1 -1
- package/dist/lib/routes/documents-by-id.js +15 -3
- package/dist/lib/routes/documents-by-id.js.map +1 -1
- package/dist/lib/routes/documents-query.d.ts.map +1 -1
- package/dist/lib/routes/documents-query.js +13 -10
- package/dist/lib/routes/documents-query.js.map +1 -1
- package/dist/lib/routes/documents.d.ts.map +1 -1
- package/dist/lib/routes/documents.js +29 -26
- package/dist/lib/routes/documents.js.map +1 -1
- package/dist/lib/routes/organizations-by-id.d.ts.map +1 -1
- package/dist/lib/routes/organizations-by-id.js +17 -4
- package/dist/lib/routes/organizations-by-id.js.map +1 -1
- package/dist/lib/routes/organizations-invitations.d.ts.map +1 -1
- package/dist/lib/routes/organizations-invitations.js +48 -18
- package/dist/lib/routes/organizations-invitations.js.map +1 -1
- package/dist/lib/routes/organizations-members.d.ts.map +1 -1
- package/dist/lib/routes/organizations-members.js +24 -18
- package/dist/lib/routes/organizations-members.js.map +1 -1
- package/dist/lib/routes/organizations-switch.d.ts.map +1 -1
- package/dist/lib/routes/organizations-switch.js +7 -3
- package/dist/lib/routes/organizations-switch.js.map +1 -1
- package/dist/lib/routes/organizations.d.ts.map +1 -1
- package/dist/lib/routes/organizations.js +11 -5
- package/dist/lib/routes/organizations.js.map +1 -1
- package/dist/lib/routes/roles-by-name.d.ts +4 -0
- package/dist/lib/routes/roles-by-name.d.ts.map +1 -0
- package/dist/lib/routes/roles-by-name.js +112 -0
- package/dist/lib/routes/roles-by-name.js.map +1 -0
- package/dist/lib/routes/roles.d.ts +4 -0
- package/dist/lib/routes/roles.d.ts.map +1 -0
- package/dist/lib/routes/roles.js +91 -0
- package/dist/lib/routes/roles.js.map +1 -0
- package/dist/lib/routes/user-preferences.d.ts.map +1 -1
- package/dist/lib/routes/user-preferences.js +7 -13
- package/dist/lib/routes/user-preferences.js.map +1 -1
- package/dist/lib/routes-exports.d.ts +2 -0
- package/dist/lib/routes-exports.d.ts.map +1 -1
- package/dist/lib/routes-exports.js +3 -0
- package/dist/lib/routes-exports.js.map +1 -1
- package/dist/lib/save-state-context.svelte.d.ts +8 -0
- package/dist/lib/save-state-context.svelte.d.ts.map +1 -0
- package/dist/lib/save-state-context.svelte.js +9 -0
- package/dist/lib/save-state-context.svelte.js.map +1 -0
- package/dist/lib/schema-utils/validator.d.ts.map +1 -1
- package/dist/lib/schema-utils/validator.js +14 -0
- package/dist/lib/schema-utils/validator.js.map +1 -1
- package/dist/lib/server/index.d.ts +1 -0
- package/dist/lib/server/index.d.ts.map +1 -1
- package/dist/lib/server/index.js +1 -0
- package/dist/lib/server/index.js.map +1 -1
- package/dist/lib/services/index.d.ts +1 -0
- package/dist/lib/services/index.d.ts.map +1 -1
- package/dist/lib/services/index.js +1 -0
- package/dist/lib/services/index.js.map +1 -1
- package/dist/lib/services/roles-service.d.ts +35 -0
- package/dist/lib/services/roles-service.d.ts.map +1 -0
- package/dist/lib/services/roles-service.js +91 -0
- package/dist/lib/services/roles-service.js.map +1 -0
- package/dist/lib/services/version-service.d.ts.map +1 -1
- package/dist/lib/services/version-service.js.map +1 -1
- package/dist/lib/types/auth.d.ts +43 -9
- package/dist/lib/types/auth.d.ts.map +1 -1
- package/dist/lib/types/auth.js +31 -26
- package/dist/lib/types/auth.js.map +1 -1
- package/dist/lib/types/capabilities.d.ts +104 -0
- package/dist/lib/types/capabilities.d.ts.map +1 -0
- package/dist/lib/types/capabilities.js +217 -0
- package/dist/lib/types/capabilities.js.map +1 -0
- package/dist/lib/types/index.d.ts +1 -0
- package/dist/lib/types/index.d.ts.map +1 -1
- package/dist/lib/types/index.js +1 -0
- package/dist/lib/types/index.js.map +1 -1
- package/dist/lib/types/organization.d.ts +13 -4
- package/dist/lib/types/organization.d.ts.map +1 -1
- package/dist/lib/types/schemas.d.ts +71 -0
- package/dist/lib/types/schemas.d.ts.map +1 -1
- package/dist/lib/types/sidebar.d.ts +1 -1
- package/dist/lib/types/sidebar.d.ts.map +1 -1
- package/dist/lib/utils/mime-detect.d.ts.map +1 -1
- package/dist/lib/utils/mime-detect.js +28 -6
- package/dist/lib/utils/mime-detect.js.map +1 -1
- package/dist/local-api/collection-api.d.ts +2 -0
- package/dist/local-api/collection-api.d.ts.map +1 -1
- package/dist/local-api/collection-api.js +98 -30
- package/dist/local-api/index.d.ts.map +1 -1
- package/dist/local-api/permissions.d.ts +26 -26
- package/dist/local-api/permissions.d.ts.map +1 -1
- package/dist/local-api/permissions.js +122 -80
- package/dist/permissions-context.svelte.d.ts +44 -0
- package/dist/permissions-context.svelte.d.ts.map +1 -0
- package/dist/permissions-context.svelte.js +79 -0
- package/dist/plugins/README.md +20 -20
- package/dist/routes/assets-bulk.d.ts.map +1 -1
- package/dist/routes/assets-bulk.js +10 -3
- package/dist/routes/assets-by-id.d.ts.map +1 -1
- package/dist/routes/assets-by-id.js +11 -1
- package/dist/routes/assets-references-counts.d.ts.map +1 -1
- package/dist/routes/assets-references-counts.js +12 -2
- package/dist/routes/assets.d.ts.map +1 -1
- package/dist/routes/assets.js +14 -13
- package/dist/routes/document-versions.d.ts.map +1 -1
- package/dist/routes/document-versions.js +16 -2
- package/dist/routes/documents-by-id.d.ts.map +1 -1
- package/dist/routes/documents-by-id.js +15 -3
- package/dist/routes/documents-query.d.ts.map +1 -1
- package/dist/routes/documents-query.js +13 -10
- package/dist/routes/documents.d.ts.map +1 -1
- package/dist/routes/documents.js +29 -26
- package/dist/routes/organizations-by-id.d.ts.map +1 -1
- package/dist/routes/organizations-by-id.js +17 -4
- package/dist/routes/organizations-invitations.d.ts.map +1 -1
- package/dist/routes/organizations-invitations.js +48 -18
- package/dist/routes/organizations-members.d.ts.map +1 -1
- package/dist/routes/organizations-members.js +24 -18
- package/dist/routes/organizations-switch.d.ts.map +1 -1
- package/dist/routes/organizations-switch.js +7 -3
- package/dist/routes/organizations.d.ts.map +1 -1
- package/dist/routes/organizations.js +11 -5
- package/dist/routes/roles-by-name.d.ts +4 -0
- package/dist/routes/roles-by-name.d.ts.map +1 -0
- package/dist/routes/roles-by-name.js +111 -0
- package/dist/routes/roles.d.ts +4 -0
- package/dist/routes/roles.d.ts.map +1 -0
- package/dist/routes/roles.js +90 -0
- package/dist/routes/user-preferences.d.ts.map +1 -1
- package/dist/routes/user-preferences.js +7 -13
- package/dist/routes-exports.d.ts +2 -0
- package/dist/routes-exports.d.ts.map +1 -1
- package/dist/routes-exports.js +3 -0
- package/dist/save-state-context.svelte.d.ts +8 -0
- package/dist/save-state-context.svelte.d.ts.map +1 -0
- package/dist/save-state-context.svelte.js +8 -0
- package/dist/schema-utils/validator.d.ts.map +1 -1
- package/dist/schema-utils/validator.js +14 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -0
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +1 -0
- package/dist/services/roles-service.d.ts +35 -0
- package/dist/services/roles-service.d.ts.map +1 -0
- package/dist/services/roles-service.js +90 -0
- package/dist/services/version-service.d.ts.map +1 -1
- package/dist/types/auth.d.ts +43 -9
- package/dist/types/auth.d.ts.map +1 -1
- package/dist/types/auth.js +31 -26
- package/dist/types/capabilities.d.ts +104 -0
- package/dist/types/capabilities.d.ts.map +1 -0
- package/dist/types/capabilities.js +216 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/dist/types/organization.d.ts +13 -4
- package/dist/types/organization.d.ts.map +1 -1
- package/dist/types/schemas.d.ts +71 -0
- package/dist/types/schemas.d.ts.map +1 -1
- package/dist/types/sidebar.d.ts +1 -1
- package/dist/types/sidebar.d.ts.map +1 -1
- package/dist/utils/mime-detect.d.ts.map +1 -1
- package/dist/utils/mime-detect.js +28 -6
- package/package.json +142 -138
|
@@ -10,11 +10,13 @@
|
|
|
10
10
|
import { Rule } from '../../field-validation/rule';
|
|
11
11
|
import { hasUnpublishedChanges } from '../../utils/content-hash';
|
|
12
12
|
import { setSchemaContext } from '../../schema-context.svelte';
|
|
13
|
+
import { setSaveStateContext } from '../../save-state-context.svelte';
|
|
14
|
+
import { usePermissions } from '../../permissions-context.svelte';
|
|
13
15
|
import { getDefaultValueForFieldType } from '../../utils/field-defaults';
|
|
14
|
-
import elementEvents from '../../utils/element-events';
|
|
15
16
|
import { cmsLogger } from '../../utils/logger';
|
|
16
17
|
import { toast } from 'svelte-sonner';
|
|
17
|
-
import {
|
|
18
|
+
import { confirmDialog } from './confirm-dialog/confirm-dialog.svelte';
|
|
19
|
+
import { History, Trash2, Ellipsis, Code } from '@lucide/svelte';
|
|
18
20
|
|
|
19
21
|
interface Props {
|
|
20
22
|
schemas: SchemaType[];
|
|
@@ -26,9 +28,16 @@
|
|
|
26
28
|
onAutoSaved?: (documentId: string, title: string) => void;
|
|
27
29
|
onDeleted?: () => void;
|
|
28
30
|
onPublished?: (documentId: string) => void;
|
|
31
|
+
onUnpublished?: (documentId: string) => void;
|
|
32
|
+
onRestored?: (documentId: string) => void;
|
|
29
33
|
onOpenReference?: (documentId: string, documentType: string) => void;
|
|
30
34
|
onOpenVersionHistory?: (documentId: string) => void;
|
|
31
|
-
externalVersionPreview?: {
|
|
35
|
+
externalVersionPreview?: {
|
|
36
|
+
versionNumber: number;
|
|
37
|
+
data: Record<string, any>;
|
|
38
|
+
eventType: string;
|
|
39
|
+
createdAt?: string;
|
|
40
|
+
} | null;
|
|
32
41
|
isReadOnly?: boolean;
|
|
33
42
|
}
|
|
34
43
|
|
|
@@ -42,6 +51,8 @@
|
|
|
42
51
|
onAutoSaved,
|
|
43
52
|
onDeleted,
|
|
44
53
|
onPublished,
|
|
54
|
+
onUnpublished,
|
|
55
|
+
onRestored,
|
|
45
56
|
onOpenReference,
|
|
46
57
|
onOpenVersionHistory,
|
|
47
58
|
externalVersionPreview = null,
|
|
@@ -51,6 +62,27 @@
|
|
|
51
62
|
// Set schema context for child components (ArrayField, etc.)
|
|
52
63
|
setSchemaContext(schemas);
|
|
53
64
|
|
|
65
|
+
// Read permissions from the AdminApp context for per-action gating.
|
|
66
|
+
// `isReadOnly` prop, when explicitly true, forces everything off — this is
|
|
67
|
+
// the escape hatch for hosts that embed the editor outside the RBAC shell
|
|
68
|
+
// (version preview, public previews, etc).
|
|
69
|
+
const perms = usePermissions();
|
|
70
|
+
const canCreate = $derived(!isReadOnly && perms.can('document.create'));
|
|
71
|
+
const canUpdate = $derived(!isReadOnly && perms.can('document.update'));
|
|
72
|
+
const canDelete = $derived(!isReadOnly && perms.can('document.delete'));
|
|
73
|
+
const canPublishDoc = $derived(!isReadOnly && perms.can('document.publish'));
|
|
74
|
+
const canUnpublishDoc = $derived(!isReadOnly && perms.can('document.unpublish'));
|
|
75
|
+
// Whichever capability matters for the *current* document state:
|
|
76
|
+
// - create mode (no id yet) → document.create
|
|
77
|
+
// - edit mode → document.update
|
|
78
|
+
// Used for field editability + auto-save gating.
|
|
79
|
+
const canWriteCurrentDoc = $derived(isCreating ? canCreate : canUpdate);
|
|
80
|
+
// "Show no mutation UI at all" — when true, render the read-only badge in
|
|
81
|
+
// place of the publish button cluster.
|
|
82
|
+
const isViewingReadOnly = $derived(
|
|
83
|
+
isReadOnly || (!canCreate && !canUpdate && !canDelete && !canPublishDoc && !canUnpublishDoc)
|
|
84
|
+
);
|
|
85
|
+
|
|
54
86
|
// Schema and document state
|
|
55
87
|
let schema = $state<SchemaType | null>(null);
|
|
56
88
|
let schemaLoading = $state(false);
|
|
@@ -64,15 +96,56 @@
|
|
|
64
96
|
let lastSaved = $state<Date | null>(null);
|
|
65
97
|
let publishSuccess = $state<Date | null>(null);
|
|
66
98
|
|
|
67
|
-
|
|
68
99
|
// Perspective toggle
|
|
69
100
|
let perspective = $state<'draft' | 'published'>('draft');
|
|
70
101
|
let publishedData = $state<Record<string, any> | null>(null);
|
|
71
102
|
const isViewingPublished = $derived(perspective === 'published');
|
|
72
103
|
|
|
104
|
+
// Field-level access: mirrors the server's strip/drop logic so the form
|
|
105
|
+
// only shows fields the user can see and disables fields they can't write.
|
|
106
|
+
function fieldRoleAllowed(list: string[] | undefined): boolean {
|
|
107
|
+
if (!list) return true;
|
|
108
|
+
const role = perms.role;
|
|
109
|
+
return role !== null && list.includes(role);
|
|
110
|
+
}
|
|
111
|
+
const hiddenFieldNames = $derived(
|
|
112
|
+
new Set(
|
|
113
|
+
(schema?.fields ?? []).filter((f) => !fieldRoleAllowed(f.access?.read)).map((f) => f.name)
|
|
114
|
+
)
|
|
115
|
+
);
|
|
116
|
+
function isFieldReadonly(fieldName: string): boolean {
|
|
117
|
+
const field = schema?.fields.find((f) => f.name === fieldName);
|
|
118
|
+
// In create mode, collection-level write ability is document.create;
|
|
119
|
+
// in edit mode it's document.update. Per-field `access.update` still
|
|
120
|
+
// wins as an additional restriction when the user IS able to write.
|
|
121
|
+
if (!field) return !canWriteCurrentDoc;
|
|
122
|
+
if (!fieldRoleAllowed(field.access?.update)) return true;
|
|
123
|
+
return !canWriteCurrentDoc;
|
|
124
|
+
}
|
|
125
|
+
|
|
73
126
|
// Inspect modal
|
|
74
127
|
let showInspect = $state(false);
|
|
75
128
|
|
|
129
|
+
// Field group tabs — only render when schema declares `groups`.
|
|
130
|
+
// 'all' = show every field; otherwise filter to fields whose `group`
|
|
131
|
+
// matches (supports string or string[]).
|
|
132
|
+
let activeGroup = $state<string>('all');
|
|
133
|
+
|
|
134
|
+
$effect(() => {
|
|
135
|
+
if (!schema?.groups?.length) {
|
|
136
|
+
activeGroup = 'all';
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const defaultGroup = schema.groups.find((g) => g.default && !g.hidden);
|
|
140
|
+
activeGroup = defaultGroup?.name ?? 'all';
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
function fieldInGroup(field: { group?: string | string[] }, groupName: string): boolean {
|
|
144
|
+
if (groupName === 'all') return true;
|
|
145
|
+
if (!field.group) return false;
|
|
146
|
+
return Array.isArray(field.group) ? field.group.includes(groupName) : field.group === groupName;
|
|
147
|
+
}
|
|
148
|
+
|
|
76
149
|
function syntaxHighlightJson(json: string): string {
|
|
77
150
|
return json.replace(
|
|
78
151
|
/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g,
|
|
@@ -106,7 +179,12 @@
|
|
|
106
179
|
|
|
107
180
|
// Version history
|
|
108
181
|
let showVersionHistory = $state(false);
|
|
109
|
-
let previewingVersion = $state<{
|
|
182
|
+
let previewingVersion = $state<{
|
|
183
|
+
versionNumber: number;
|
|
184
|
+
data: Record<string, any>;
|
|
185
|
+
eventType: string;
|
|
186
|
+
createdAt?: string;
|
|
187
|
+
} | null>(null);
|
|
110
188
|
const activePreview = $derived(externalVersionPreview || previewingVersion);
|
|
111
189
|
const isPreviewingVersion = $derived(!!activePreview);
|
|
112
190
|
|
|
@@ -136,29 +214,46 @@
|
|
|
136
214
|
|
|
137
215
|
const savedAgoText = $derived(lastSaved ? `Saved ${timeAgo(lastSaved)}` : null);
|
|
138
216
|
|
|
139
|
-
// Menu dropdown state
|
|
140
|
-
let showDropdown = $state(false);
|
|
141
|
-
|
|
142
217
|
// Auto-save functionality (every 2 seconds when there are changes)
|
|
143
218
|
let hasUnsavedChanges = $state(false);
|
|
144
219
|
let autoSaveTimer: ReturnType<typeof setTimeout> | null = null;
|
|
145
220
|
let hasValidationErrors = $state(false);
|
|
221
|
+
// Snapshot of the default-initialized data for a brand-new (unsaved) document.
|
|
222
|
+
// Used to detect real user edits without relying on "is the value truthy" heuristics,
|
|
223
|
+
// so unchecking a boolean counts as a change and booleans with initialValue: true
|
|
224
|
+
// don't auto-create the doc on mount.
|
|
225
|
+
let initialDocumentData: Record<string, any> | null = null;
|
|
226
|
+
|
|
227
|
+
// Share save state with nested components (e.g. ObjectModal) via context
|
|
228
|
+
setSaveStateContext({
|
|
229
|
+
get saving() {
|
|
230
|
+
return saving;
|
|
231
|
+
},
|
|
232
|
+
get hasUnsavedChanges() {
|
|
233
|
+
return hasUnsavedChanges;
|
|
234
|
+
},
|
|
235
|
+
get savedAgoText() {
|
|
236
|
+
return savedAgoText;
|
|
237
|
+
}
|
|
238
|
+
});
|
|
146
239
|
|
|
147
240
|
let orphanedFields = $state<OrphanedField[]>([]);
|
|
148
241
|
let showOrphanedFields = $state(false);
|
|
149
242
|
let schemaFields: SchemaField[] = [];
|
|
150
243
|
|
|
151
244
|
// Track previous document to detect actual switches (not create→edit transitions)
|
|
152
|
-
|
|
153
|
-
let
|
|
245
|
+
// Plain lets (not $state) — only read/written inside the clearing effect; no need for reactivity
|
|
246
|
+
let previousDocumentId: string | null | undefined = undefined;
|
|
247
|
+
let previousDocumentType: string | undefined = undefined;
|
|
154
248
|
let justCreatedDocument = $state(false); // Flag to skip loadDocumentData after creation
|
|
155
249
|
|
|
156
250
|
// Hash-based state tracking
|
|
157
251
|
const hasUnpublishedContent = $derived(
|
|
158
252
|
hasUnpublishedChanges(documentData, fullDocument?._meta?.publishedHash || null)
|
|
159
253
|
);
|
|
254
|
+
const isUnpublished = $derived(fullDocument?._meta?.status === 'unpublished');
|
|
160
255
|
const canPublish = $derived(
|
|
161
|
-
hasUnpublishedContent && !saving && documentId && !hasValidationErrors
|
|
256
|
+
(hasUnpublishedContent || isUnpublished) && !saving && documentId && !hasValidationErrors
|
|
162
257
|
);
|
|
163
258
|
|
|
164
259
|
// Get preview title based on schema config
|
|
@@ -189,6 +284,7 @@
|
|
|
189
284
|
// Clear all state immediately to prevent old data from being auto-saved
|
|
190
285
|
documentData = {};
|
|
191
286
|
fullDocument = null;
|
|
287
|
+
initialDocumentData = null;
|
|
192
288
|
hasUnsavedChanges = false;
|
|
193
289
|
saveError = null;
|
|
194
290
|
lastSaved = null;
|
|
@@ -293,8 +389,8 @@
|
|
|
293
389
|
|
|
294
390
|
// With LocalAPI, data is flattened at top level (not in draftData)
|
|
295
391
|
// Extract all fields except id and _meta
|
|
296
|
-
|
|
297
|
-
|
|
392
|
+
const { id: _id, _meta, ...data } = response.data;
|
|
393
|
+
cmsLogger.debug('[Document Editor', '📄 Full response.data:', _id);
|
|
298
394
|
cmsLogger.debug('[Document Editor]', '📄 Full response.data:', response.data);
|
|
299
395
|
cmsLogger.debug('[Document Editor]', '📄 Extracted data (after destructuring):', data);
|
|
300
396
|
cmsLogger.debug('[Document Editor]', '📄 Keys in extracted data:', Object.keys(data));
|
|
@@ -365,6 +461,7 @@
|
|
|
365
461
|
}
|
|
366
462
|
|
|
367
463
|
documentData = initialData;
|
|
464
|
+
initialDocumentData = structuredClone(initialData);
|
|
368
465
|
fullDocument = null;
|
|
369
466
|
hasUnsavedChanges = false;
|
|
370
467
|
lastSaved = null;
|
|
@@ -372,29 +469,21 @@
|
|
|
372
469
|
cmsLogger.debug('[Document Editor]', '✅ Document initialized with:', initialData);
|
|
373
470
|
}
|
|
374
471
|
|
|
375
|
-
// Check if
|
|
376
|
-
function hasMeaningfulContent(data: Record<string, any>): boolean {
|
|
377
|
-
return Object.values(data).some((value) => {
|
|
378
|
-
if (typeof value === 'string') return value.trim() !== '';
|
|
379
|
-
if (typeof value === 'boolean') return value !== false; // Assuming false is default
|
|
380
|
-
if (Array.isArray(value)) return value.length > 0;
|
|
381
|
-
if (typeof value === 'object' && value !== null) return Object.keys(value).length > 0;
|
|
382
|
-
return value !== null && value !== undefined && value !== '';
|
|
383
|
-
});
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// Check if current data differs from last saved draft
|
|
472
|
+
// Check if current data differs from last saved draft (or initial defaults for a new doc)
|
|
387
473
|
function hasChangesFromSaved(): boolean {
|
|
388
|
-
|
|
474
|
+
const baseline = fullDocument
|
|
475
|
+
? (() => {
|
|
476
|
+
const { id, _meta, ...savedData } = fullDocument;
|
|
477
|
+
return savedData;
|
|
478
|
+
})()
|
|
479
|
+
: initialDocumentData;
|
|
389
480
|
|
|
390
|
-
|
|
391
|
-
const { id, _meta, ...savedData } = fullDocument;
|
|
481
|
+
if (!baseline) return true;
|
|
392
482
|
|
|
393
|
-
// Compare current documentData with saved data using stable JSON
|
|
394
483
|
const currentJson = JSON.stringify(sortObjectForComparison(documentData));
|
|
395
|
-
const
|
|
484
|
+
const baselineJson = JSON.stringify(sortObjectForComparison(baseline));
|
|
396
485
|
|
|
397
|
-
return currentJson !==
|
|
486
|
+
return currentJson !== baselineJson;
|
|
398
487
|
}
|
|
399
488
|
|
|
400
489
|
// Helper to recursively sort object keys for stable comparison
|
|
@@ -419,24 +508,18 @@
|
|
|
419
508
|
|
|
420
509
|
// Watch for changes to trigger auto-save (debounced)
|
|
421
510
|
$effect(() => {
|
|
422
|
-
const hasContent = hasMeaningfulContent(documentData);
|
|
423
511
|
const hasChanges = hasChangesFromSaved();
|
|
424
512
|
|
|
425
|
-
|
|
426
|
-
if (hasContent && hasChanges) {
|
|
427
|
-
hasUnsavedChanges = true;
|
|
428
|
-
} else if (!hasChanges) {
|
|
429
|
-
// If there are no changes from saved, mark as not having unsaved changes
|
|
430
|
-
hasUnsavedChanges = false;
|
|
431
|
-
}
|
|
513
|
+
hasUnsavedChanges = hasChanges;
|
|
432
514
|
|
|
433
515
|
if (autoSaveTimer) {
|
|
434
516
|
clearTimeout(autoSaveTimer);
|
|
435
517
|
}
|
|
436
518
|
|
|
437
|
-
// Debounced auto-save
|
|
438
|
-
//
|
|
439
|
-
|
|
519
|
+
// Debounced auto-save. Comparing against the initial-defaults snapshot (for new
|
|
520
|
+
// docs) or the saved draft (for existing docs) means any real user edit triggers
|
|
521
|
+
// save — including unchecking a boolean or clearing a field.
|
|
522
|
+
if (hasChanges && schema && canWriteCurrentDoc) {
|
|
440
523
|
autoSaveTimer = setTimeout(() => {
|
|
441
524
|
cmsLogger.debug(
|
|
442
525
|
'[Document Editor]',
|
|
@@ -507,7 +590,6 @@
|
|
|
507
590
|
// instead of what the server returned, in case server adds extra metadata
|
|
508
591
|
if (response?.success && response.data) {
|
|
509
592
|
cmsLogger.debug('[Document Editor]', 'meta response data:', response.data);
|
|
510
|
-
// @ts-expect-error
|
|
511
593
|
const { id: responseId, _meta } = response.data;
|
|
512
594
|
// Reconstruct fullDocument using the data we sent (documentData)
|
|
513
595
|
// plus the id and _meta from the response
|
|
@@ -560,10 +642,22 @@
|
|
|
560
642
|
if (!documentId || saving) return;
|
|
561
643
|
|
|
562
644
|
// Check for validation errors before publishing (Sanity-style)
|
|
563
|
-
await validateAllFields();
|
|
564
|
-
|
|
565
|
-
if (
|
|
566
|
-
|
|
645
|
+
const invalid = await validateAllFields();
|
|
646
|
+
|
|
647
|
+
if (invalid.length > 0) {
|
|
648
|
+
// Show up to three field titles in the toast so the user doesn't
|
|
649
|
+
// have to hunt for the red outline — longer lists are summarised.
|
|
650
|
+
const preview = invalid
|
|
651
|
+
.slice(0, 3)
|
|
652
|
+
.map((f) => f.title)
|
|
653
|
+
.join(', ');
|
|
654
|
+
const remainder = invalid.length > 3 ? ` and ${invalid.length - 3} more` : '';
|
|
655
|
+
const detail = invalid.map((f) => `${f.title}: ${f.messages.join(', ')}`).join('\n');
|
|
656
|
+
|
|
657
|
+
saveError = `Cannot publish — fix: ${preview}${remainder}`;
|
|
658
|
+
toast.error(`Fix ${invalid.length} field${invalid.length === 1 ? '' : 's'} to publish`, {
|
|
659
|
+
description: detail
|
|
660
|
+
});
|
|
567
661
|
return;
|
|
568
662
|
}
|
|
569
663
|
|
|
@@ -632,7 +726,13 @@
|
|
|
632
726
|
async function unpublishDocument() {
|
|
633
727
|
if (!documentId || saving) return;
|
|
634
728
|
|
|
635
|
-
const confirmUnpublish =
|
|
729
|
+
const confirmUnpublish = await confirmDialog({
|
|
730
|
+
title: 'Unpublish this document?',
|
|
731
|
+
description:
|
|
732
|
+
'It will be removed from published queries, but the data is preserved and you can re-publish anytime.',
|
|
733
|
+
confirmText: 'Unpublish',
|
|
734
|
+
variant: 'destructive'
|
|
735
|
+
});
|
|
636
736
|
if (!confirmUnpublish) return;
|
|
637
737
|
|
|
638
738
|
saving = true;
|
|
@@ -641,9 +741,14 @@
|
|
|
641
741
|
try {
|
|
642
742
|
const response = await documents.unpublish(documentId);
|
|
643
743
|
if (response.success) {
|
|
644
|
-
fullDocument = {
|
|
744
|
+
fullDocument = {
|
|
745
|
+
...fullDocument,
|
|
746
|
+
_meta: { ...fullDocument?._meta, status: 'unpublished' }
|
|
747
|
+
};
|
|
645
748
|
toast.success('Document unpublished — you can re-publish anytime');
|
|
646
|
-
|
|
749
|
+
if (onUnpublished && documentId) {
|
|
750
|
+
onUnpublished(documentId);
|
|
751
|
+
}
|
|
647
752
|
} else {
|
|
648
753
|
throw new Error(response.error || 'Failed to unpublish');
|
|
649
754
|
}
|
|
@@ -655,13 +760,15 @@
|
|
|
655
760
|
}
|
|
656
761
|
|
|
657
762
|
// Validate all fields before publishing
|
|
658
|
-
async function validateAllFields(): Promise<
|
|
763
|
+
async function validateAllFields(): Promise<
|
|
764
|
+
Array<{ name: string; title: string; messages: string[] }>
|
|
765
|
+
> {
|
|
659
766
|
if (!schema) {
|
|
660
767
|
hasValidationErrors = false;
|
|
661
|
-
return;
|
|
768
|
+
return [];
|
|
662
769
|
}
|
|
663
770
|
|
|
664
|
-
|
|
771
|
+
const invalid: Array<{ name: string; title: string; messages: string[] }> = [];
|
|
665
772
|
|
|
666
773
|
for (const field of schema.fields) {
|
|
667
774
|
if (field.validation) {
|
|
@@ -670,34 +777,47 @@
|
|
|
670
777
|
? field.validation
|
|
671
778
|
: [field.validation];
|
|
672
779
|
|
|
780
|
+
const messages: string[] = [];
|
|
673
781
|
for (const validationFn of validationFunctions) {
|
|
674
782
|
const rule = validationFn(new Rule());
|
|
675
783
|
const markers = await rule.validate(documentData[field.name], { path: [field.name] });
|
|
676
784
|
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
cmsLogger.debug(
|
|
680
|
-
`[DocumentEditor] Validation error in field '${field.name}':`,
|
|
681
|
-
markers
|
|
682
|
-
);
|
|
785
|
+
for (const m of markers) {
|
|
786
|
+
if (m.level === 'error') messages.push(m.message ?? 'Invalid');
|
|
683
787
|
}
|
|
684
788
|
}
|
|
789
|
+
|
|
790
|
+
if (messages.length > 0) {
|
|
791
|
+
invalid.push({ name: field.name, title: field.title ?? field.name, messages });
|
|
792
|
+
cmsLogger.debug(
|
|
793
|
+
`[DocumentEditor] Validation error in field '${field.name}':`,
|
|
794
|
+
messages
|
|
795
|
+
);
|
|
796
|
+
}
|
|
685
797
|
} catch (error) {
|
|
686
|
-
|
|
687
|
-
|
|
798
|
+
invalid.push({
|
|
799
|
+
name: field.name,
|
|
800
|
+
title: field.title ?? field.name,
|
|
801
|
+
messages: ['Validation check threw an error']
|
|
802
|
+
});
|
|
803
|
+
toast.error(`Validation failed for field "${field.title ?? field.name}"`);
|
|
688
804
|
}
|
|
689
805
|
}
|
|
690
806
|
}
|
|
691
807
|
|
|
692
|
-
hasValidationErrors =
|
|
808
|
+
hasValidationErrors = invalid.length > 0;
|
|
809
|
+
return invalid;
|
|
693
810
|
}
|
|
694
811
|
|
|
695
812
|
async function deleteDocument() {
|
|
696
813
|
if (!documentId || saving) return;
|
|
697
814
|
|
|
698
|
-
const confirmDelete =
|
|
699
|
-
|
|
700
|
-
|
|
815
|
+
const confirmDelete = await confirmDialog({
|
|
816
|
+
title: 'Delete this document?',
|
|
817
|
+
description: 'This action cannot be undone.',
|
|
818
|
+
confirmText: 'Delete',
|
|
819
|
+
variant: 'destructive'
|
|
820
|
+
});
|
|
701
821
|
if (!confirmDelete) return;
|
|
702
822
|
|
|
703
823
|
saving = true;
|
|
@@ -779,264 +899,386 @@
|
|
|
779
899
|
}
|
|
780
900
|
</script>
|
|
781
901
|
|
|
782
|
-
<div class="relative flex h-full flex-col overflow-hidden">
|
|
783
|
-
<!-- Header
|
|
784
|
-
<div class="
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
<div class="
|
|
788
|
-
<
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
902
|
+
<div class="relative flex h-full w-full min-w-0 flex-col overflow-hidden">
|
|
903
|
+
<!-- Hero Header -->
|
|
904
|
+
<div class="bg-background w-full min-w-0 overflow-x-clip px-4 pt-4 pb-5 lg:px-6 lg:pt-5">
|
|
905
|
+
<div class="mx-auto w-full max-w-3xl">
|
|
906
|
+
<!-- Top row: breadcrumb + actions -->
|
|
907
|
+
<div class="mb-4 flex items-center justify-between gap-3">
|
|
908
|
+
<div
|
|
909
|
+
class="text-muted-foreground flex min-w-0 items-center gap-2 text-[11px] font-medium tracking-wider uppercase"
|
|
910
|
+
>
|
|
911
|
+
<span class="whitespace-nowrap">{schema?.title || documentType}</span>
|
|
912
|
+
<span aria-hidden="true">·</span>
|
|
913
|
+
<span class="truncate">{getPreviewTitle()}</span>
|
|
914
|
+
</div>
|
|
915
|
+
|
|
916
|
+
<div class="flex shrink-0 items-center gap-2">
|
|
792
917
|
{#if saving}
|
|
793
|
-
<span
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
918
|
+
<span
|
|
919
|
+
class="text-muted-foreground hidden items-center gap-1.5 text-[10px] font-medium tracking-wider whitespace-nowrap uppercase sm:inline-flex"
|
|
920
|
+
>
|
|
921
|
+
<span class="bg-muted-foreground/60 h-1.5 w-1.5 animate-pulse rounded-full"></span>
|
|
922
|
+
Saving
|
|
797
923
|
</span>
|
|
798
924
|
{:else if hasUnsavedChanges}
|
|
799
|
-
<span
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
925
|
+
<span
|
|
926
|
+
class="text-muted-foreground hidden items-center gap-1.5 text-[10px] font-medium tracking-wider whitespace-nowrap uppercase sm:inline-flex"
|
|
927
|
+
>
|
|
928
|
+
<span class="bg-muted-foreground/60 h-1.5 w-1.5 rounded-full"></span>
|
|
929
|
+
Unsaved
|
|
930
|
+
</span>
|
|
931
|
+
{:else if savedAgoText}
|
|
932
|
+
<span
|
|
933
|
+
class="text-muted-foreground hidden items-center gap-1.5 text-[10px] font-medium tracking-wider whitespace-nowrap uppercase sm:inline-flex"
|
|
934
|
+
>
|
|
935
|
+
<span class="bg-muted-foreground/60 h-1.5 w-1.5 rounded-full"></span>
|
|
936
|
+
Auto-saved
|
|
808
937
|
</span>
|
|
809
938
|
{/if}
|
|
810
|
-
</div>
|
|
811
|
-
</div>
|
|
812
|
-
</div>
|
|
813
939
|
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
<button
|
|
820
|
-
class="px-2.5 py-1 text-xs font-medium transition-colors {perspective === 'draft' ? 'bg-primary text-primary-foreground' : 'hover:bg-muted'}"
|
|
821
|
-
onclick={() => switchPerspective('draft')}
|
|
822
|
-
>
|
|
823
|
-
Draft
|
|
824
|
-
</button>
|
|
825
|
-
<button
|
|
826
|
-
class="px-2.5 py-1 text-xs font-medium transition-colors {perspective === 'published' ? 'bg-primary text-primary-foreground' : 'hover:bg-muted'}"
|
|
827
|
-
onclick={() => switchPerspective('published')}
|
|
828
|
-
>
|
|
829
|
-
Published
|
|
830
|
-
</button>
|
|
831
|
-
</div>
|
|
832
|
-
{/if}
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
<!-- Options dropdown -->
|
|
836
|
-
{#if documentId}
|
|
837
|
-
<div class="relative">
|
|
838
|
-
<Button
|
|
839
|
-
variant="ghost"
|
|
840
|
-
size="icon"
|
|
841
|
-
onclick={() => (showHeaderMenu = !showHeaderMenu)}
|
|
842
|
-
class="h-8 w-8"
|
|
843
|
-
>
|
|
844
|
-
<Ellipsis class="h-4 w-4" />
|
|
845
|
-
</Button>
|
|
846
|
-
{#if showHeaderMenu}
|
|
847
|
-
<div class="bg-background border-border absolute right-0 top-full z-50 mt-1 min-w-[160px] rounded-md border py-1 shadow-lg">
|
|
940
|
+
{#if documentId && fullDocument?._meta?.publishedHash}
|
|
941
|
+
{@const isPublished =
|
|
942
|
+
fullDocument?._meta?.status === 'published' && fullDocument?._meta?.publishedAt}
|
|
943
|
+
{@const isUnpub = fullDocument?._meta?.status === 'unpublished'}
|
|
944
|
+
<div class="flex items-center gap-1.5">
|
|
848
945
|
<button
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
showVersionHistory = true;
|
|
855
|
-
}
|
|
856
|
-
}}
|
|
857
|
-
class="hover:bg-muted flex w-full items-center gap-2 px-3 py-2 text-left text-sm transition-colors"
|
|
946
|
+
class="inline-flex cursor-pointer items-center gap-1.5 rounded-full border px-2.5 py-1 text-[10px] font-medium tracking-wider uppercase transition-colors {perspective ===
|
|
947
|
+
'draft'
|
|
948
|
+
? 'bg-primary/90 text-primary-foreground border-transparent'
|
|
949
|
+
: 'text-muted-foreground hover:bg-muted'}"
|
|
950
|
+
onclick={() => switchPerspective('draft')}
|
|
858
951
|
>
|
|
859
|
-
<
|
|
952
|
+
<span
|
|
953
|
+
class="bg-muted-foreground/60 h-1.5 w-1.5 rounded-full {perspective === 'draft'
|
|
954
|
+
? 'bg-primary-foreground/60'
|
|
955
|
+
: ''}"
|
|
956
|
+
></span>
|
|
957
|
+
Draft
|
|
860
958
|
</button>
|
|
861
959
|
<button
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
960
|
+
class="inline-flex cursor-pointer items-center gap-1.5 rounded-full border px-2.5 py-1 text-[10px] font-medium tracking-wider uppercase transition-colors {perspective ===
|
|
961
|
+
'published'
|
|
962
|
+
? 'bg-primary text-primary-foreground border-transparent'
|
|
963
|
+
: 'text-muted-foreground hover:bg-muted'}"
|
|
964
|
+
onclick={() => switchPerspective('published')}
|
|
867
965
|
>
|
|
868
|
-
|
|
966
|
+
{#if isPublished}
|
|
967
|
+
<span
|
|
968
|
+
class="h-1.5 w-1.5 rounded-full {perspective === 'published'
|
|
969
|
+
? 'bg-primary-foreground/60'
|
|
970
|
+
: 'bg-green-500'}"
|
|
971
|
+
></span>
|
|
972
|
+
Published · {timeAgo(new Date(fullDocument._meta.publishedAt))}
|
|
973
|
+
{:else if isUnpub}
|
|
974
|
+
<span
|
|
975
|
+
class="h-1.5 w-1.5 rounded-full {perspective === 'published'
|
|
976
|
+
? 'bg-primary-foreground/60'
|
|
977
|
+
: 'bg-muted-foreground/60'}"
|
|
978
|
+
></span>
|
|
979
|
+
Unpublished
|
|
980
|
+
{:else}
|
|
981
|
+
Published
|
|
982
|
+
{/if}
|
|
869
983
|
</button>
|
|
870
984
|
</div>
|
|
871
|
-
<div
|
|
872
|
-
class="fixed inset-0 z-40"
|
|
873
|
-
onclick={() => (showHeaderMenu = false)}
|
|
874
|
-
></div>
|
|
875
985
|
{/if}
|
|
876
|
-
</div>
|
|
877
|
-
{/if}
|
|
878
|
-
|
|
879
|
-
<!-- Close button (X) - hidden on mobile -->
|
|
880
|
-
<Button
|
|
881
|
-
variant="ghost"
|
|
882
|
-
size="icon"
|
|
883
|
-
onclick={onBack}
|
|
884
|
-
class="hidden h-8 w-8 hover:cursor-pointer lg:flex"
|
|
885
|
-
title="Close"
|
|
886
|
-
>
|
|
887
|
-
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
888
|
-
<path
|
|
889
|
-
stroke-linecap="round"
|
|
890
|
-
stroke-linejoin="round"
|
|
891
|
-
stroke-width="2"
|
|
892
|
-
d="M6 18L18 6M6 6l12 12"
|
|
893
|
-
/>
|
|
894
|
-
</svg>
|
|
895
|
-
</Button>
|
|
896
|
-
</div>
|
|
897
|
-
</div>
|
|
898
986
|
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
987
|
+
{#if documentId}
|
|
988
|
+
<div class="relative">
|
|
989
|
+
<Button
|
|
990
|
+
variant="ghost"
|
|
991
|
+
size="icon"
|
|
992
|
+
onclick={() => (showHeaderMenu = !showHeaderMenu)}
|
|
993
|
+
class="h-8 w-8 cursor-pointer"
|
|
994
|
+
>
|
|
995
|
+
<Ellipsis class="h-4 w-4" />
|
|
996
|
+
</Button>
|
|
997
|
+
{#if showHeaderMenu}
|
|
998
|
+
<div
|
|
999
|
+
class="bg-background border-rule absolute top-full right-0 z-50 mt-1 min-w-[160px] rounded-md border py-1 shadow-lg"
|
|
1000
|
+
>
|
|
1001
|
+
<button
|
|
1002
|
+
onclick={() => {
|
|
1003
|
+
showHeaderMenu = false;
|
|
1004
|
+
if (onOpenVersionHistory && documentId) {
|
|
1005
|
+
onOpenVersionHistory(documentId);
|
|
1006
|
+
} else {
|
|
1007
|
+
showVersionHistory = true;
|
|
1008
|
+
}
|
|
1009
|
+
}}
|
|
1010
|
+
class="hover:bg-muted flex w-full cursor-pointer items-center gap-2 px-3 py-2 text-left text-sm transition-colors"
|
|
1011
|
+
>
|
|
1012
|
+
<History class="h-3.5 w-3.5" /> History
|
|
1013
|
+
</button>
|
|
1014
|
+
<button
|
|
1015
|
+
onclick={() => {
|
|
1016
|
+
showHeaderMenu = false;
|
|
1017
|
+
showInspect = true;
|
|
1018
|
+
}}
|
|
1019
|
+
class="hover:bg-muted flex w-full cursor-pointer items-center gap-2 px-3 py-2 text-left text-sm transition-colors"
|
|
1020
|
+
>
|
|
1021
|
+
<Code class="h-3.5 w-3.5" /> Inspect
|
|
1022
|
+
</button>
|
|
1023
|
+
</div>
|
|
1024
|
+
<div class="fixed inset-0 z-40" onclick={() => (showHeaderMenu = false)}></div>
|
|
1025
|
+
{/if}
|
|
1026
|
+
</div>
|
|
1027
|
+
{/if}
|
|
906
1028
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
{:else if schema}
|
|
916
|
-
<!-- Orphaned Fields Warning -->
|
|
917
|
-
{#if showOrphanedFields && orphanedFields.length > 0}
|
|
918
|
-
<div class="space-y-3 rounded-md border border-orange-200 bg-orange-50 p-4">
|
|
919
|
-
<div class="flex items-center gap-2">
|
|
920
|
-
<svg
|
|
921
|
-
class="h-5 w-5 text-orange-600"
|
|
922
|
-
fill="none"
|
|
923
|
-
viewBox="0 0 24 24"
|
|
924
|
-
stroke="currentColor"
|
|
925
|
-
>
|
|
1029
|
+
<Button
|
|
1030
|
+
variant="ghost"
|
|
1031
|
+
size="icon"
|
|
1032
|
+
onclick={onBack}
|
|
1033
|
+
class="hidden h-8 w-8 hover:cursor-pointer lg:flex"
|
|
1034
|
+
title="Close"
|
|
1035
|
+
>
|
|
1036
|
+
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
926
1037
|
<path
|
|
927
1038
|
stroke-linecap="round"
|
|
928
1039
|
stroke-linejoin="round"
|
|
929
1040
|
stroke-width="2"
|
|
930
|
-
d="
|
|
1041
|
+
d="M6 18L18 6M6 6l12 12"
|
|
931
1042
|
/>
|
|
932
1043
|
</svg>
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
</div>
|
|
1044
|
+
</Button>
|
|
1045
|
+
</div>
|
|
1046
|
+
</div>
|
|
937
1047
|
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
1048
|
+
<!-- Title -->
|
|
1049
|
+
<h1 class="block w-full min-w-0 truncate text-3xl font-semibold tracking-tight">
|
|
1050
|
+
{getPreviewTitle()}
|
|
1051
|
+
</h1>
|
|
1052
|
+
|
|
1053
|
+
<!-- Mobile-only status row: save state + draft pill for narrow viewports -->
|
|
1054
|
+
<div class="mt-3 flex items-center justify-between gap-3 sm:hidden">
|
|
1055
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
1056
|
+
{#if !fullDocument?._meta?.publishedHash && documentId && fullDocument?._meta?.status !== 'unpublished'}
|
|
1057
|
+
<span
|
|
1058
|
+
class="text-muted-foreground inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-[10px] font-medium tracking-wider uppercase"
|
|
1059
|
+
>
|
|
1060
|
+
<span class="bg-muted-foreground/60 h-1.5 w-1.5 rounded-full"></span>
|
|
1061
|
+
Draft
|
|
1062
|
+
</span>
|
|
1063
|
+
{/if}
|
|
1064
|
+
</div>
|
|
941
1065
|
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
1066
|
+
{#if saving}
|
|
1067
|
+
<span
|
|
1068
|
+
class="text-muted-foreground inline-flex items-center gap-1.5 text-[10px] font-medium tracking-wider whitespace-nowrap uppercase"
|
|
1069
|
+
>
|
|
1070
|
+
<span class="bg-muted-foreground/60 h-1.5 w-1.5 animate-pulse rounded-full"></span>
|
|
1071
|
+
Saving
|
|
1072
|
+
</span>
|
|
1073
|
+
{:else if hasUnsavedChanges}
|
|
1074
|
+
<span
|
|
1075
|
+
class="text-muted-foreground inline-flex items-center gap-1.5 text-[10px] font-medium tracking-wider whitespace-nowrap uppercase"
|
|
1076
|
+
>
|
|
1077
|
+
<span class="bg-muted-foreground/60 h-1.5 w-1.5 rounded-full"></span>
|
|
1078
|
+
Unsaved
|
|
1079
|
+
</span>
|
|
1080
|
+
{:else if savedAgoText}
|
|
1081
|
+
<span
|
|
1082
|
+
class="text-muted-foreground inline-flex items-center gap-1.5 text-[10px] font-medium tracking-wider whitespace-nowrap uppercase"
|
|
1083
|
+
>
|
|
1084
|
+
<span class="bg-muted-foreground/60 h-1.5 w-1.5 rounded-full"></span>
|
|
1085
|
+
Auto-saved
|
|
1086
|
+
</span>
|
|
1087
|
+
{/if}
|
|
1088
|
+
</div>
|
|
1089
|
+
</div>
|
|
1090
|
+
</div>
|
|
1091
|
+
|
|
1092
|
+
<!-- Content Form -->
|
|
1093
|
+
<div class="flex-1 overflow-auto p-4 lg:p-6">
|
|
1094
|
+
<div class="mx-auto w-full max-w-3xl space-y-4 lg:space-y-6">
|
|
1095
|
+
{#if saveError}
|
|
1096
|
+
<div class="bg-destructive/10 border-destructive/20 rounded-md border p-3">
|
|
1097
|
+
<p class="text-destructive text-sm">{saveError}</p>
|
|
1098
|
+
</div>
|
|
1099
|
+
{/if}
|
|
1100
|
+
|
|
1101
|
+
{#if schemaError}
|
|
1102
|
+
<div class="bg-destructive/10 border-destructive/20 rounded-md border p-3">
|
|
1103
|
+
<p class="text-destructive text-sm">Schema Error: {schemaError}</p>
|
|
1104
|
+
</div>
|
|
1105
|
+
{:else if schemaLoading}
|
|
1106
|
+
<div class="p-6 text-center">
|
|
1107
|
+
<div class="text-muted-foreground text-sm">Loading schema...</div>
|
|
1108
|
+
</div>
|
|
1109
|
+
{:else if schema}
|
|
1110
|
+
<!-- Orphaned Fields Warning -->
|
|
1111
|
+
{#if showOrphanedFields && orphanedFields.length > 0}
|
|
1112
|
+
<div class="space-y-3 rounded-md border border-orange-200 bg-orange-50 p-4">
|
|
1113
|
+
<div class="flex items-center gap-2">
|
|
1114
|
+
<svg
|
|
1115
|
+
class="h-5 w-5 text-orange-600"
|
|
1116
|
+
fill="none"
|
|
1117
|
+
viewBox="0 0 24 24"
|
|
1118
|
+
stroke="currentColor"
|
|
946
1119
|
>
|
|
947
|
-
<
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
1120
|
+
<path
|
|
1121
|
+
stroke-linecap="round"
|
|
1122
|
+
stroke-linejoin="round"
|
|
1123
|
+
stroke-width="2"
|
|
1124
|
+
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L5.081 16.5c-.77.833.192 2.5 1.732 2.5z"
|
|
1125
|
+
/>
|
|
1126
|
+
</svg>
|
|
1127
|
+
<h4 class="text-sm font-medium text-orange-800">
|
|
1128
|
+
{orphanedFields.length} orphaned field{orphanedFields.length === 1 ? '' : 's'} detected
|
|
1129
|
+
</h4>
|
|
1130
|
+
</div>
|
|
1131
|
+
|
|
1132
|
+
<p class="text-sm text-orange-700">
|
|
1133
|
+
These fields exist in your document but are no longer defined in the schema:
|
|
1134
|
+
</p>
|
|
1135
|
+
|
|
1136
|
+
<div class="space-y-2">
|
|
1137
|
+
{#each orphanedFields as field, index (index)}
|
|
1138
|
+
<div
|
|
1139
|
+
class="flex items-center justify-between rounded border border-orange-200 bg-white p-3"
|
|
1140
|
+
>
|
|
1141
|
+
<div class="flex-1">
|
|
1142
|
+
<div class="font-mono text-sm font-medium text-orange-800">
|
|
1143
|
+
{field.path || field.key}
|
|
1144
|
+
</div>
|
|
1145
|
+
<div class="mt-1 text-xs text-orange-600">
|
|
1146
|
+
<code class="rounded bg-orange-100 px-1">{JSON.stringify(field.value)}</code>
|
|
1147
|
+
</div>
|
|
953
1148
|
</div>
|
|
1149
|
+
<Button
|
|
1150
|
+
size="sm"
|
|
1151
|
+
variant="outline"
|
|
1152
|
+
onclick={() => removeOrphanedField(field)}
|
|
1153
|
+
class="ml-3 h-8 border-red-200 px-3 text-red-600 hover:border-red-300 hover:bg-red-50"
|
|
1154
|
+
>
|
|
1155
|
+
Remove
|
|
1156
|
+
</Button>
|
|
954
1157
|
</div>
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
variant="outline"
|
|
958
|
-
onclick={() => removeOrphanedField(field)}
|
|
959
|
-
class="ml-3 h-8 border-red-200 px-3 text-red-600 hover:border-red-300 hover:bg-red-50"
|
|
960
|
-
>
|
|
961
|
-
Remove
|
|
962
|
-
</Button>
|
|
963
|
-
</div>
|
|
964
|
-
{/each}
|
|
965
|
-
</div>
|
|
1158
|
+
{/each}
|
|
1159
|
+
</div>
|
|
966
1160
|
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
1161
|
+
<div class="flex gap-2 border-t border-orange-200 pt-2">
|
|
1162
|
+
<Button
|
|
1163
|
+
size="sm"
|
|
1164
|
+
variant="outline"
|
|
1165
|
+
onclick={cleanupAllOrphanedFields}
|
|
1166
|
+
class="border-orange-600 bg-orange-600 text-white hover:bg-orange-700"
|
|
1167
|
+
>
|
|
1168
|
+
Remove All
|
|
1169
|
+
</Button>
|
|
1170
|
+
<Button
|
|
1171
|
+
size="sm"
|
|
1172
|
+
variant="ghost"
|
|
1173
|
+
onclick={dismissOrphanedFields}
|
|
1174
|
+
class="text-orange-700 hover:text-orange-800"
|
|
1175
|
+
>
|
|
1176
|
+
Dismiss
|
|
1177
|
+
</Button>
|
|
1178
|
+
</div>
|
|
984
1179
|
</div>
|
|
985
|
-
|
|
986
|
-
{/if}
|
|
1180
|
+
{/if}
|
|
987
1181
|
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
{#each schema.fields as field, index (index)}
|
|
993
|
-
{@const viewData = isPreviewingVersion && activePreview ? activePreview.data : isViewingPublished && publishedData ? publishedData : documentData}
|
|
994
|
-
<SchemaField
|
|
995
|
-
{field}
|
|
996
|
-
value={viewData[field.name]}
|
|
997
|
-
documentData={viewData}
|
|
998
|
-
onUpdate={(newValue) => {
|
|
999
|
-
if (isViewingPublished) return;
|
|
1000
|
-
documentData = { ...documentData, [field.name]: newValue };
|
|
1001
|
-
hasUnsavedChanges = true;
|
|
1002
|
-
}}
|
|
1003
|
-
{onOpenReference}
|
|
1004
|
-
schemaType={documentType}
|
|
1005
|
-
readonly={isReadOnly || isViewingPublished || isPreviewingVersion}
|
|
1006
|
-
organizationId={fullDocument?._meta?.organizationId}
|
|
1007
|
-
/>
|
|
1008
|
-
{/each}
|
|
1009
|
-
|
|
1010
|
-
{#snippet failed(error, reset)}
|
|
1011
|
-
<div class="border-destructive/30 bg-destructive/5 rounded-md border p-4 text-center">
|
|
1012
|
-
<p class="text-destructive font-medium">Document editor encountered an error</p>
|
|
1013
|
-
<p class="text-muted-foreground mt-1 text-sm">{error instanceof Error ? error.message : 'Unknown error'}</p>
|
|
1182
|
+
<!-- Field Group Tabs -->
|
|
1183
|
+
{#if schema.groups && schema.groups.length > 0}
|
|
1184
|
+
{@const visibleGroups = schema.groups.filter((g) => !g.hidden)}
|
|
1185
|
+
<div class="mb-4 flex items-center gap-1 overflow-x-auto py-1">
|
|
1014
1186
|
<button
|
|
1015
|
-
|
|
1016
|
-
onclick={
|
|
1187
|
+
type="button"
|
|
1188
|
+
onclick={() => (activeGroup = 'all')}
|
|
1189
|
+
class="cursor-pointer rounded-md px-3 py-1.5 text-sm font-medium whitespace-nowrap transition-colors {activeGroup ===
|
|
1190
|
+
'all'
|
|
1191
|
+
? 'bg-muted text-foreground'
|
|
1192
|
+
: 'text-muted-foreground hover:text-foreground'}"
|
|
1017
1193
|
>
|
|
1018
|
-
|
|
1194
|
+
All fields
|
|
1019
1195
|
</button>
|
|
1196
|
+
{#each visibleGroups as group (group.name)}
|
|
1197
|
+
<button
|
|
1198
|
+
type="button"
|
|
1199
|
+
onclick={() => (activeGroup = group.name)}
|
|
1200
|
+
class="flex cursor-pointer items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium whitespace-nowrap transition-colors {activeGroup ===
|
|
1201
|
+
group.name
|
|
1202
|
+
? 'bg-muted text-foreground'
|
|
1203
|
+
: 'text-muted-foreground hover:text-foreground'}"
|
|
1204
|
+
>
|
|
1205
|
+
{#if group.icon}
|
|
1206
|
+
{@const Icon = group.icon}
|
|
1207
|
+
<Icon class="h-4 w-4" />
|
|
1208
|
+
{/if}
|
|
1209
|
+
{group.title}
|
|
1210
|
+
</button>
|
|
1211
|
+
{/each}
|
|
1020
1212
|
</div>
|
|
1021
|
-
{/
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1213
|
+
{/if}
|
|
1214
|
+
|
|
1215
|
+
<!-- Dynamic Schema Fields -->
|
|
1216
|
+
<svelte:boundary
|
|
1217
|
+
onerror={(error) =>
|
|
1218
|
+
cmsLogger.error('[DocumentEditor]', 'Error in editor content:', error)}
|
|
1219
|
+
>
|
|
1220
|
+
{#each schema.fields.filter((f) => fieldInGroup(f, activeGroup) && !hiddenFieldNames.has(f.name)) as field (field.name)}
|
|
1221
|
+
{@const viewData =
|
|
1222
|
+
isPreviewingVersion && activePreview
|
|
1223
|
+
? activePreview.data
|
|
1224
|
+
: isViewingPublished && publishedData
|
|
1225
|
+
? publishedData
|
|
1226
|
+
: documentData}
|
|
1227
|
+
<SchemaField
|
|
1228
|
+
{field}
|
|
1229
|
+
value={viewData[field.name]}
|
|
1230
|
+
documentData={viewData}
|
|
1231
|
+
onUpdate={(newValue) => {
|
|
1232
|
+
if (isViewingPublished) return;
|
|
1233
|
+
documentData = { ...documentData, [field.name]: newValue };
|
|
1234
|
+
hasUnsavedChanges = true;
|
|
1235
|
+
}}
|
|
1236
|
+
{onOpenReference}
|
|
1237
|
+
schemaType={documentType}
|
|
1238
|
+
readonly={isReadOnly ||
|
|
1239
|
+
isViewingPublished ||
|
|
1240
|
+
isPreviewingVersion ||
|
|
1241
|
+
isFieldReadonly(field.name)}
|
|
1242
|
+
organizationId={fullDocument?._meta?.organizationId}
|
|
1243
|
+
/>
|
|
1244
|
+
{/each}
|
|
1245
|
+
|
|
1246
|
+
{#snippet failed(error, reset)}
|
|
1247
|
+
<div class="border-destructive/30 bg-destructive/5 rounded-md border p-4 text-center">
|
|
1248
|
+
<p class="text-destructive font-medium">Document editor encountered an error</p>
|
|
1249
|
+
<p class="text-muted-foreground mt-1 text-sm">
|
|
1250
|
+
{error instanceof Error ? error.message : 'Unknown error'}
|
|
1251
|
+
</p>
|
|
1252
|
+
<button
|
|
1253
|
+
class="bg-primary text-primary-foreground mt-3 rounded px-4 py-2 text-sm"
|
|
1254
|
+
onclick={reset}
|
|
1255
|
+
>
|
|
1256
|
+
Reload editor
|
|
1257
|
+
</button>
|
|
1258
|
+
</div>
|
|
1259
|
+
{/snippet}
|
|
1260
|
+
</svelte:boundary>
|
|
1261
|
+
{:else}
|
|
1262
|
+
<div class="border-muted-foreground/30 rounded-md border border-dashed p-4">
|
|
1263
|
+
<p class="text-muted-foreground text-center text-sm">
|
|
1264
|
+
No schema found for document type: {documentType}
|
|
1265
|
+
</p>
|
|
1266
|
+
</div>
|
|
1267
|
+
{/if}
|
|
1268
|
+
</div>
|
|
1030
1269
|
</div>
|
|
1031
1270
|
|
|
1032
1271
|
<!-- Sanity-style bottom bar -->
|
|
1033
1272
|
{#if documentId}
|
|
1034
|
-
<div class="border-
|
|
1273
|
+
<div class="border-rule bg-background relative z-50 border-t p-4">
|
|
1035
1274
|
{#if isPreviewingVersion && activePreview}
|
|
1036
1275
|
<!-- Version preview footer -->
|
|
1037
1276
|
<div class="flex items-center justify-between">
|
|
1038
1277
|
<p class="text-muted-foreground text-sm">
|
|
1039
|
-
Revision from {new Date(activePreview.createdAt || Date.now()).toLocaleString(
|
|
1278
|
+
Revision from {new Date(activePreview.createdAt || Date.now()).toLocaleString(
|
|
1279
|
+
undefined,
|
|
1280
|
+
{ month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit', hour12: true }
|
|
1281
|
+
)}
|
|
1040
1282
|
</p>
|
|
1041
1283
|
<Button
|
|
1042
1284
|
size="sm"
|
|
@@ -1064,6 +1306,9 @@
|
|
|
1064
1306
|
perspective = 'draft';
|
|
1065
1307
|
publishedData = null;
|
|
1066
1308
|
toast.success('Revision restored');
|
|
1309
|
+
if (onRestored && documentId) {
|
|
1310
|
+
onRestored(documentId);
|
|
1311
|
+
}
|
|
1067
1312
|
} catch {
|
|
1068
1313
|
toast.error('Failed to restore revision');
|
|
1069
1314
|
}
|
|
@@ -1079,78 +1324,74 @@
|
|
|
1079
1324
|
{#if fullDocument?._meta?.status === 'unpublished'}
|
|
1080
1325
|
Unpublished
|
|
1081
1326
|
{:else}
|
|
1082
|
-
Published on {fullDocument?._meta?.publishedAt
|
|
1327
|
+
Published on {fullDocument?._meta?.publishedAt
|
|
1328
|
+
? new Date(fullDocument._meta.publishedAt).toLocaleString(undefined, {
|
|
1329
|
+
month: 'short',
|
|
1330
|
+
day: 'numeric',
|
|
1331
|
+
year: 'numeric',
|
|
1332
|
+
hour: 'numeric',
|
|
1333
|
+
minute: '2-digit',
|
|
1334
|
+
hour12: true
|
|
1335
|
+
})
|
|
1336
|
+
: 'Unknown'}
|
|
1083
1337
|
{/if}
|
|
1084
1338
|
</p>
|
|
1085
1339
|
{#if fullDocument?._meta?.status === 'unpublished'}
|
|
1086
|
-
|
|
1087
|
-
size="sm"
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
>
|
|
1091
|
-
Publish
|
|
1092
|
-
</Button>
|
|
1093
|
-
{:else}
|
|
1094
|
-
<Button
|
|
1095
|
-
size="sm"
|
|
1096
|
-
variant="secondary"
|
|
1097
|
-
onclick={unpublishDocument}
|
|
1098
|
-
disabled={saving}
|
|
1099
|
-
>
|
|
1340
|
+
{#if canPublishDoc}
|
|
1341
|
+
<Button size="sm" onclick={publishDocument} disabled={saving}>Publish</Button>
|
|
1342
|
+
{/if}
|
|
1343
|
+
{:else if canUnpublishDoc}
|
|
1344
|
+
<Button size="sm" variant="secondary" onclick={unpublishDocument} disabled={saving}>
|
|
1100
1345
|
Unpublish
|
|
1101
1346
|
</Button>
|
|
1102
1347
|
{/if}
|
|
1103
1348
|
</div>
|
|
1104
1349
|
{:else}
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
{:else if hasUnsavedChanges}
|
|
1113
|
-
<Badge variant="outline">Unsaved</Badge>
|
|
1114
|
-
{:else if savedAgoText}
|
|
1115
|
-
<Badge variant="secondary">{savedAgoText}</Badge>
|
|
1116
|
-
{/if}
|
|
1117
|
-
</div>
|
|
1350
|
+
<div class="flex items-center justify-between">
|
|
1351
|
+
<!-- Left: Transient publish confirmation (save/unsaved now shown in header) -->
|
|
1352
|
+
<div class="flex items-center gap-2">
|
|
1353
|
+
{#if publishSuccess && now - publishSuccess.getTime() < 3000}
|
|
1354
|
+
<Badge variant="default">Published!</Badge>
|
|
1355
|
+
{/if}
|
|
1356
|
+
</div>
|
|
1118
1357
|
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1358
|
+
<!-- Right: Publish button + horizontal three dots menu -->
|
|
1359
|
+
<div class="flex items-center gap-2">
|
|
1360
|
+
{#if canPublishDoc && !isViewingPublished}
|
|
1361
|
+
<Button
|
|
1362
|
+
onclick={publishDocument}
|
|
1363
|
+
disabled={!canPublish}
|
|
1364
|
+
size="sm"
|
|
1365
|
+
variant={canPublish ? 'default' : 'secondary'}
|
|
1366
|
+
class="cursor-pointer"
|
|
1367
|
+
>
|
|
1368
|
+
{#if saving}
|
|
1369
|
+
Publishing...
|
|
1370
|
+
{:else if isUnpublished}
|
|
1371
|
+
Publish
|
|
1372
|
+
{:else if !hasUnpublishedContent}
|
|
1373
|
+
Published
|
|
1374
|
+
{:else}
|
|
1375
|
+
Publish Changes
|
|
1376
|
+
{/if}
|
|
1377
|
+
</Button>
|
|
1378
|
+
{:else if isViewingReadOnly}
|
|
1379
|
+
<Badge variant="secondary" class="text-xs">Read Only</Badge>
|
|
1380
|
+
{/if}
|
|
1140
1381
|
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1382
|
+
{#if canDelete}
|
|
1383
|
+
<Button
|
|
1384
|
+
variant="ghost"
|
|
1385
|
+
size="icon"
|
|
1386
|
+
class="text-muted-foreground hover:text-destructive h-8 w-8"
|
|
1387
|
+
onclick={deleteDocument}
|
|
1388
|
+
title="Delete document"
|
|
1389
|
+
>
|
|
1390
|
+
<Trash2 class="h-4 w-4" />
|
|
1391
|
+
</Button>
|
|
1392
|
+
{/if}
|
|
1393
|
+
</div>
|
|
1152
1394
|
</div>
|
|
1153
|
-
</div>
|
|
1154
1395
|
{/if}
|
|
1155
1396
|
</div>
|
|
1156
1397
|
{/if}
|
|
@@ -1161,18 +1402,29 @@
|
|
|
1161
1402
|
<!-- Backdrop -->
|
|
1162
1403
|
<button
|
|
1163
1404
|
class="flex-1 bg-black/30"
|
|
1164
|
-
onclick={() => {
|
|
1405
|
+
onclick={() => {
|
|
1406
|
+
showVersionHistory = false;
|
|
1407
|
+
previewingVersion = null;
|
|
1408
|
+
}}
|
|
1165
1409
|
></button>
|
|
1166
1410
|
<!-- Panel -->
|
|
1167
|
-
<div class="bg-background border-
|
|
1168
|
-
<div class="border-
|
|
1411
|
+
<div class="bg-background border-rule flex w-80 flex-col border-l shadow-lg">
|
|
1412
|
+
<div class="border-rule flex items-center justify-between border-b px-4 py-3">
|
|
1169
1413
|
<h3 class="text-sm font-medium">Version History</h3>
|
|
1170
1414
|
<button
|
|
1171
1415
|
class="hover:bg-muted rounded p-1 transition-colors"
|
|
1172
|
-
onclick={() => {
|
|
1416
|
+
onclick={() => {
|
|
1417
|
+
showVersionHistory = false;
|
|
1418
|
+
previewingVersion = null;
|
|
1419
|
+
}}
|
|
1173
1420
|
>
|
|
1174
1421
|
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
1175
|
-
<path
|
|
1422
|
+
<path
|
|
1423
|
+
stroke-linecap="round"
|
|
1424
|
+
stroke-linejoin="round"
|
|
1425
|
+
stroke-width="2"
|
|
1426
|
+
d="M6 18L18 6M6 6l12 12"
|
|
1427
|
+
/>
|
|
1176
1428
|
</svg>
|
|
1177
1429
|
</button>
|
|
1178
1430
|
</div>
|
|
@@ -1186,14 +1438,17 @@
|
|
|
1186
1438
|
<div class="divide-y">
|
|
1187
1439
|
{#each response.data as version}
|
|
1188
1440
|
<button
|
|
1189
|
-
class="w-full space-y-1 px-4 py-3 text-left transition-colors
|
|
1441
|
+
class="hover:bg-muted w-full space-y-1 px-4 py-3 text-left transition-colors {previewingVersion?.versionNumber ===
|
|
1442
|
+
version.versionNumber
|
|
1443
|
+
? 'bg-muted border-l-primary border-l-2'
|
|
1444
|
+
: ''}"
|
|
1190
1445
|
onclick={async () => {
|
|
1191
1446
|
try {
|
|
1192
1447
|
const res = await documents.getVersion(documentId, version.versionNumber);
|
|
1193
1448
|
if (res.success && res.data) {
|
|
1194
1449
|
previewingVersion = {
|
|
1195
1450
|
versionNumber: version.versionNumber,
|
|
1196
|
-
data: res.data.data,
|
|
1451
|
+
data: res.data.data ?? {},
|
|
1197
1452
|
eventType: version.eventType
|
|
1198
1453
|
};
|
|
1199
1454
|
}
|
|
@@ -1204,12 +1459,19 @@
|
|
|
1204
1459
|
>
|
|
1205
1460
|
<div class="flex items-center gap-2">
|
|
1206
1461
|
<span class="text-xs font-medium">v{version.versionNumber}</span>
|
|
1207
|
-
<Badge
|
|
1462
|
+
<Badge
|
|
1463
|
+
variant={version.eventType === 'publish'
|
|
1464
|
+
? 'default'
|
|
1465
|
+
: version.eventType === 'restore'
|
|
1466
|
+
? 'outline'
|
|
1467
|
+
: 'secondary'}
|
|
1468
|
+
class="text-[10px]"
|
|
1469
|
+
>
|
|
1208
1470
|
{version.eventType}
|
|
1209
1471
|
</Badge>
|
|
1210
1472
|
</div>
|
|
1211
1473
|
<p class="text-muted-foreground text-[11px]">
|
|
1212
|
-
{new Date(version.createdAt).toLocaleString()}
|
|
1474
|
+
{version.createdAt ? new Date(version.createdAt).toLocaleString() : ''}
|
|
1213
1475
|
</p>
|
|
1214
1476
|
</button>
|
|
1215
1477
|
{/each}
|
|
@@ -1232,32 +1494,44 @@
|
|
|
1232
1494
|
<!-- Inspect Modal -->
|
|
1233
1495
|
{#if showInspect}
|
|
1234
1496
|
<div class="absolute inset-0 z-50 flex items-center justify-center bg-black/50">
|
|
1235
|
-
<div
|
|
1497
|
+
<div
|
|
1498
|
+
class="bg-background border-rule mx-4 flex h-[80%] w-full max-w-3xl flex-col rounded-lg border shadow-xl"
|
|
1499
|
+
>
|
|
1236
1500
|
<!-- Modal header -->
|
|
1237
1501
|
<div class="flex items-center justify-between border-b px-4 py-3">
|
|
1238
1502
|
<div>
|
|
1239
1503
|
<h3 class="text-sm font-semibold">Inspecting <em>{getPreviewTitle()}</em></h3>
|
|
1240
1504
|
</div>
|
|
1241
|
-
<
|
|
1505
|
+
<Button
|
|
1506
|
+
variant="ghost"
|
|
1242
1507
|
class="hover:bg-muted rounded p-1 transition-colors"
|
|
1243
1508
|
onclick={() => (showInspect = false)}
|
|
1244
1509
|
>
|
|
1245
1510
|
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
1246
|
-
<path
|
|
1511
|
+
<path
|
|
1512
|
+
stroke-linecap="round"
|
|
1513
|
+
stroke-linejoin="round"
|
|
1514
|
+
stroke-width="2"
|
|
1515
|
+
d="M6 18L18 6M6 6l12 12"
|
|
1516
|
+
/>
|
|
1247
1517
|
</svg>
|
|
1248
|
-
</
|
|
1518
|
+
</Button>
|
|
1249
1519
|
</div>
|
|
1250
1520
|
|
|
1251
1521
|
<!-- Tabs -->
|
|
1252
|
-
<div class="border-b
|
|
1522
|
+
<div class="flex border-b">
|
|
1253
1523
|
<button
|
|
1254
|
-
class="px-4 py-2 text-sm font-medium transition-colors {inspectTab === 'parsed'
|
|
1524
|
+
class="px-4 py-2 text-sm font-medium transition-colors {inspectTab === 'parsed'
|
|
1525
|
+
? 'border-primary text-foreground border-b-2'
|
|
1526
|
+
: 'text-muted-foreground hover:text-foreground'}"
|
|
1255
1527
|
onclick={() => (inspectTab = 'parsed')}
|
|
1256
1528
|
>
|
|
1257
1529
|
Parsed
|
|
1258
1530
|
</button>
|
|
1259
1531
|
<button
|
|
1260
|
-
class="px-4 py-2 text-sm font-medium transition-colors {inspectTab === 'raw'
|
|
1532
|
+
class="px-4 py-2 text-sm font-medium transition-colors {inspectTab === 'raw'
|
|
1533
|
+
? 'border-primary text-foreground border-b-2'
|
|
1534
|
+
: 'text-muted-foreground hover:text-foreground'}"
|
|
1261
1535
|
onclick={() => (inspectTab = 'raw')}
|
|
1262
1536
|
>
|
|
1263
1537
|
Raw JSON
|
|
@@ -1268,7 +1542,7 @@
|
|
|
1268
1542
|
<div class="flex-1 overflow-auto p-4 font-mono text-sm">
|
|
1269
1543
|
{#if inspectTab === 'raw'}
|
|
1270
1544
|
<pre
|
|
1271
|
-
class="
|
|
1545
|
+
class="text-xs break-all whitespace-pre-wrap select-text"
|
|
1272
1546
|
tabindex="0"
|
|
1273
1547
|
onkeydown={(e) => {
|
|
1274
1548
|
if ((e.metaKey || e.ctrlKey) && e.key === 'a') {
|
|
@@ -1280,10 +1554,19 @@
|
|
|
1280
1554
|
sel?.removeAllRanges();
|
|
1281
1555
|
sel?.addRange(range);
|
|
1282
1556
|
}
|
|
1283
|
-
}}
|
|
1284
|
-
|
|
1557
|
+
}}>{@html syntaxHighlightJson(
|
|
1558
|
+
JSON.stringify(
|
|
1559
|
+
{ id: documentId, _meta: fullDocument?._meta, ...documentData },
|
|
1560
|
+
null,
|
|
1561
|
+
2
|
|
1562
|
+
)
|
|
1563
|
+
)}</pre>
|
|
1285
1564
|
{:else}
|
|
1286
|
-
{@render parsedValue(
|
|
1565
|
+
{@render parsedValue(
|
|
1566
|
+
null,
|
|
1567
|
+
{ id: documentId, _meta: fullDocument?._meta, ...documentData },
|
|
1568
|
+
0
|
|
1569
|
+
)}
|
|
1287
1570
|
{/if}
|
|
1288
1571
|
</div>
|
|
1289
1572
|
</div>
|
|
@@ -1303,12 +1586,17 @@
|
|
|
1303
1586
|
{/if}
|
|
1304
1587
|
{/if}
|
|
1305
1588
|
{#if Array.isArray(val)}
|
|
1306
|
-
<span class="text-muted-foreground"
|
|
1589
|
+
<span class="text-muted-foreground"
|
|
1590
|
+
>[...] {val.length} {val.length === 1 ? 'item' : 'items'}</span
|
|
1591
|
+
>
|
|
1307
1592
|
{:else}
|
|
1308
|
-
<span class="text-muted-foreground"
|
|
1593
|
+
<span class="text-muted-foreground"
|
|
1594
|
+
>{...} {Object.keys(val).length}
|
|
1595
|
+
{Object.keys(val).length === 1 ? 'property' : 'properties'}</span
|
|
1596
|
+
>
|
|
1309
1597
|
{/if}
|
|
1310
1598
|
</summary>
|
|
1311
|
-
<div class="ml-4 border-l
|
|
1599
|
+
<div class="border-rule/50 ml-4 border-l pl-3">
|
|
1312
1600
|
{#if Array.isArray(val)}
|
|
1313
1601
|
{#each val as item, i}
|
|
1314
1602
|
{@render parsedValue(String(i), item, depth + 1)}
|