fix(richtext-lexical): richtext fields in drawers aren't editable, inline toolbar artifacts are shown for readOnly editors (#8774)

Fixes this:


https://github.com/user-attachments/assets/cf78082d-9054-4324-90cd-c81219a4f26d
This commit is contained in:
Alessio Gravili
2024-10-17 23:38:48 -06:00
committed by GitHub
parent fa49215078
commit f3bec93d76
8 changed files with 235 additions and 44 deletions

View File

@@ -295,22 +295,18 @@ function InlineToolbar({
return ( return (
<div className="inline-toolbar-popup" ref={floatingToolbarRef}> <div className="inline-toolbar-popup" ref={floatingToolbarRef}>
<div className="caret" ref={caretRef} /> <div className="caret" ref={caretRef} />
{editor.isEditable() && ( {editorConfig?.features &&
<React.Fragment> editorConfig.features?.toolbarInline?.groups.map((group, i) => {
{editorConfig?.features && return (
editorConfig.features?.toolbarInline?.groups.map((group, i) => { <ToolbarGroupComponent
return ( anchorElem={anchorElem}
<ToolbarGroupComponent editor={editor}
anchorElem={anchorElem} group={group}
editor={editor} index={i}
group={group} key={group.key}
index={i} />
key={group.key} )
/> })}
)
})}
</React.Fragment>
)}
</div> </div>
) )
} }
@@ -392,7 +388,7 @@ function useInlineToolbar(
) )
}, [editor, updatePopup]) }, [editor, updatePopup])
if (!isText) { if (!isText || !editor.isEditable()) {
return null return null
} }

View File

@@ -5,6 +5,7 @@ import {
FieldDescription, FieldDescription,
FieldError, FieldError,
FieldLabel, FieldLabel,
useEditDepth,
useField, useField,
useFieldProps, useFieldProps,
withCondition, withCondition,
@@ -47,6 +48,8 @@ const RichTextComponent: React.FC<
const Label = components?.Label const Label = components?.Label
const readOnlyFromProps = readOnlyFromTopLevelProps || readOnlyFromAdmin const readOnlyFromProps = readOnlyFromTopLevelProps || readOnlyFromAdmin
const editDepth = useEditDepth()
const memoizedValidate = useCallback( const memoizedValidate = useCallback(
(value, validationOptions) => { (value, validationOptions) => {
if (typeof validate === 'function') { if (typeof validate === 'function') {
@@ -82,10 +85,12 @@ const RichTextComponent: React.FC<
.filter(Boolean) .filter(Boolean)
.join(' ') .join(' ')
const pathWithEditDepth = `${path}.${editDepth}`
return ( return (
<div <div
className={classes} className={classes}
key={path} key={pathWithEditDepth}
style={{ style={{
...style, ...style,
width, width,
@@ -102,6 +107,7 @@ const RichTextComponent: React.FC<
<div className={`${baseClass}__wrap`}> <div className={`${baseClass}__wrap`}>
<ErrorBoundary fallbackRender={fallbackRender} onReset={() => {}}> <ErrorBoundary fallbackRender={fallbackRender} onReset={() => {}}>
<LexicalProvider <LexicalProvider
composerKey={pathWithEditDepth}
editorConfig={editorConfig} editorConfig={editorConfig}
field={field} field={field}
key={JSON.stringify({ initialValue, path })} // makes sure lexical is completely re-rendered when initialValue changes, bypassing the lexical-internal value memoization. That way, external changes to the form will update the editor. More infos in PR description (https://github.com/payloadcms/payload/pull/5010) key={JSON.stringify({ initialValue, path })} // makes sure lexical is completely re-rendered when initialValue changes, bypassing the lexical-internal value memoization. That way, external changes to the form will update the editor. More infos in PR description (https://github.com/payloadcms/payload/pull/5010)
@@ -117,7 +123,6 @@ const RichTextComponent: React.FC<
setValue(serializedEditorState) setValue(serializedEditorState)
}} }}
path={path}
readOnly={disabled} readOnly={disabled}
value={value} value={value}
/> />

View File

@@ -17,10 +17,10 @@ import { LexicalEditor as LexicalEditorComponent } from './LexicalEditor.js'
import { getEnabledNodes } from './nodes/index.js' import { getEnabledNodes } from './nodes/index.js'
export type LexicalProviderProps = { export type LexicalProviderProps = {
composerKey: string
editorConfig: SanitizedClientEditorConfig editorConfig: SanitizedClientEditorConfig
field: LexicalRichTextFieldProps['field'] field: LexicalRichTextFieldProps['field']
onChange: (editorState: EditorState, editor: LexicalEditor, tags: Set<string>) => void onChange: (editorState: EditorState, editor: LexicalEditor, tags: Set<string>) => void
path: string
readOnly: boolean readOnly: boolean
value: SerializedEditorState value: SerializedEditorState
} }
@@ -41,7 +41,7 @@ const NestProviders = ({ children, providers }) => {
} }
export const LexicalProvider: React.FC<LexicalProviderProps> = (props) => { export const LexicalProvider: React.FC<LexicalProviderProps> = (props) => {
const { editorConfig, field, onChange, path, readOnly, value } = props const { composerKey, editorConfig, field, onChange, readOnly, value } = props
const parentContext = useEditorConfigContext() const parentContext = useEditorConfigContext()
@@ -82,7 +82,7 @@ export const LexicalProvider: React.FC<LexicalProviderProps> = (props) => {
editable: readOnly !== true, editable: readOnly !== true,
editorState: processedValue != null ? JSON.stringify(processedValue) : undefined, editorState: processedValue != null ? JSON.stringify(processedValue) : undefined,
namespace: editorConfig.lexical.namespace, namespace: editorConfig.lexical.namespace,
nodes: [...getEnabledNodes({ editorConfig })], nodes: getEnabledNodes({ editorConfig }),
onError: (error: Error) => { onError: (error: Error) => {
throw error throw error
}, },
@@ -94,8 +94,10 @@ export const LexicalProvider: React.FC<LexicalProviderProps> = (props) => {
return <p>Loading...</p> return <p>Loading...</p>
} }
// We need to add initialConfig.editable to the key to force a re-render when the readOnly prop changes.
// Without it, there were cases where lexical editors inside drawers turn readOnly initially - a few miliseconds later they turn editable, but the editor does not re-render and stays readOnly.
return ( return (
<LexicalComposer initialConfig={initialConfig} key={path}> <LexicalComposer initialConfig={initialConfig} key={composerKey + initialConfig.editable}>
<EditorConfigProvider <EditorConfigProvider
editorConfig={editorConfig} editorConfig={editorConfig}
editorContainerRef={editorContainerRef} editorContainerRef={editorContainerRef}

View File

@@ -9,6 +9,7 @@ import {
HeadingFeature, HeadingFeature,
IndentFeature, IndentFeature,
InlineCodeFeature, InlineCodeFeature,
InlineToolbarFeature,
ItalicFeature, ItalicFeature,
lexicalEditor, lexicalEditor,
LinkFeature, LinkFeature,
@@ -84,6 +85,7 @@ export async function buildConfigWithDefaults(
SubscriptFeature(), SubscriptFeature(),
SuperscriptFeature(), SuperscriptFeature(),
InlineCodeFeature(), InlineCodeFeature(),
InlineToolbarFeature(),
TreeViewFeature(), TreeViewFeature(),
HeadingFeature(), HeadingFeature(),
IndentFeature(), IndentFeature(),

View File

@@ -101,7 +101,7 @@ describe('lexicalBlocks', () => {
describe('nested lexical editor in block', () => { describe('nested lexical editor in block', () => {
test('should type and save typed text', async () => { test('should type and save typed text', async () => {
await navigateToLexicalFields() await navigateToLexicalFields()
const richTextField = page.locator('.rich-text-lexical').nth(1) // second const richTextField = page.locator('.rich-text-lexical').nth(2) // second
await richTextField.scrollIntoViewIfNeeded() await richTextField.scrollIntoViewIfNeeded()
await expect(richTextField).toBeVisible() await expect(richTextField).toBeVisible()
@@ -157,7 +157,7 @@ describe('lexicalBlocks', () => {
test('should be able to bold text using floating select toolbar', async () => { test('should be able to bold text using floating select toolbar', async () => {
// Reproduces https://github.com/payloadcms/payload/issues/4025 // Reproduces https://github.com/payloadcms/payload/issues/4025
await navigateToLexicalFields() await navigateToLexicalFields()
const richTextField = page.locator('.rich-text-lexical').nth(1) // second const richTextField = page.locator('.rich-text-lexical').nth(2) // second
await richTextField.scrollIntoViewIfNeeded() await richTextField.scrollIntoViewIfNeeded()
await expect(richTextField).toBeVisible() await expect(richTextField).toBeVisible()
@@ -239,7 +239,7 @@ describe('lexicalBlocks', () => {
test('should be able to select text, make it an external link and receive the updated link value', async () => { test('should be able to select text, make it an external link and receive the updated link value', async () => {
// Reproduces https://github.com/payloadcms/payload/issues/4025 // Reproduces https://github.com/payloadcms/payload/issues/4025
await navigateToLexicalFields() await navigateToLexicalFields()
const richTextField = page.locator('.rich-text-lexical').nth(1) // second const richTextField = page.locator('.rich-text-lexical').nth(2) // second
await richTextField.scrollIntoViewIfNeeded() await richTextField.scrollIntoViewIfNeeded()
await expect(richTextField).toBeVisible() await expect(richTextField).toBeVisible()
@@ -323,7 +323,7 @@ describe('lexicalBlocks', () => {
test('ensure slash menu is not hidden behind other blocks', async () => { test('ensure slash menu is not hidden behind other blocks', async () => {
// This test makes sure there are no z-index issues here // This test makes sure there are no z-index issues here
await navigateToLexicalFields() await navigateToLexicalFields()
const richTextField = page.locator('.rich-text-lexical').nth(1) // second const richTextField = page.locator('.rich-text-lexical').nth(2) // second
await richTextField.scrollIntoViewIfNeeded() await richTextField.scrollIntoViewIfNeeded()
await expect(richTextField).toBeVisible() await expect(richTextField).toBeVisible()
@@ -396,7 +396,7 @@ describe('lexicalBlocks', () => {
}) })
test('should allow adding new blocks to a sub-blocks field, part of a parent lexical blocks field', async () => { test('should allow adding new blocks to a sub-blocks field, part of a parent lexical blocks field', async () => {
await navigateToLexicalFields() await navigateToLexicalFields()
const richTextField = page.locator('.rich-text-lexical').nth(1) // second const richTextField = page.locator('.rich-text-lexical').nth(2) // second
await richTextField.scrollIntoViewIfNeeded() await richTextField.scrollIntoViewIfNeeded()
await expect(richTextField).toBeVisible() await expect(richTextField).toBeVisible()
@@ -471,7 +471,7 @@ describe('lexicalBlocks', () => {
// Big test which tests a bunch of things: Creation of blocks via slash commands, creation of deeply nested sub-lexical-block fields via slash commands, properly populated deeply nested fields within those // Big test which tests a bunch of things: Creation of blocks via slash commands, creation of deeply nested sub-lexical-block fields via slash commands, properly populated deeply nested fields within those
test('ensure creation of a lexical, lexical-field-block, which contains another lexical, lexical-and-upload-field-block, works and that the sub-upload field is properly populated', async () => { test('ensure creation of a lexical, lexical-field-block, which contains another lexical, lexical-and-upload-field-block, works and that the sub-upload field is properly populated', async () => {
await navigateToLexicalFields() await navigateToLexicalFields()
const richTextField = page.locator('.rich-text-lexical').nth(1) // second const richTextField = page.locator('.rich-text-lexical').nth(2) // second
await richTextField.scrollIntoViewIfNeeded() await richTextField.scrollIntoViewIfNeeded()
await expect(richTextField).toBeVisible() await expect(richTextField).toBeVisible()
@@ -690,7 +690,7 @@ describe('lexicalBlocks', () => {
// This test ensures that https://github.com/payloadcms/payload/issues/3911 does not happen again // This test ensures that https://github.com/payloadcms/payload/issues/3911 does not happen again
await navigateToLexicalFields() await navigateToLexicalFields()
const richTextField = page.locator('.rich-text-lexical').nth(1) // second const richTextField = page.locator('.rich-text-lexical').nth(2) // second
await richTextField.scrollIntoViewIfNeeded() await richTextField.scrollIntoViewIfNeeded()
await expect(richTextField).toBeVisible() await expect(richTextField).toBeVisible()
@@ -762,7 +762,7 @@ describe('lexicalBlocks', () => {
// 3. In the issue, after writing one character, the cursor focuses back into the parent editor // 3. In the issue, after writing one character, the cursor focuses back into the parent editor
await navigateToLexicalFields() await navigateToLexicalFields()
const richTextField = page.locator('.rich-text-lexical').nth(1) // second const richTextField = page.locator('.rich-text-lexical').nth(2) // second
await richTextField.scrollIntoViewIfNeeded() await richTextField.scrollIntoViewIfNeeded()
await expect(richTextField).toBeVisible() await expect(richTextField).toBeVisible()
@@ -802,7 +802,7 @@ describe('lexicalBlocks', () => {
}) })
const shouldRespectRowRemovalTest = async () => { const shouldRespectRowRemovalTest = async () => {
const richTextField = page.locator('.rich-text-lexical').nth(1) // second const richTextField = page.locator('.rich-text-lexical').nth(2) // second
await richTextField.scrollIntoViewIfNeeded() await richTextField.scrollIntoViewIfNeeded()
await expect(richTextField).toBeVisible() await expect(richTextField).toBeVisible()
@@ -859,7 +859,7 @@ describe('lexicalBlocks', () => {
await navigateToLexicalFields() await navigateToLexicalFields()
// Wait for lexical to be loaded up fully // Wait for lexical to be loaded up fully
const richTextField = page.locator('.rich-text-lexical').nth(1) // second const richTextField = page.locator('.rich-text-lexical').nth(2) // second
await richTextField.scrollIntoViewIfNeeded() await richTextField.scrollIntoViewIfNeeded()
await expect(richTextField).toBeVisible() await expect(richTextField).toBeVisible()
@@ -882,7 +882,7 @@ describe('lexicalBlocks', () => {
test('ensure pre-seeded uploads node is visible', async () => { test('ensure pre-seeded uploads node is visible', async () => {
// Due to issues with the relationships condition, we had issues with that not being visible. Checking for visibility ensures there is no breakage there again // Due to issues with the relationships condition, we had issues with that not being visible. Checking for visibility ensures there is no breakage there again
await navigateToLexicalFields() await navigateToLexicalFields()
const richTextField = page.locator('.rich-text-lexical').nth(1) // second const richTextField = page.locator('.rich-text-lexical').nth(2) // second
await richTextField.scrollIntoViewIfNeeded() await richTextField.scrollIntoViewIfNeeded()
await expect(richTextField).toBeVisible() await expect(richTextField).toBeVisible()
@@ -897,7 +897,7 @@ describe('lexicalBlocks', () => {
test('should respect required error state in deeply nested text field', async () => { test('should respect required error state in deeply nested text field', async () => {
await navigateToLexicalFields() await navigateToLexicalFields()
const richTextField = page.locator('.rich-text-lexical').nth(1) // second const richTextField = page.locator('.rich-text-lexical').nth(2) // second
await richTextField.scrollIntoViewIfNeeded() await richTextField.scrollIntoViewIfNeeded()
await expect(richTextField).toBeVisible() await expect(richTextField).toBeVisible()
@@ -946,7 +946,7 @@ describe('lexicalBlocks', () => {
// Reproduces https://github.com/payloadcms/payload/issues/6631 // Reproduces https://github.com/payloadcms/payload/issues/6631
test('ensure tabs field within lexical block correctly loads and saves data', async () => { test('ensure tabs field within lexical block correctly loads and saves data', async () => {
await navigateToLexicalFields() await navigateToLexicalFields()
const richTextField = page.locator('.rich-text-lexical').nth(1) // second const richTextField = page.locator('.rich-text-lexical').nth(2) // second
await richTextField.scrollIntoViewIfNeeded() await richTextField.scrollIntoViewIfNeeded()
await expect(richTextField).toBeVisible() await expect(richTextField).toBeVisible()

View File

@@ -103,7 +103,7 @@ describe('lexicalMain', () => {
await navigateToLexicalFields() await navigateToLexicalFields()
await expect( await expect(
page.locator('.rich-text-lexical').nth(1).locator('.lexical-block').first(), page.locator('.rich-text-lexical').nth(2).locator('.lexical-block').first(),
).toBeVisible() ).toBeVisible()
// Navigate to some different page, away from the current document // Navigate to some different page, away from the current document
@@ -116,7 +116,7 @@ describe('lexicalMain', () => {
test('should not warn about unsaved changes when navigating to lexical editor with blocks node and then leaving the page after making a change and saving', async () => { test('should not warn about unsaved changes when navigating to lexical editor with blocks node and then leaving the page after making a change and saving', async () => {
// Relevant issue: https://github.com/payloadcms/payload/issues/4115 // Relevant issue: https://github.com/payloadcms/payload/issues/4115
await navigateToLexicalFields() await navigateToLexicalFields()
const thirdBlock = page.locator('.rich-text-lexical').nth(1).locator('.lexical-block').nth(2) const thirdBlock = page.locator('.rich-text-lexical').nth(2).locator('.lexical-block').nth(2)
await thirdBlock.scrollIntoViewIfNeeded() await thirdBlock.scrollIntoViewIfNeeded()
await expect(thirdBlock).toBeVisible() await expect(thirdBlock).toBeVisible()
@@ -150,7 +150,7 @@ describe('lexicalMain', () => {
test('should type and save typed text', async () => { test('should type and save typed text', async () => {
await navigateToLexicalFields() await navigateToLexicalFields()
const richTextField = page.locator('.rich-text-lexical').nth(1) // second const richTextField = page.locator('.rich-text-lexical').nth(2) // second
await richTextField.scrollIntoViewIfNeeded() await richTextField.scrollIntoViewIfNeeded()
await expect(richTextField).toBeVisible() await expect(richTextField).toBeVisible()
@@ -194,7 +194,7 @@ describe('lexicalMain', () => {
}) })
test('should be able to bold text using floating select toolbar', async () => { test('should be able to bold text using floating select toolbar', async () => {
await navigateToLexicalFields() await navigateToLexicalFields()
const richTextField = page.locator('.rich-text-lexical').nth(1) // second const richTextField = page.locator('.rich-text-lexical').nth(2) // second
await richTextField.scrollIntoViewIfNeeded() await richTextField.scrollIntoViewIfNeeded()
await expect(richTextField).toBeVisible() await expect(richTextField).toBeVisible()
@@ -299,7 +299,7 @@ describe('lexicalMain', () => {
// This test makes sure there are no z-index issues here // This test makes sure there are no z-index issues here
await navigateToLexicalFields() await navigateToLexicalFields()
const richTextField = page.locator('.rich-text-lexical').first() const richTextField = page.locator('.rich-text-lexical').nth(1)
await richTextField.scrollIntoViewIfNeeded() await richTextField.scrollIntoViewIfNeeded()
await expect(richTextField).toBeVisible() await expect(richTextField).toBeVisible()
@@ -360,7 +360,7 @@ describe('lexicalMain', () => {
// 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 // 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
test('ensure creation of new upload document within upload node works', async () => { test('ensure creation of new upload document within upload node works', async () => {
await navigateToLexicalFields() await navigateToLexicalFields()
const richTextField = page.locator('.rich-text-lexical').nth(1) // second const richTextField = page.locator('.rich-text-lexical').nth(2) // second
await richTextField.scrollIntoViewIfNeeded() await richTextField.scrollIntoViewIfNeeded()
await expect(richTextField).toBeVisible() await expect(richTextField).toBeVisible()
@@ -439,10 +439,13 @@ describe('lexicalMain', () => {
// This reproduces https://github.com/payloadcms/payload/issues/7128 // This reproduces https://github.com/payloadcms/payload/issues/7128
test('ensure newly created upload node has fields, saves them, and loads them correctly', async () => { test('ensure newly created upload node has fields, saves them, and loads them correctly', async () => {
await navigateToLexicalFields() await navigateToLexicalFields()
const richTextField = page.locator('.rich-text-lexical').nth(1) // second const richTextField = page.locator('.rich-text-lexical').nth(2) // second
await richTextField.scrollIntoViewIfNeeded() await richTextField.scrollIntoViewIfNeeded()
await expect(richTextField).toBeVisible() await expect(richTextField).toBeVisible()
// Wait until there at least 10 blocks visible in that richtext field - thus wait for it to be fully loaded
await expect(richTextField.locator('.lexical-block')).toHaveCount(10)
const lastParagraph = richTextField.locator('p').last() const lastParagraph = richTextField.locator('p').last()
await lastParagraph.scrollIntoViewIfNeeded() await lastParagraph.scrollIntoViewIfNeeded()
await expect(lastParagraph).toBeVisible() await expect(lastParagraph).toBeVisible()
@@ -503,7 +506,7 @@ describe('lexicalMain', () => {
await wait(300) await wait(300)
const reloadedUploadNode = page const reloadedUploadNode = page
.locator('.rich-text-lexical') .locator('.rich-text-lexical')
.nth(1) .nth(2)
.locator('.lexical-upload') .locator('.lexical-upload')
.nth(1) .nth(1)
await reloadedUploadNode.scrollIntoViewIfNeeded() await reloadedUploadNode.scrollIntoViewIfNeeded()
@@ -561,6 +564,126 @@ describe('lexicalMain', () => {
await expect(page.locator('.rich-text-lexical').nth(1)).toBeVisible() await expect(page.locator('.rich-text-lexical').nth(1)).toBeVisible()
}) })
/**
* There was a bug where the inline toolbar inside a lexical editor in a drawer was not shown
*/
test('ensure lexical editor within drawer within relationship within lexical field has fully-functioning inline toolbar', async () => {
await navigateToLexicalFields()
const richTextField = page.locator('.rich-text-lexical').first()
await richTextField.scrollIntoViewIfNeeded()
await expect(richTextField).toBeVisible()
const paragraph = richTextField.locator('.LexicalEditorTheme__paragraph').first()
await paragraph.scrollIntoViewIfNeeded()
await expect(paragraph).toBeVisible()
/**
* Create new relationship node
*/
// type / to open the slash menu
await paragraph.click()
await page.keyboard.press('/')
await page.keyboard.type('Relationship')
// Create Relationship node
const slashMenuPopover = page.locator('#slash-menu .slash-menu-popup')
await expect(slashMenuPopover).toBeVisible()
const relationshipSelectButton = slashMenuPopover.locator('button').first()
await expect(relationshipSelectButton).toBeVisible()
await expect(relationshipSelectButton).toHaveText('Relationship')
await relationshipSelectButton.click()
await expect(slashMenuPopover).toBeHidden()
await wait(500) // wait for drawer form state to initialize (it's a flake)
const relationshipListDrawer = page.locator('dialog[id^=list-drawer_1_]').first() // IDs starting with list-drawer_1_ (there's some other symbol after the underscore)
await expect(relationshipListDrawer).toBeVisible()
await wait(500)
await expect(relationshipListDrawer.locator('.rs__single-value')).toHaveText('Lexical Field')
await relationshipListDrawer.locator('button').getByText('Rich Text').first().click()
await expect(relationshipListDrawer).toBeHidden()
const newRelationshipNode = richTextField.locator('.lexical-relationship').first()
await newRelationshipNode.scrollIntoViewIfNeeded()
await expect(newRelationshipNode).toBeVisible()
await newRelationshipNode.locator('.doc-drawer__toggler').first().click()
await wait(500) // wait for drawer form state to initialize (it's a flake)
/**
* Now we are inside the doc drawer containing the richtext field.
* Let's test if its inline toolbar works
*/
const docDrawer = page.locator('dialog[id^=doc-drawer_lexical-fields_1_]').first() // IDs starting with list-drawer_1_ (there's some other symbol after the underscore)
await expect(docDrawer).toBeVisible()
await wait(500)
const docRichTextField = docDrawer.locator('.rich-text-lexical').first()
await docRichTextField.scrollIntoViewIfNeeded()
await expect(docRichTextField).toBeVisible()
const docParagraph = docRichTextField.locator('.LexicalEditorTheme__paragraph').first()
await docParagraph.scrollIntoViewIfNeeded()
await expect(docParagraph).toBeVisible()
await docParagraph.click()
await page.keyboard.type('Some text')
// Select "text" by pressing shift + arrow left
for (let i = 0; i < 4; i++) {
await page.keyboard.press('Shift+ArrowLeft')
}
// Ensure inline toolbar appeared
const inlineToolbar = docRichTextField.locator('.inline-toolbar-popup')
await expect(inlineToolbar).toBeVisible()
const boldButton = inlineToolbar.locator('.toolbar-popup__button-bold')
await expect(boldButton).toBeVisible()
// make text bold
await boldButton.click()
// Save drawer
await docDrawer.locator('button').getByText('Save').first().click()
await expect(docDrawer).toBeHidden()
await wait(1500) // Ensure doc is saved in the database
// Do not save the main page, as it will still have the stale, previous data. // TODO: This should eventually be fixed. It's a separate issue than what this test is about though.
// Check if the text is bold. It's a self-relationship, so no need to follow relationship
await expect(async () => {
const lexicalDoc: LexicalField = (
await payload.find({
collection: lexicalFieldsSlug,
depth: 0,
overrideAccess: true,
where: {
title: {
equals: lexicalDocData.title,
},
},
})
).docs[0] as never
const lexicalField: SerializedEditorState = lexicalDoc.lexicalRootEditor
const firstParagraph: SerializedParagraphNode = lexicalField.root
.children[0] as SerializedParagraphNode
expect(firstParagraph.children).toHaveLength(2)
const textNode: SerializedTextNode = firstParagraph.children[0] as SerializedTextNode
const boldNode: SerializedTextNode = firstParagraph.children[1] as SerializedTextNode
expect(textNode.text).toBe('Some ')
expect(textNode.format).toBe(0)
expect(boldNode.text).toBe('text')
expect(boldNode.format).toBe(1)
}).toPass({
timeout: POLL_TOPASS_TIMEOUT,
})
})
describe('localization', () => { describe('localization', () => {
test.skip('ensure simple localized lexical field works', async () => { test.skip('ensure simple localized lexical field works', async () => {
await navigateToLexicalFields(true, true) await navigateToLexicalFields(true, true)

View File

@@ -123,6 +123,10 @@ export const LexicalFields: CollectionConfig = {
type: 'text', type: 'text',
required: true, required: true,
}, },
{
name: 'lexicalRootEditor',
type: 'richText',
},
{ {
name: 'lexicalSimple', name: 'lexicalSimple',
type: 'richText', type: 'richText',

View File

@@ -31,6 +31,7 @@ export interface Config {
'lexical-fields': LexicalField; 'lexical-fields': LexicalField;
'lexical-migrate-fields': LexicalMigrateField; 'lexical-migrate-fields': LexicalMigrateField;
'lexical-localized-fields': LexicalLocalizedField; 'lexical-localized-fields': LexicalLocalizedField;
lexicalObjectReferenceBug: LexicalObjectReferenceBug;
users: User; users: User;
'array-fields': ArrayField; 'array-fields': ArrayField;
'block-fields': BlockField; 'block-fields': BlockField;
@@ -102,6 +103,21 @@ export interface UserAuthOperations {
export interface LexicalField { export interface LexicalField {
id: string; id: string;
title: string; title: string;
lexicalRootEditor?: {
root: {
type: string;
children: {
type: string;
version: number;
[k: string]: unknown;
}[];
direction: ('ltr' | 'rtl') | null;
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
indent: number;
version: number;
};
[k: string]: unknown;
} | null;
lexicalSimple?: { lexicalSimple?: {
root: { root: {
type: string; type: string;
@@ -271,6 +287,45 @@ export interface LexicalLocalizedField {
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "lexicalObjectReferenceBug".
*/
export interface LexicalObjectReferenceBug {
id: string;
lexicalDefault?: {
root: {
type: string;
children: {
type: string;
version: number;
[k: string]: unknown;
}[];
direction: ('ltr' | 'rtl') | null;
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
indent: number;
version: number;
};
[k: string]: unknown;
} | null;
lexicalEditor?: {
root: {
type: string;
children: {
type: string;
version: number;
[k: string]: unknown;
}[];
direction: ('ltr' | 'rtl') | null;
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
indent: number;
version: number;
};
[k: string]: unknown;
} | null;
updatedAt: string;
createdAt: string;
}
/** /**
* This interface was referenced by `Config`'s JSON-Schema * This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "users". * via the `definition` "users".
@@ -1678,6 +1733,10 @@ export interface PayloadLockedDocument {
relationTo: 'lexical-localized-fields'; relationTo: 'lexical-localized-fields';
value: string | LexicalLocalizedField; value: string | LexicalLocalizedField;
} | null) } | null)
| ({
relationTo: 'lexicalObjectReferenceBug';
value: string | LexicalObjectReferenceBug;
} | null)
| ({ | ({
relationTo: 'users'; relationTo: 'users';
value: string | User; value: string | User;