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:
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user