fix: lock documents using the live-preview view (#8343)
Updates: - Exports `handleGoBack`, `handleBackToDashboard`, & `handleTakeOver` functions to consolidate logic in default edit view & live-preview edit view. - Only unlock document on navigation away from edit view entirely (aka do not unlock document if switching between tabs like `edit` --> `live-preview` --> `versions` --> `api`
This commit is contained in:
@@ -17,7 +17,13 @@ import {
|
||||
useEditDepth,
|
||||
useUploadEdits,
|
||||
} from '@payloadcms/ui'
|
||||
import { formatAdminURL, getFormState } from '@payloadcms/ui/shared'
|
||||
import {
|
||||
formatAdminURL,
|
||||
getFormState,
|
||||
handleBackToDashboard,
|
||||
handleGoBack,
|
||||
handleTakeOver,
|
||||
} from '@payloadcms/ui/shared'
|
||||
import { useRouter, useSearchParams } from 'next/navigation.js'
|
||||
import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react'
|
||||
|
||||
@@ -151,89 +157,6 @@ export const DefaultEditView: React.FC = () => {
|
||||
return false
|
||||
})
|
||||
|
||||
const handleTakeOver = useCallback(() => {
|
||||
if (!isLockingEnabled) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// Call updateDocumentEditor to update the document's owner to the current user
|
||||
void updateDocumentEditor(id, collectionSlug ?? globalSlug, user)
|
||||
|
||||
documentLockStateRef.current.hasShownLockedModal = true
|
||||
|
||||
// Update the locked state to reflect the current user as the owner
|
||||
documentLockStateRef.current = {
|
||||
hasShownLockedModal: documentLockStateRef.current?.hasShownLockedModal,
|
||||
isLocked: true,
|
||||
user,
|
||||
}
|
||||
setCurrentEditor(user)
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Error during document takeover:', error)
|
||||
}
|
||||
}, [
|
||||
updateDocumentEditor,
|
||||
id,
|
||||
collectionSlug,
|
||||
globalSlug,
|
||||
user,
|
||||
setCurrentEditor,
|
||||
isLockingEnabled,
|
||||
])
|
||||
|
||||
const handleTakeOverWithinDoc = useCallback(() => {
|
||||
if (!isLockingEnabled) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// Call updateDocumentEditor to update the document's owner to the current user
|
||||
void updateDocumentEditor(id, collectionSlug ?? globalSlug, user)
|
||||
|
||||
// Update the locked state to reflect the current user as the owner
|
||||
documentLockStateRef.current = {
|
||||
hasShownLockedModal: documentLockStateRef.current?.hasShownLockedModal,
|
||||
isLocked: true,
|
||||
user,
|
||||
}
|
||||
setCurrentEditor(user)
|
||||
|
||||
// Ensure the document is editable for the incoming user
|
||||
setIsReadOnlyForIncomingUser(false)
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Error during document takeover:', error)
|
||||
}
|
||||
}, [
|
||||
updateDocumentEditor,
|
||||
id,
|
||||
collectionSlug,
|
||||
globalSlug,
|
||||
user,
|
||||
setCurrentEditor,
|
||||
isLockingEnabled,
|
||||
])
|
||||
|
||||
const handleGoBack = useCallback(() => {
|
||||
const redirectRoute = formatAdminURL({
|
||||
adminRoute,
|
||||
path: collectionSlug ? `/collections/${collectionSlug}` : '/',
|
||||
})
|
||||
router.push(redirectRoute)
|
||||
}, [adminRoute, collectionSlug, router])
|
||||
|
||||
const handleBackToDashboard = useCallback(() => {
|
||||
setShowTakeOverModal(false)
|
||||
const redirectRoute = formatAdminURL({
|
||||
adminRoute,
|
||||
path: '/',
|
||||
})
|
||||
|
||||
router.push(redirectRoute)
|
||||
}, [adminRoute, router])
|
||||
|
||||
const onSave = useCallback(
|
||||
(json) => {
|
||||
reportUpdate({
|
||||
@@ -373,7 +296,19 @@ export const DefaultEditView: React.FC = () => {
|
||||
return
|
||||
}
|
||||
|
||||
if ((id || globalSlug) && documentIsLocked) {
|
||||
const currentPath = window.location.pathname
|
||||
|
||||
const documentId = id || globalSlug
|
||||
|
||||
// Routes where we do NOT want to unlock the document
|
||||
const stayWithinDocumentPaths = ['preview', 'api', 'versions']
|
||||
|
||||
const isStayingWithinDocument = stayWithinDocumentPaths.some((path) =>
|
||||
currentPath.includes(path),
|
||||
)
|
||||
|
||||
// Unlock the document only if we're actually navigating away from the document
|
||||
if (documentId && documentIsLocked && !isStayingWithinDocument) {
|
||||
// Check if this user is still the current editor
|
||||
if (documentLockStateRef.current?.user?.id === user.id) {
|
||||
void unlockDocument(id, collectionSlug ?? globalSlug)
|
||||
@@ -421,20 +356,32 @@ export const DefaultEditView: React.FC = () => {
|
||||
{BeforeDocument}
|
||||
{isLockingEnabled && shouldShowDocumentLockedModal && !isReadOnlyForIncomingUser && (
|
||||
<DocumentLocked
|
||||
handleGoBack={handleGoBack}
|
||||
handleGoBack={() => handleGoBack({ adminRoute, collectionSlug, router })}
|
||||
isActive={shouldShowDocumentLockedModal}
|
||||
onReadOnly={() => {
|
||||
setIsReadOnlyForIncomingUser(true)
|
||||
setShowTakeOverModal(false)
|
||||
}}
|
||||
onTakeOver={handleTakeOver}
|
||||
onTakeOver={() =>
|
||||
handleTakeOver(
|
||||
id,
|
||||
collectionSlug,
|
||||
globalSlug,
|
||||
user,
|
||||
false,
|
||||
updateDocumentEditor,
|
||||
setCurrentEditor,
|
||||
documentLockStateRef,
|
||||
isLockingEnabled,
|
||||
)
|
||||
}
|
||||
updatedAt={lastUpdateTime}
|
||||
user={currentEditor}
|
||||
/>
|
||||
)}
|
||||
{isLockingEnabled && showTakeOverModal && (
|
||||
<DocumentTakeOver
|
||||
handleBackToDashboard={handleBackToDashboard}
|
||||
handleBackToDashboard={() => handleBackToDashboard({ adminRoute, router })}
|
||||
isActive={showTakeOverModal}
|
||||
onReadOnly={() => {
|
||||
setIsReadOnlyForIncomingUser(true)
|
||||
@@ -469,7 +416,20 @@ export const DefaultEditView: React.FC = () => {
|
||||
onDrawerCreate={onDrawerCreate}
|
||||
onDuplicate={onDuplicate}
|
||||
onSave={onSave}
|
||||
onTakeOver={handleTakeOverWithinDoc}
|
||||
onTakeOver={() =>
|
||||
handleTakeOver(
|
||||
id,
|
||||
collectionSlug,
|
||||
globalSlug,
|
||||
user,
|
||||
true,
|
||||
updateDocumentEditor,
|
||||
setCurrentEditor,
|
||||
documentLockStateRef,
|
||||
isLockingEnabled,
|
||||
setIsReadOnlyForIncomingUser,
|
||||
)
|
||||
}
|
||||
permissions={docPermissions}
|
||||
readOnlyForIncomingUser={isReadOnlyForIncomingUser}
|
||||
redirectAfterDelete={redirectAfterDelete}
|
||||
@@ -494,6 +454,7 @@ export const DefaultEditView: React.FC = () => {
|
||||
requirePassword={!id}
|
||||
setSchemaPath={setSchemaPath}
|
||||
setValidateBeforeSubmit={setValidateBeforeSubmit}
|
||||
// eslint-disable-next-line react-compiler/react-compiler
|
||||
useAPIKey={auth.useAPIKey}
|
||||
username={data?.username}
|
||||
verify={auth.verify}
|
||||
|
||||
@@ -5,6 +5,7 @@ import type {
|
||||
ClientConfig,
|
||||
ClientField,
|
||||
ClientGlobalConfig,
|
||||
ClientUser,
|
||||
Data,
|
||||
LivePreviewConfig,
|
||||
} from 'payload'
|
||||
@@ -21,9 +22,17 @@ import {
|
||||
useDocumentInfo,
|
||||
useTranslation,
|
||||
} from '@payloadcms/ui'
|
||||
import { getFormState } from '@payloadcms/ui/shared'
|
||||
import React, { Fragment, useCallback } from 'react'
|
||||
import {
|
||||
getFormState,
|
||||
handleBackToDashboard,
|
||||
handleGoBack,
|
||||
handleTakeOver,
|
||||
} from '@payloadcms/ui/shared'
|
||||
import { useRouter } from 'next/navigation.js'
|
||||
import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react'
|
||||
|
||||
import { DocumentLocked } from '../../elements/DocumentLocked/index.js'
|
||||
import { DocumentTakeOver } from '../../elements/DocumentTakeOver/index.js'
|
||||
import { LeaveWithoutSaving } from '../../elements/LeaveWithoutSaving/index.js'
|
||||
import { SetDocumentStepNav } from '../Edit/Default/SetDocumentStepNav/index.js'
|
||||
import { SetDocumentTitle } from '../Edit/Default/SetDocumentTitle/index.js'
|
||||
@@ -63,9 +72,11 @@ const PreviewView: React.FC<Props> = ({
|
||||
BeforeDocument,
|
||||
BeforeFields,
|
||||
collectionSlug,
|
||||
currentEditor,
|
||||
disableActions,
|
||||
disableLeaveWithoutSaving,
|
||||
docPermissions,
|
||||
documentIsLocked,
|
||||
getDocPreferences,
|
||||
globalSlug,
|
||||
hasPublishPermission,
|
||||
@@ -75,6 +86,10 @@ const PreviewView: React.FC<Props> = ({
|
||||
isEditing,
|
||||
isInitializing,
|
||||
onSave: onSaveFromProps,
|
||||
setCurrentEditor,
|
||||
setDocumentIsLocked,
|
||||
unlockDocument,
|
||||
updateDocumentEditor,
|
||||
} = useDocumentInfo()
|
||||
|
||||
const operation = id ? 'update' : 'create'
|
||||
@@ -82,13 +97,36 @@ const PreviewView: React.FC<Props> = ({
|
||||
const {
|
||||
config: {
|
||||
admin: { user: userSlug },
|
||||
routes: { admin: adminRoute },
|
||||
},
|
||||
} = useConfig()
|
||||
const router = useRouter()
|
||||
const { t } = useTranslation()
|
||||
const { previewWindowType } = useLivePreviewContext()
|
||||
const { refreshCookieAsync, user } = useAuth()
|
||||
const { reportUpdate } = useDocumentEvents()
|
||||
|
||||
const docConfig = collectionConfig || globalConfig
|
||||
|
||||
const lockDocumentsProp = docConfig?.lockDocuments !== undefined ? docConfig?.lockDocuments : true
|
||||
|
||||
const isLockingEnabled = lockDocumentsProp !== false
|
||||
|
||||
const [isReadOnlyForIncomingUser, setIsReadOnlyForIncomingUser] = useState(false)
|
||||
const [showTakeOverModal, setShowTakeOverModal] = useState(false)
|
||||
|
||||
const documentLockStateRef = useRef<{
|
||||
hasShownLockedModal: boolean
|
||||
isLocked: boolean
|
||||
user: ClientUser
|
||||
} | null>({
|
||||
hasShownLockedModal: false,
|
||||
isLocked: false,
|
||||
user: null,
|
||||
})
|
||||
|
||||
const [lastUpdateTime, setLastUpdateTime] = useState(Date.now())
|
||||
|
||||
const onSave = useCallback(
|
||||
(json) => {
|
||||
reportUpdate({
|
||||
@@ -103,6 +141,11 @@ const PreviewView: React.FC<Props> = ({
|
||||
void refreshCookieAsync()
|
||||
}
|
||||
|
||||
// Unlock the document after save
|
||||
if ((id || globalSlug) && isLockingEnabled) {
|
||||
setDocumentIsLocked(false)
|
||||
}
|
||||
|
||||
if (typeof onSaveFromProps === 'function') {
|
||||
void onSaveFromProps({
|
||||
...json,
|
||||
@@ -110,47 +153,194 @@ const PreviewView: React.FC<Props> = ({
|
||||
})
|
||||
}
|
||||
},
|
||||
[collectionSlug, id, onSaveFromProps, refreshCookieAsync, reportUpdate, user, userSlug],
|
||||
[
|
||||
collectionSlug,
|
||||
globalSlug,
|
||||
id,
|
||||
isLockingEnabled,
|
||||
onSaveFromProps,
|
||||
refreshCookieAsync,
|
||||
reportUpdate,
|
||||
setDocumentIsLocked,
|
||||
user,
|
||||
userSlug,
|
||||
],
|
||||
)
|
||||
|
||||
const onChange: FormProps['onChange'][0] = useCallback(
|
||||
async ({ formState: prevFormState }) => {
|
||||
const currentTime = Date.now()
|
||||
const timeSinceLastUpdate = currentTime - lastUpdateTime
|
||||
|
||||
const updateLastEdited = isLockingEnabled && timeSinceLastUpdate >= 10000 // 10 seconds
|
||||
|
||||
if (updateLastEdited) {
|
||||
setLastUpdateTime(currentTime)
|
||||
}
|
||||
|
||||
const docPreferences = await getDocPreferences()
|
||||
|
||||
const { state } = await getFormState({
|
||||
const { lockedState, state } = await getFormState({
|
||||
apiRoute,
|
||||
body: {
|
||||
id,
|
||||
collectionSlug,
|
||||
docPreferences,
|
||||
formState: prevFormState,
|
||||
globalSlug,
|
||||
operation,
|
||||
returnLockStatus: isLockingEnabled ? true : false,
|
||||
schemaPath,
|
||||
updateLastEdited,
|
||||
},
|
||||
serverURL,
|
||||
})
|
||||
|
||||
setDocumentIsLocked(true)
|
||||
|
||||
if (isLockingEnabled) {
|
||||
const previousOwnerId = documentLockStateRef.current?.user?.id
|
||||
|
||||
if (lockedState) {
|
||||
if (!documentLockStateRef.current || lockedState.user.id !== previousOwnerId) {
|
||||
if (previousOwnerId === user.id && lockedState.user.id !== user.id) {
|
||||
setShowTakeOverModal(true)
|
||||
documentLockStateRef.current.hasShownLockedModal = true
|
||||
}
|
||||
|
||||
documentLockStateRef.current = documentLockStateRef.current = {
|
||||
hasShownLockedModal: documentLockStateRef.current?.hasShownLockedModal || false,
|
||||
isLocked: true,
|
||||
user: lockedState.user,
|
||||
}
|
||||
setCurrentEditor(lockedState.user)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return state
|
||||
},
|
||||
[serverURL, apiRoute, id, operation, schemaPath, getDocPreferences],
|
||||
[
|
||||
collectionSlug,
|
||||
globalSlug,
|
||||
serverURL,
|
||||
apiRoute,
|
||||
id,
|
||||
isLockingEnabled,
|
||||
lastUpdateTime,
|
||||
operation,
|
||||
schemaPath,
|
||||
getDocPreferences,
|
||||
setCurrentEditor,
|
||||
setDocumentIsLocked,
|
||||
user,
|
||||
],
|
||||
)
|
||||
|
||||
// Clean up when the component unmounts or when the document is unlocked
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (!isLockingEnabled) {
|
||||
return
|
||||
}
|
||||
|
||||
const currentPath = window.location.pathname
|
||||
|
||||
const documentId = id || globalSlug
|
||||
|
||||
// Routes where we do NOT want to unlock the document
|
||||
const stayWithinDocumentPaths = ['preview', 'api', 'versions']
|
||||
|
||||
const isStayingWithinDocument = stayWithinDocumentPaths.some((path) =>
|
||||
currentPath.includes(path),
|
||||
)
|
||||
|
||||
// Unlock the document only if we're actually navigating away from the document
|
||||
if (documentId && documentIsLocked && !isStayingWithinDocument) {
|
||||
// Check if this user is still the current editor
|
||||
if (documentLockStateRef.current?.user?.id === user.id) {
|
||||
void unlockDocument(id, collectionSlug ?? globalSlug)
|
||||
setDocumentIsLocked(false)
|
||||
setCurrentEditor(null)
|
||||
}
|
||||
}
|
||||
|
||||
setShowTakeOverModal(false)
|
||||
}
|
||||
}, [
|
||||
collectionSlug,
|
||||
globalSlug,
|
||||
id,
|
||||
unlockDocument,
|
||||
user.id,
|
||||
setCurrentEditor,
|
||||
isLockingEnabled,
|
||||
documentIsLocked,
|
||||
setDocumentIsLocked,
|
||||
])
|
||||
|
||||
const shouldShowDocumentLockedModal =
|
||||
documentIsLocked &&
|
||||
currentEditor &&
|
||||
currentEditor.id !== user.id &&
|
||||
!isReadOnlyForIncomingUser &&
|
||||
!showTakeOverModal &&
|
||||
// eslint-disable-next-line react-compiler/react-compiler
|
||||
!documentLockStateRef.current?.hasShownLockedModal
|
||||
|
||||
return (
|
||||
<OperationProvider operation={operation}>
|
||||
<Form
|
||||
action={action}
|
||||
className={`${baseClass}__form`}
|
||||
disabled={!hasSavePermission}
|
||||
disabled={isReadOnlyForIncomingUser || !hasSavePermission}
|
||||
initialState={initialState}
|
||||
isInitializing={isInitializing}
|
||||
method={id ? 'PATCH' : 'POST'}
|
||||
onChange={[onChange]}
|
||||
onSuccess={onSave}
|
||||
>
|
||||
{isLockingEnabled && shouldShowDocumentLockedModal && !isReadOnlyForIncomingUser && (
|
||||
<DocumentLocked
|
||||
handleGoBack={() => handleGoBack({ adminRoute, collectionSlug, router })}
|
||||
isActive={shouldShowDocumentLockedModal}
|
||||
onReadOnly={() => {
|
||||
setIsReadOnlyForIncomingUser(true)
|
||||
setShowTakeOverModal(false)
|
||||
}}
|
||||
onTakeOver={() =>
|
||||
handleTakeOver(
|
||||
id,
|
||||
collectionSlug,
|
||||
globalSlug,
|
||||
user,
|
||||
false,
|
||||
updateDocumentEditor,
|
||||
setCurrentEditor,
|
||||
documentLockStateRef,
|
||||
isLockingEnabled,
|
||||
)
|
||||
}
|
||||
updatedAt={lastUpdateTime}
|
||||
user={currentEditor}
|
||||
/>
|
||||
)}
|
||||
{isLockingEnabled && showTakeOverModal && (
|
||||
<DocumentTakeOver
|
||||
handleBackToDashboard={() => handleBackToDashboard({ adminRoute, router })}
|
||||
isActive={showTakeOverModal}
|
||||
onReadOnly={() => {
|
||||
setIsReadOnlyForIncomingUser(true)
|
||||
setShowTakeOverModal(false)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{((collectionConfig &&
|
||||
!(collectionConfig.versions?.drafts && collectionConfig.versions?.drafts?.autosave)) ||
|
||||
(globalConfig &&
|
||||
!(globalConfig.versions?.drafts && globalConfig.versions?.drafts?.autosave))) &&
|
||||
!disableLeaveWithoutSaving && <LeaveWithoutSaving />}
|
||||
!disableLeaveWithoutSaving &&
|
||||
!isReadOnlyForIncomingUser && <LeaveWithoutSaving />}
|
||||
<SetDocumentStepNav
|
||||
collectionSlug={collectionSlug}
|
||||
globalLabel={globalConfig?.label}
|
||||
@@ -174,8 +364,24 @@ const PreviewView: React.FC<Props> = ({
|
||||
hasSavePermission={hasSavePermission}
|
||||
id={id}
|
||||
isEditing={isEditing}
|
||||
onTakeOver={() =>
|
||||
handleTakeOver(
|
||||
id,
|
||||
collectionSlug,
|
||||
globalSlug,
|
||||
user,
|
||||
true,
|
||||
updateDocumentEditor,
|
||||
setCurrentEditor,
|
||||
documentLockStateRef,
|
||||
isLockingEnabled,
|
||||
setIsReadOnlyForIncomingUser,
|
||||
)
|
||||
}
|
||||
permissions={docPermissions}
|
||||
readOnlyForIncomingUser={isReadOnlyForIncomingUser}
|
||||
slug={collectionConfig?.slug || globalConfig?.slug}
|
||||
user={currentEditor}
|
||||
/>
|
||||
<div
|
||||
className={[baseClass, previewWindowType === 'popup' && `${baseClass}--detached`]
|
||||
@@ -197,7 +403,7 @@ const PreviewView: React.FC<Props> = ({
|
||||
docPermissions={docPermissions}
|
||||
fields={fields}
|
||||
forceSidebarWrap
|
||||
readOnly={!hasSavePermission}
|
||||
readOnly={isReadOnlyForIncomingUser || !hasSavePermission}
|
||||
schemaPath={collectionSlug || globalSlug}
|
||||
/>
|
||||
{AfterDocument}
|
||||
|
||||
@@ -235,7 +235,7 @@ export const DocumentControls: React.FC<{
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
id="take-over"
|
||||
onClick={() => void onTakeOver()}
|
||||
onClick={onTakeOver}
|
||||
size="medium"
|
||||
type="button"
|
||||
>
|
||||
|
||||
@@ -20,5 +20,8 @@ export {
|
||||
type Group,
|
||||
groupNavItems,
|
||||
} from '../../utilities/groupNavItems.js'
|
||||
export { handleBackToDashboard } from '../../utilities/handleBackToDashboard.js'
|
||||
export { handleGoBack } from '../../utilities/handleGoBack.js'
|
||||
export { handleTakeOver } from '../../utilities/handleTakeOver.js'
|
||||
export { hasSavePermission } from '../../utilities/hasSavePermission.js'
|
||||
export { isEditing } from '../../utilities/isEditing.js'
|
||||
|
||||
16
packages/ui/src/utilities/handleBackToDashboard.tsx
Normal file
16
packages/ui/src/utilities/handleBackToDashboard.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import type { AppRouterInstance } from 'next/dist/shared/lib/app-router-context.shared-runtime.js'
|
||||
|
||||
import { formatAdminURL } from './formatAdminURL.js'
|
||||
|
||||
type BackToDashboardProps = {
|
||||
adminRoute: string
|
||||
router: AppRouterInstance
|
||||
}
|
||||
|
||||
export const handleBackToDashboard = ({ adminRoute, router }: BackToDashboardProps) => {
|
||||
const redirectRoute = formatAdminURL({
|
||||
adminRoute,
|
||||
path: '/',
|
||||
})
|
||||
router.push(redirectRoute)
|
||||
}
|
||||
17
packages/ui/src/utilities/handleGoBack.tsx
Normal file
17
packages/ui/src/utilities/handleGoBack.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { AppRouterInstance } from 'next/dist/shared/lib/app-router-context.shared-runtime.js'
|
||||
|
||||
import { formatAdminURL } from './formatAdminURL.js'
|
||||
|
||||
type GoBackProps = {
|
||||
adminRoute: string
|
||||
collectionSlug: string
|
||||
router: AppRouterInstance
|
||||
}
|
||||
|
||||
export const handleGoBack = ({ adminRoute, collectionSlug, router }: GoBackProps) => {
|
||||
const redirectRoute = formatAdminURL({
|
||||
adminRoute,
|
||||
path: collectionSlug ? `/collections/${collectionSlug}` : '/',
|
||||
})
|
||||
router.push(redirectRoute)
|
||||
}
|
||||
47
packages/ui/src/utilities/handleTakeOver.tsx
Normal file
47
packages/ui/src/utilities/handleTakeOver.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import type { ClientUser } from 'payload'
|
||||
|
||||
export const handleTakeOver = (
|
||||
id: number | string,
|
||||
collectionSlug: string,
|
||||
globalSlug: string,
|
||||
user: ClientUser,
|
||||
isWithinDoc: boolean,
|
||||
updateDocumentEditor: (docId: number | string, slug: string, user: ClientUser) => Promise<void>,
|
||||
setCurrentEditor: (value: React.SetStateAction<ClientUser>) => void,
|
||||
documentLockStateRef: React.RefObject<{
|
||||
hasShownLockedModal: boolean
|
||||
isLocked: boolean
|
||||
user: ClientUser
|
||||
}>,
|
||||
isLockingEnabled: boolean,
|
||||
setIsReadOnlyForIncomingUser?: (value: React.SetStateAction<boolean>) => void,
|
||||
): void => {
|
||||
if (!isLockingEnabled) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// Call updateDocumentEditor to update the document's owner to the current user
|
||||
void updateDocumentEditor(id, collectionSlug ?? globalSlug, user)
|
||||
|
||||
if (!isWithinDoc) {
|
||||
documentLockStateRef.current.hasShownLockedModal = true
|
||||
}
|
||||
|
||||
// Update the locked state to reflect the current user as the owner
|
||||
documentLockStateRef.current = {
|
||||
hasShownLockedModal: documentLockStateRef.current?.hasShownLockedModal,
|
||||
isLocked: true,
|
||||
user,
|
||||
}
|
||||
setCurrentEditor(user)
|
||||
|
||||
// If this is a takeover within the document, ensure the document is editable
|
||||
if (isWithinDoc && setIsReadOnlyForIncomingUser) {
|
||||
setIsReadOnlyForIncomingUser(false)
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Error during document takeover:', error)
|
||||
}
|
||||
}
|
||||
@@ -292,6 +292,51 @@ describe('locked documents', () => {
|
||||
|
||||
expect(unlockedDocs.docs.length).toBe(0)
|
||||
})
|
||||
|
||||
test('should keep document locked when navigating to other tabs i.e. api', async () => {
|
||||
await page.goto(postsUrl.edit(postDoc.id))
|
||||
await page.waitForURL(postsUrl.edit(postDoc.id))
|
||||
|
||||
const textInput = page.locator('#field-text')
|
||||
await textInput.fill('testing tab navigation...')
|
||||
|
||||
// eslint-disable-next-line payload/no-wait-function
|
||||
await wait(500)
|
||||
|
||||
const lockedDocs = await payload.find({
|
||||
collection: lockedDocumentCollection,
|
||||
limit: 1,
|
||||
pagination: false,
|
||||
where: {
|
||||
'document.value': { equals: postDoc.id },
|
||||
},
|
||||
})
|
||||
|
||||
expect(lockedDocs.docs.length).toBe(1)
|
||||
|
||||
await page.locator('li[aria-label="API"] a').click()
|
||||
|
||||
// Locate the modal container
|
||||
const modalContainer = page.locator('.payload__modal-container')
|
||||
await expect(modalContainer).toBeVisible()
|
||||
|
||||
// Click the "Leave anyway" button
|
||||
await page.locator('.leave-without-saving__controls .btn--style-primary').click()
|
||||
|
||||
// eslint-disable-next-line payload/no-wait-function
|
||||
await wait(500)
|
||||
|
||||
const unlockedDocs = await payload.find({
|
||||
collection: lockedDocumentCollection,
|
||||
limit: 1,
|
||||
pagination: false,
|
||||
where: {
|
||||
'document.value': { equals: postDoc.id },
|
||||
},
|
||||
})
|
||||
|
||||
expect(unlockedDocs.docs.length).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('document locking - incoming user', () => {
|
||||
|
||||
Reference in New Issue
Block a user