diff --git a/packages/sanity/src/core/store/_legacy/document/document-pair/utils/isLiveEditEnabled.ts b/packages/sanity/src/core/store/_legacy/document/document-pair/utils/isLiveEditEnabled.ts index e5f33f898a6..4c30024c467 100644 --- a/packages/sanity/src/core/store/_legacy/document/document-pair/utils/isLiveEditEnabled.ts +++ b/packages/sanity/src/core/store/_legacy/document/document-pair/utils/isLiveEditEnabled.ts @@ -1,4 +1,5 @@ import {type Schema} from '@sanity/types' export const isLiveEditEnabled = (schema: Schema, typeName: string): boolean => + (globalThis as any).__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED_LIVE_EDIT_OVERRIDE ?? schema.get(typeName)?.liveEdit === true diff --git a/packages/sanity/src/structure/panes/document/DocumentPaneProvider.tsx b/packages/sanity/src/structure/panes/document/DocumentPaneProvider.tsx index ae9047f36e6..ac36e4cbcc9 100644 --- a/packages/sanity/src/structure/panes/document/DocumentPaneProvider.tsx +++ b/packages/sanity/src/structure/panes/document/DocumentPaneProvider.tsx @@ -11,6 +11,8 @@ import {fromString as pathFromString, resolveKeyedPath} from '@sanity/util/paths import {omit, throttle} from 'lodash' import {memo, useCallback, useEffect, useMemo, useRef, useState} from 'react' import deepEquals from 'react-fast-compare' +import {useObservable} from 'react-rx' +import {map} from 'rxjs' import { type DocumentFieldAction, type DocumentInspector, @@ -26,6 +28,7 @@ import { toMutationPatches, useConnectionState, useDocumentOperation, + useDocumentStore, useDocumentValuePermissions, useEditState, useFormState, @@ -43,6 +46,8 @@ import { import {DocumentPaneContext} from 'sanity/_singletons' import {useRouter} from 'sanity/router' +// eslint-disable-next-line +import {type DraftsModelDocumentAvailability} from '../../../core/preview' import {usePaneRouter} from '../../components' import {structureLocaleNamespace} from '../../i18n' import {type PaneMenuItem} from '../../types' @@ -82,18 +87,41 @@ export const DocumentPaneProvider = memo((props: DocumentPaneProviderProps) => { const setPaneParams = paneRouter.setParams const {features} = useStructureTool() const {push: pushToast} = useToast() + const documentStore = useDocumentStore() const { options, menuItemGroups = DEFAULT_MENU_ITEM_GROUPS, title = null, views: viewsProp = [], } = pane + const perspective = stickyParams.perspective + const bundle = perspective?.startsWith('bundle.') ? perspective.split('bundle.').at(1) : undefined const paneOptions = useUnique(options) const documentIdRaw = paneOptions.id - const documentId = getPublishedId(documentIdRaw) + const [documentId, setDocumentId] = useState( + bundle ? [bundle, getPublishedId(documentIdRaw)].join('.') : getPublishedId(documentIdRaw), + ) + const documentType = options.type const params = useUnique(paneRouter.params) || EMPTY_PARAMS const panePayload = useUnique(paneRouter.payload) + + const versionExists$ = useMemo( + () => + documentStore + .listenQuery( + '*[_id == $versionId][0]._id', + { + versionId: [bundle, getPublishedId(documentIdRaw)].join('.'), + }, + {}, + ) + .pipe(map((id) => id !== null)), + [documentStore, documentIdRaw, bundle], + ) + + const versionExists = useObservable(versionExists$) + const {templateName, templateParams} = useMemo( () => getInitialValueTemplateOpts(templates, { @@ -112,17 +140,41 @@ export const DocumentPaneProvider = memo((props: DocumentPaneProviderProps) => { templateParams, }) + const [liveEdit, setLiveEdit] = useState(false) const initialValue = useUnique(initialValueRaw) const {patch} = useDocumentOperation(documentId, documentType) - const editState = useEditState(documentId, documentType) + const schemaType = schema.get(documentType) as ObjectSchemaType | undefined + const editState = useEditState(documentId, documentType, 'default') const {validation: validationRaw} = useValidationStatus(documentId, documentType) const connectionState = useConnectionState(documentId, documentType) - const schemaType = schema.get(documentType) as ObjectSchemaType | undefined - const perspective = stickyParams.perspective + // When a bundle is checked out and the document being viewed either comes into existence or is + // removed from the bundle, switch to the version or the default document accordingly. + useEffect(() => { + setDocumentId( + bundle && versionExists + ? [bundle, getPublishedId(documentIdRaw)].join('.') + : getPublishedId(documentIdRaw), + ) + }, [bundle, versionExists, documentIdRaw]) + + // When a bundle is checked out and the document being viewed exists in that bundle, enable + // live editing. + useEffect(() => { + const isEnabled = Boolean(schemaType?.liveEdit || versionExists) + + setLiveEdit(isEnabled) + ;(globalThis as any).__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED_LIVE_EDIT_OVERRIDE = + isEnabled + + return () => { + ;(globalThis as any).__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED_LIVE_EDIT_OVERRIDE = + undefined + } + }, [schemaType?.liveEdit, versionExists]) const value: SanityDocumentLike = - (perspective === 'published' + (perspective === 'published' && !bundle ? editState.published || editState.draft : editState?.draft || editState?.published) || initialValue.value const [isDeleting, setIsDeleting] = useState(false) @@ -272,7 +324,10 @@ export const DocumentPaneProvider = memo((props: DocumentPaneProviderProps) => { * a timeline revision in this instance will display an error localized to the popover itself. */ const ready = - connectionState === 'connected' && editState.ready && (timelineReady || !!timelineError) + connectionState === 'connected' && + editState.ready && + (timelineReady || !!timelineError) && + typeof versionExists === 'boolean' const displayed: Partial | undefined = useMemo( () => (onOlderRevision ? timelineDisplayed || {_id: value._id, _type: value._type} : value), @@ -491,7 +546,6 @@ export const DocumentPaneProvider = memo((props: DocumentPaneProviderProps) => { ) const requiredPermission = value._createdAt ? 'update' : 'create' - const liveEdit = Boolean(schemaType?.liveEdit) const docId = value._id ? value._id : 'dummy-id' const docPermissionsInput = useMemo(() => { return { @@ -515,7 +569,7 @@ export const DocumentPaneProvider = memo((props: DocumentPaneProviderProps) => { const isLocked = editState.transactionSyncLock?.enabled return ( - !!perspective || + (!!perspective && !bundle) || !ready || revTime !== null || hasNoPermission || @@ -538,6 +592,7 @@ export const DocumentPaneProvider = memo((props: DocumentPaneProviderProps) => { revTime, isDeleting, isDeleted, + bundle, ]) const formState = useFormState(schemaType!, {