Skip to content

Commit 6083870

Browse files
authored
fix(richtext-lexical): upload, relationship and block node insertion fails sometimes
1 parent 9556d1b commit 6083870

File tree

14 files changed

+219
-111
lines changed

14 files changed

+219
-111
lines changed

packages/richtext-lexical/src/field/features/blocks/plugin/index.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,13 @@ export const BlocksPlugin: PluginComponent<BlocksFeatureClientProps> = () => {
3333
INSERT_BLOCK_COMMAND,
3434
(payload: InsertBlockPayload) => {
3535
editor.update(() => {
36-
const blockNode = $createBlockNode(payload)
37-
3836
const selection = $getSelection() || $getPreviousSelection()
3937

4038
if ($isRangeSelection(selection)) {
39+
const blockNode = $createBlockNode(payload)
40+
// Insert blocks node BEFORE potentially removing focusNode, as $insertNodeToNearestRoot errors if the focusNode doesn't exist
41+
$insertNodeToNearestRoot(blockNode)
42+
4143
const { focus } = selection
4244
const focusNode = focus.getNode()
4345

@@ -53,8 +55,6 @@ export const BlocksPlugin: PluginComponent<BlocksFeatureClientProps> = () => {
5355
) {
5456
focusNode.remove()
5557
}
56-
57-
$insertNodeToNearestRoot(blockNode)
5858
}
5959
})
6060

packages/richtext-lexical/src/field/features/relationship/plugins/index.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,13 @@ export const RelationshipPlugin: PluginComponent<RelationshipFeatureProps> = ({
4848
return editor.registerCommand<RelationshipData>(
4949
INSERT_RELATIONSHIP_COMMAND,
5050
(payload) => {
51-
const relationshipNode = $createRelationshipNode(payload)
52-
5351
const selection = $getSelection() || $getPreviousSelection()
5452

5553
if ($isRangeSelection(selection)) {
54+
const relationshipNode = $createRelationshipNode(payload)
55+
// Insert relationship node BEFORE potentially removing focusNode, as $insertNodeToNearestRoot errors if the focusNode doesn't exist
56+
$insertNodeToNearestRoot(relationshipNode)
57+
5658
const { focus } = selection
5759
const focusNode = focus.getNode()
5860

@@ -68,8 +70,6 @@ export const RelationshipPlugin: PluginComponent<RelationshipFeatureProps> = ({
6870
) {
6971
focusNode.remove()
7072
}
71-
72-
$insertNodeToNearestRoot(relationshipNode)
7373
}
7474

7575
return true

packages/richtext-lexical/src/field/features/upload/component/index.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
'use client'
2-
import type { BaseSelection } from 'lexical'
32
import type { ClientCollectionConfig } from 'payload/types'
43

54
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext.js'

packages/richtext-lexical/src/field/features/upload/plugin/index.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,19 @@ export const UploadPlugin: PluginComponentWithAnchor<UploadFeaturePropsClient> =
4242
INSERT_UPLOAD_COMMAND,
4343
(payload: InsertUploadPayload) => {
4444
editor.update(() => {
45-
const uploadNode = $createUploadNode({
46-
data: {
47-
fields: payload.fields,
48-
relationTo: payload.relationTo,
49-
value: payload.value,
50-
},
51-
})
52-
5345
const selection = $getSelection() || $getPreviousSelection()
5446

5547
if ($isRangeSelection(selection)) {
48+
const uploadNode = $createUploadNode({
49+
data: {
50+
fields: payload.fields,
51+
relationTo: payload.relationTo,
52+
value: payload.value,
53+
},
54+
})
55+
// Insert upload node BEFORE potentially removing focusNode, as $insertNodeToNearestRoot errors if the focusNode doesn't exist
56+
$insertNodeToNearestRoot(uploadNode)
57+
5658
const { focus } = selection
5759
const focusNode = focus.getNode()
5860

@@ -68,8 +70,6 @@ export const UploadPlugin: PluginComponentWithAnchor<UploadFeaturePropsClient> =
6870
) {
6971
focusNode.remove()
7072
}
71-
72-
$insertNodeToNearestRoot(uploadNode)
7373
}
7474
})
7575

packages/richtext-lexical/src/field/features/upload/validate.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export const uploadValidation = (
3838

3939
const collection = props?.collections[node.relationTo]
4040

41-
if (!collection.fields?.length) {
41+
if (!collection?.fields?.length) {
4242
return true
4343
}
4444

test/fields/collections/Array/e2e.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,15 @@ describe('Array', () => {
4646
await reInitializeDB({
4747
serverURL,
4848
snapshotKey: 'fieldsArrayTest',
49-
uploadsDir: path.resolve(dirname, '../Upload/uploads'),
49+
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
5050
})
5151
await ensureAutoLoginAndCompilationIsDone({ page, serverURL })
5252
})
5353
beforeEach(async () => {
5454
await reInitializeDB({
5555
serverURL,
5656
snapshotKey: 'fieldsArrayTest',
57-
uploadsDir: path.resolve(dirname, '../Upload/uploads'),
57+
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
5858
})
5959

6060
if (client) {

test/fields/collections/Blocks/e2e.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,15 @@ describe('Block fields', () => {
4141
await reInitializeDB({
4242
serverURL,
4343
snapshotKey: 'blockFieldsTest',
44-
uploadsDir: path.resolve(dirname, '../Upload/uploads'),
44+
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
4545
})
4646
await ensureAutoLoginAndCompilationIsDone({ page, serverURL })
4747
})
4848
beforeEach(async () => {
4949
await reInitializeDB({
5050
serverURL,
5151
snapshotKey: 'blockFieldsTest',
52-
uploadsDir: path.resolve(dirname, '../Upload/uploads'),
52+
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
5353
})
5454

5555
if (client) {

test/fields/collections/Lexical/e2e.spec.ts

Lines changed: 86 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ describe('lexical', () => {
7373
await reInitializeDB({
7474
serverURL,
7575
snapshotKey: 'fieldsLexicalTest',
76-
uploadsDir: path.resolve(dirname, '../Upload/uploads'),
76+
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
7777
})
7878
await ensureAutoLoginAndCompilationIsDone({ page, serverURL })
7979
})
@@ -86,7 +86,10 @@ describe('lexical', () => {
8686
await reInitializeDB({
8787
serverURL,
8888
snapshotKey: 'fieldsLexicalTest',
89-
uploadsDir: path.resolve(dirname, '../Upload/uploads'),
89+
uploadsDir: [
90+
path.resolve(dirname, './collections/Upload/uploads'),
91+
path.resolve(dirname, './collections/Upload2/uploads2'),
92+
],
9093
})
9194

9295
if (client) {
@@ -356,6 +359,85 @@ describe('lexical', () => {
356359
await expect(reactSelect.locator('.rs__value-container').first()).toHaveText('Option 3')
357360
})
358361

362+
// This reproduces an issue where if you create an upload node, the document drawer opens, you select a collection other than the default one, create a NEW upload document and save, it throws a lexical error
363+
test('ensure creation of new upload document within upload node works', async () => {
364+
await navigateToLexicalFields()
365+
const richTextField = page.locator('.rich-text-lexical').nth(1) // second
366+
await richTextField.scrollIntoViewIfNeeded()
367+
await expect(richTextField).toBeVisible()
368+
369+
const lastParagraph = richTextField.locator('p').last()
370+
await lastParagraph.scrollIntoViewIfNeeded()
371+
await expect(lastParagraph).toBeVisible()
372+
373+
/**
374+
* Create new upload node
375+
*/
376+
// type / to open the slash menu
377+
await lastParagraph.click()
378+
await page.keyboard.press('/')
379+
await page.keyboard.type('Upload')
380+
381+
// Create Upload node
382+
const slashMenuPopover = page.locator('#slash-menu .slash-menu-popup')
383+
await expect(slashMenuPopover).toBeVisible()
384+
385+
const uploadSelectButton = slashMenuPopover.locator('button').nth(1)
386+
await expect(uploadSelectButton).toBeVisible()
387+
await expect(uploadSelectButton).toContainText('Upload')
388+
await uploadSelectButton.click()
389+
await expect(slashMenuPopover).toBeHidden()
390+
391+
await wait(500) // wait for drawer form state to initialize (it's a flake)
392+
const uploadListDrawer = page.locator('dialog[id^=list-drawer_1_]').first() // IDs starting with list-drawer_1_ (there's some other symbol after the underscore)
393+
await expect(uploadListDrawer).toBeVisible()
394+
await wait(500)
395+
396+
await uploadListDrawer.locator('.rs__control .value-container').first().click()
397+
await wait(500)
398+
await expect(uploadListDrawer.locator('.rs__option').nth(1)).toBeVisible()
399+
await expect(uploadListDrawer.locator('.rs__option').nth(1)).toContainText('Upload 2')
400+
await uploadListDrawer.locator('.rs__option').nth(1).click()
401+
402+
// wait till the text appears in uploadListDrawer: "No Uploads 2 found. Either no Uploads 2 exist yet or none match the filters you've specified above."
403+
await expect(
404+
uploadListDrawer.getByText(
405+
"No Uploads 2 found. Either no Uploads 2 exist yet or none match the filters you've specified above.",
406+
),
407+
).toBeVisible()
408+
409+
await uploadListDrawer.getByText('Create New').first().click()
410+
const createUploadDrawer = page.locator('dialog[id^=doc-drawer_uploads2_]').first() // IDs starting with list-drawer_1_ (there's some other symbol after the underscore)
411+
await expect(createUploadDrawer).toBeVisible()
412+
await wait(500)
413+
414+
const input = createUploadDrawer.locator('.file-field__upload input[type="file"]').first()
415+
await expect(input).toBeAttached()
416+
417+
await input.setInputFiles(path.resolve(dirname, './collections/Upload/payload.jpg'))
418+
await expect(createUploadDrawer.locator('.file-field .file-field__filename')).toHaveValue(
419+
'payload.jpg',
420+
)
421+
await wait(500)
422+
await createUploadDrawer.getByText('Save').first().click()
423+
await expect(createUploadDrawer).toBeHidden()
424+
await expect(uploadListDrawer).toBeHidden()
425+
await wait(500)
426+
await saveDocAndAssert(page)
427+
428+
// second one should be the newly created one
429+
const secondUploadNode = richTextField.locator('.lexical-upload').nth(1)
430+
await secondUploadNode.scrollIntoViewIfNeeded()
431+
await expect(secondUploadNode).toBeVisible()
432+
433+
await expect(secondUploadNode.locator('.lexical-upload__bottomRow')).toContainText(
434+
'payload.jpg',
435+
)
436+
await expect(secondUploadNode.locator('.lexical-upload__collectionLabel')).toContainText(
437+
'Upload 2',
438+
)
439+
})
440+
359441
describe('nested lexical editor in block', () => {
360442
test('should type and save typed text', async () => {
361443
await navigateToLexicalFields()
@@ -850,7 +932,7 @@ describe('lexical', () => {
850932

851933
const lexicalField: SerializedEditorState = lexicalDoc.lexicalWithBlocks
852934
const richTextBlock: SerializedBlockNode = lexicalField.root
853-
.children[12] as SerializedBlockNode
935+
.children[13] as SerializedBlockNode
854936
const subRichTextBlock: SerializedBlockNode = richTextBlock.fields.richTextField.root
855937
.children[1] as SerializedBlockNode // index 0 and 2 are paragraphs created by default around the block node when a new block is added via slash command
856938

@@ -894,7 +976,7 @@ describe('lexical', () => {
894976

895977
const lexicalField2: SerializedEditorState = lexicalDocDepth1.lexicalWithBlocks
896978
const richTextBlock2: SerializedBlockNode = lexicalField2.root
897-
.children[12] as SerializedBlockNode
979+
.children[13] as SerializedBlockNode
898980
const subRichTextBlock2: SerializedBlockNode = richTextBlock2.fields.richTextField.root
899981
.children[1] as SerializedBlockNode // index 0 and 2 are paragraphs created by default around the block node when a new block is added via slash command
900982

test/fields/collections/Relationship/e2e.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,15 @@ describe('relationship', () => {
4848
await reInitializeDB({
4949
serverURL,
5050
snapshotKey: 'fieldsRelationshipTest',
51-
uploadsDir: path.resolve(dirname, '../Upload/uploads'),
51+
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
5252
})
5353
await ensureAutoLoginAndCompilationIsDone({ page, serverURL })
5454
})
5555
beforeEach(async () => {
5656
await reInitializeDB({
5757
serverURL,
5858
snapshotKey: 'fieldsRelationshipTest',
59-
uploadsDir: path.resolve(dirname, '../Upload/uploads'),
59+
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
6060
})
6161

6262
if (client) {

test/fields/collections/RichText/e2e.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,15 @@ describe('Rich Text', () => {
4141
await reInitializeDB({
4242
serverURL,
4343
snapshotKey: 'fieldsRichTextTest',
44-
uploadsDir: path.resolve(dirname, '../Upload/uploads'),
44+
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
4545
})
4646
await ensureAutoLoginAndCompilationIsDone({ page, serverURL })
4747
})
4848
beforeEach(async () => {
4949
await reInitializeDB({
5050
serverURL,
5151
snapshotKey: 'fieldsRichTextTest',
52-
uploadsDir: path.resolve(dirname, '../Upload/uploads'),
52+
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
5353
})
5454

5555
if (client) {

0 commit comments

Comments
 (0)