diff --git a/packages/next/src/views/Document/getIsLocked.ts b/packages/next/src/views/Document/getIsLocked.ts index e88d813b4..695a54893 100644 --- a/packages/next/src/views/Document/getIsLocked.ts +++ b/packages/next/src/views/Document/getIsLocked.ts @@ -42,10 +42,28 @@ export const getIsLocked = async ({ const where: Where = {} + const lockDurationDefault = 300 // Default 5 minutes in seconds + const lockDuration = + typeof entityConfig.lockDocuments === 'object' + ? entityConfig.lockDocuments.duration + : lockDurationDefault + const lockDurationInMilliseconds = lockDuration * 1000 + + const now = new Date().getTime() + if (globalConfig) { - where.globalSlug = { - equals: globalConfig.slug, - } + where.and = [ + { + globalSlug: { + equals: globalConfig.slug, + }, + }, + { + updatedAt: { + greater_than: new Date(now - lockDurationInMilliseconds), + }, + }, + ] } else { where.and = [ { @@ -58,6 +76,11 @@ export const getIsLocked = async ({ equals: collectionConfig.slug, }, }, + { + updatedAt: { + greater_than: new Date(now - lockDurationInMilliseconds), + }, + }, ] } diff --git a/test/locked-documents/e2e.spec.ts b/test/locked-documents/e2e.spec.ts index 8ca1030f7..585f4cefa 100644 --- a/test/locked-documents/e2e.spec.ts +++ b/test/locked-documents/e2e.spec.ts @@ -630,6 +630,8 @@ describe('Locked Documents', () => { let lockedDoc: PayloadLockedDocument let expiredTestDoc: Test let expiredTestLockedDoc: PayloadLockedDocument + let expiredPostDoc: Post + let expiredPostLockedDoc: PayloadLockedDocument beforeAll(async () => { postDoc = await createPostDoc({ @@ -678,6 +680,27 @@ describe('Locked Documents', () => { }, }, }) + + expiredPostDoc = await createPostDoc({ + text: 'expired post doc', + }) + + expiredPostLockedDoc = await payload.create({ + collection: lockedDocumentCollection, + data: { + document: { + relationTo: 'posts', + value: expiredPostDoc.id, + }, + globalSlug: undefined, + user: { + relationTo: 'users', + value: user2.id, + }, + createdAt: new Date(Date.now() - 1000 * 60 * 60).toISOString(), + updatedAt: new Date(Date.now() - 1000 * 60 * 60).toISOString(), + }, + }) }) afterAll(async () => { @@ -705,6 +728,16 @@ describe('Locked Documents', () => { collection: 'tests', id: expiredTestDoc.id, }) + + await payload.delete({ + collection: lockedDocumentCollection, + id: expiredPostLockedDoc.id, + }) + + await payload.delete({ + collection: 'posts', + id: expiredPostDoc.id, + }) }) test('should show Document Locked modal for incoming user when entering locked document', async () => { @@ -739,6 +772,20 @@ describe('Locked Documents', () => { await expect(modalContainer).toBeHidden() }) + test('expired lock should render editable fields (no read-only)', async () => { + await page.goto(postsUrl.edit(expiredPostDoc.id)) + + await expect(page.locator('#field-text')).toBeEnabled() + + const richTextRoot = page + .locator('.rich-text-lexical .ContentEditable__root[data-lexical-editor="true"]') + .first() + await expect(richTextRoot).toBeVisible() + + // ensure richtext is editable + await expect(richTextRoot).toHaveAttribute('contenteditable', 'true') + }) + test('should show fields in read-only if incoming user views locked doc in read-only mode', async () => { await page.goto(postsUrl.edit(postDoc.id))