fix(next): richtext field is read-only for expired lock (#13789)

### What?

Opening a document with an expired lock and richtext caused the richtext
to be read-only.

### Why?

The changes in #13579 made the richtext read only if isLocked is set.
But the server side implementation of isLocked did not consider expired
locks.

### How?

Update the server-side getIsLocked to also consider expired locks by not
loading them.
This commit is contained in:
Anders Semb Hermansen
2025-09-12 16:46:51 +02:00
committed by GitHub
parent 3c5aa1bdbd
commit 4278e724f5
2 changed files with 73 additions and 3 deletions

View File

@@ -42,10 +42,28 @@ export const getIsLocked = async ({
const where: Where = {} 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) { if (globalConfig) {
where.globalSlug = { where.and = [
{
globalSlug: {
equals: globalConfig.slug, equals: globalConfig.slug,
} },
},
{
updatedAt: {
greater_than: new Date(now - lockDurationInMilliseconds),
},
},
]
} else { } else {
where.and = [ where.and = [
{ {
@@ -58,6 +76,11 @@ export const getIsLocked = async ({
equals: collectionConfig.slug, equals: collectionConfig.slug,
}, },
}, },
{
updatedAt: {
greater_than: new Date(now - lockDurationInMilliseconds),
},
},
] ]
} }

View File

@@ -630,6 +630,8 @@ describe('Locked Documents', () => {
let lockedDoc: PayloadLockedDocument let lockedDoc: PayloadLockedDocument
let expiredTestDoc: Test let expiredTestDoc: Test
let expiredTestLockedDoc: PayloadLockedDocument let expiredTestLockedDoc: PayloadLockedDocument
let expiredPostDoc: Post
let expiredPostLockedDoc: PayloadLockedDocument
beforeAll(async () => { beforeAll(async () => {
postDoc = await createPostDoc({ 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 () => { afterAll(async () => {
@@ -705,6 +728,16 @@ describe('Locked Documents', () => {
collection: 'tests', collection: 'tests',
id: expiredTestDoc.id, 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 () => { 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() 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 () => { 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)) await page.goto(postsUrl.edit(postDoc.id))