fix(richtext-lexical): inline blocks and tables not functioning correctly if they are used in more than one editor on the same page (#7665)
Fixes https://github.com/payloadcms/payload/issues/7579 The problem was that multiple richtext editors shared the same drawer slugs for the table and inline block drawers.
This commit is contained in:
@@ -5,7 +5,13 @@ import type { BlockFieldClient } from 'payload'
|
|||||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext.js'
|
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext.js'
|
||||||
import { $insertNodeToNearestRoot, $wrapNodeInElement, mergeRegister } from '@lexical/utils'
|
import { $insertNodeToNearestRoot, $wrapNodeInElement, mergeRegister } from '@lexical/utils'
|
||||||
import { getTranslation } from '@payloadcms/translations'
|
import { getTranslation } from '@payloadcms/translations'
|
||||||
import { useFieldProps, useModal, useTranslation } from '@payloadcms/ui'
|
import {
|
||||||
|
formatDrawerSlug,
|
||||||
|
useEditDepth,
|
||||||
|
useFieldProps,
|
||||||
|
useModal,
|
||||||
|
useTranslation,
|
||||||
|
} from '@payloadcms/ui'
|
||||||
import {
|
import {
|
||||||
$createParagraphNode,
|
$createParagraphNode,
|
||||||
$getNodeByKey,
|
$getNodeByKey,
|
||||||
@@ -37,8 +43,6 @@ import {
|
|||||||
|
|
||||||
export type InsertBlockPayload = Exclude<BlockFields, 'id'>
|
export type InsertBlockPayload = Exclude<BlockFields, 'id'>
|
||||||
|
|
||||||
const drawerSlug = 'lexical-inlineBlocks-create'
|
|
||||||
|
|
||||||
export const BlocksPlugin: PluginComponent<BlocksFeatureClientProps> = () => {
|
export const BlocksPlugin: PluginComponent<BlocksFeatureClientProps> = () => {
|
||||||
const [editor] = useLexicalComposerContext()
|
const [editor] = useLexicalComposerContext()
|
||||||
const { closeModal, toggleModal } = useModal()
|
const { closeModal, toggleModal } = useModal()
|
||||||
@@ -47,22 +51,18 @@ export const BlocksPlugin: PluginComponent<BlocksFeatureClientProps> = () => {
|
|||||||
const [targetNodeKey, setTargetNodeKey] = useState<null | string>(null)
|
const [targetNodeKey, setTargetNodeKey] = useState<null | string>(null)
|
||||||
const { i18n, t } = useTranslation<string, any>()
|
const { i18n, t } = useTranslation<string, any>()
|
||||||
const { schemaPath } = useFieldProps()
|
const { schemaPath } = useFieldProps()
|
||||||
|
const { uuid } = useEditorConfigContext()
|
||||||
|
const editDepth = useEditDepth()
|
||||||
|
|
||||||
|
const drawerSlug = formatDrawerSlug({
|
||||||
|
slug: `lexical-inlineBlocks-create-` + uuid,
|
||||||
|
depth: editDepth,
|
||||||
|
})
|
||||||
|
|
||||||
const {
|
const {
|
||||||
field: { richTextComponentMap },
|
field: { richTextComponentMap },
|
||||||
} = useEditorConfigContext()
|
} = useEditorConfigContext()
|
||||||
|
|
||||||
const schemaFieldsPath = `${schemaPath}.lexical_internal_feature.blocks.lexical_inline_blocks.lexical_inline_blocks.${blockFields?.blockType}`
|
|
||||||
|
|
||||||
const componentMapRenderedBlockPath = `lexical_internal_feature.blocks.fields.lexical_inline_blocks`
|
|
||||||
const blocksField: BlockFieldClient = richTextComponentMap.has(componentMapRenderedBlockPath)
|
|
||||||
? richTextComponentMap.get(componentMapRenderedBlockPath)[0]
|
|
||||||
: null
|
|
||||||
|
|
||||||
const clientBlock = blocksField
|
|
||||||
? blocksField.blocks.find((block) => block.slug === blockFields?.blockType)
|
|
||||||
: null
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!editor.hasNodes([BlockNode])) {
|
if (!editor.hasNodes([BlockNode])) {
|
||||||
throw new Error('BlocksPlugin: BlocksNode not registered on editor')
|
throw new Error('BlocksPlugin: BlocksNode not registered on editor')
|
||||||
@@ -158,7 +158,22 @@ export const BlocksPlugin: PluginComponent<BlocksFeatureClientProps> = () => {
|
|||||||
COMMAND_PRIORITY_EDITOR,
|
COMMAND_PRIORITY_EDITOR,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}, [editor, targetNodeKey, toggleModal])
|
}, [editor, targetNodeKey, toggleModal, drawerSlug])
|
||||||
|
|
||||||
|
if (!blockFields) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const schemaFieldsPath = `${schemaPath}.lexical_internal_feature.blocks.lexical_inline_blocks.lexical_inline_blocks.${blockFields?.blockType}`
|
||||||
|
|
||||||
|
const componentMapRenderedBlockPath = `lexical_internal_feature.blocks.fields.lexical_inline_blocks`
|
||||||
|
const blocksField: BlockFieldClient = richTextComponentMap.has(componentMapRenderedBlockPath)
|
||||||
|
? richTextComponentMap.get(componentMapRenderedBlockPath)[0]
|
||||||
|
: null
|
||||||
|
|
||||||
|
const clientBlock = blocksField
|
||||||
|
? blocksField.blocks.find((block) => block.slug === blockFields?.blockType)
|
||||||
|
: null
|
||||||
|
|
||||||
if (!blocksField) {
|
if (!blocksField) {
|
||||||
return null
|
return null
|
||||||
|
|||||||
@@ -14,13 +14,14 @@ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext
|
|||||||
import { TablePlugin as LexicalReactTablePlugin } from '@lexical/react/LexicalTablePlugin'
|
import { TablePlugin as LexicalReactTablePlugin } from '@lexical/react/LexicalTablePlugin'
|
||||||
import { INSERT_TABLE_COMMAND, TableNode } from '@lexical/table'
|
import { INSERT_TABLE_COMMAND, TableNode } from '@lexical/table'
|
||||||
import { mergeRegister } from '@lexical/utils'
|
import { mergeRegister } from '@lexical/utils'
|
||||||
import { useModal } from '@payloadcms/ui'
|
import { formatDrawerSlug, useEditDepth, useModal } from '@payloadcms/ui'
|
||||||
import { $getSelection, $isRangeSelection, COMMAND_PRIORITY_EDITOR, createCommand } from 'lexical'
|
import { $getSelection, $isRangeSelection, COMMAND_PRIORITY_EDITOR, createCommand } from 'lexical'
|
||||||
import { createContext, useContext, useEffect, useMemo, useState } from 'react'
|
import { createContext, useContext, useEffect, useMemo, useState } from 'react'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
|
||||||
import type { PluginComponent } from '../../../../typesClient.js'
|
import type { PluginComponent } from '../../../../typesClient.js'
|
||||||
|
|
||||||
|
import { useEditorConfigContext } from '../../../../../lexical/config/client/EditorConfigProvider.js'
|
||||||
import { FieldsDrawer } from '../../../../../utilities/fieldsDrawer/Drawer.js'
|
import { FieldsDrawer } from '../../../../../utilities/fieldsDrawer/Drawer.js'
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
@@ -52,7 +53,6 @@ export const CellContext = createContext<CellContextShape>({
|
|||||||
// Empty
|
// Empty
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
const drawerSlug = 'lexical-table-create'
|
|
||||||
|
|
||||||
export function TableContext({ children }: { children: JSX.Element }) {
|
export function TableContext({ children }: { children: JSX.Element }) {
|
||||||
const [contextValue, setContextValue] = useState<{
|
const [contextValue, setContextValue] = useState<{
|
||||||
@@ -84,6 +84,13 @@ export const TablePlugin: PluginComponent = () => {
|
|||||||
const [editor] = useLexicalComposerContext()
|
const [editor] = useLexicalComposerContext()
|
||||||
const cellContext = useContext(CellContext)
|
const cellContext = useContext(CellContext)
|
||||||
const { closeModal, toggleModal } = useModal()
|
const { closeModal, toggleModal } = useModal()
|
||||||
|
const editDepth = useEditDepth()
|
||||||
|
const { uuid } = useEditorConfigContext()
|
||||||
|
|
||||||
|
const drawerSlug = formatDrawerSlug({
|
||||||
|
slug: 'lexical-table-create-' + uuid,
|
||||||
|
depth: editDepth,
|
||||||
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!editor.hasNodes([TableNode])) {
|
if (!editor.hasNodes([TableNode])) {
|
||||||
|
|||||||
@@ -9,9 +9,10 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
DrawerToggler,
|
DrawerToggler,
|
||||||
File,
|
File,
|
||||||
|
formatDrawerSlug,
|
||||||
useConfig,
|
useConfig,
|
||||||
useDocumentDrawer,
|
useDocumentDrawer,
|
||||||
useDrawerSlug,
|
useEditDepth,
|
||||||
useModal,
|
useModal,
|
||||||
usePayloadAPI,
|
usePayloadAPI,
|
||||||
useTranslation,
|
useTranslation,
|
||||||
@@ -25,7 +26,7 @@ import {
|
|||||||
KEY_BACKSPACE_COMMAND,
|
KEY_BACKSPACE_COMMAND,
|
||||||
KEY_DELETE_COMMAND,
|
KEY_DELETE_COMMAND,
|
||||||
} from 'lexical'
|
} from 'lexical'
|
||||||
import React, { useCallback, useEffect, useReducer, useRef, useState } from 'react'
|
import React, { useCallback, useEffect, useId, useReducer, useRef, useState } from 'react'
|
||||||
|
|
||||||
import type { ClientComponentProps } from '../../../typesClient.js'
|
import type { ClientComponentProps } from '../../../typesClient.js'
|
||||||
import type { UploadData } from '../../server/nodes/UploadNode.js'
|
import type { UploadData } from '../../server/nodes/UploadNode.js'
|
||||||
@@ -65,7 +66,8 @@ const Component: React.FC<ElementProps> = (props) => {
|
|||||||
} = useConfig()
|
} = useConfig()
|
||||||
const uploadRef = useRef<HTMLDivElement | null>(null)
|
const uploadRef = useRef<HTMLDivElement | null>(null)
|
||||||
const { closeModal } = useModal()
|
const { closeModal } = useModal()
|
||||||
|
const { uuid } = useEditorConfigContext()
|
||||||
|
const editDepth = useEditDepth()
|
||||||
const [editor] = useLexicalComposerContext()
|
const [editor] = useLexicalComposerContext()
|
||||||
const [isSelected, setSelected, clearSelection] = useLexicalNodeSelection(nodeKey)
|
const [isSelected, setSelected, clearSelection] = useLexicalNodeSelection(nodeKey)
|
||||||
|
|
||||||
@@ -77,7 +79,12 @@ const Component: React.FC<ElementProps> = (props) => {
|
|||||||
collections.find((coll) => coll.slug === relationTo),
|
collections.find((coll) => coll.slug === relationTo),
|
||||||
)
|
)
|
||||||
|
|
||||||
const drawerSlug = useDrawerSlug('upload-drawer')
|
const componentID = useId()
|
||||||
|
|
||||||
|
const drawerSlug = formatDrawerSlug({
|
||||||
|
slug: `lexical-upload-drawer-` + uuid + componentID, // There can be multiple upload components, each with their own drawer, in one single editor => separate them by componentID
|
||||||
|
depth: editDepth,
|
||||||
|
})
|
||||||
|
|
||||||
const [DocumentDrawer, DocumentDrawerToggler, { closeDrawer }] = useDocumentDrawer({
|
const [DocumentDrawer, DocumentDrawerToggler, { closeDrawer }] = useDocumentDrawer({
|
||||||
id: value,
|
id: value,
|
||||||
|
|||||||
@@ -482,7 +482,9 @@ describe('lexicalMain', () => {
|
|||||||
// Click on button with class lexical-upload__upload-drawer-toggler
|
// Click on button with class lexical-upload__upload-drawer-toggler
|
||||||
await newUploadNode.locator('.lexical-upload__upload-drawer-toggler').first().click()
|
await newUploadNode.locator('.lexical-upload__upload-drawer-toggler').first().click()
|
||||||
|
|
||||||
const uploadExtraFieldsDrawer = page.locator('dialog[id^=drawer_1_upload-drawer-]').first()
|
const uploadExtraFieldsDrawer = page
|
||||||
|
.locator('dialog[id^=drawer_1_lexical-upload-drawer-]')
|
||||||
|
.first()
|
||||||
await expect(uploadExtraFieldsDrawer).toBeVisible()
|
await expect(uploadExtraFieldsDrawer).toBeVisible()
|
||||||
await wait(500)
|
await wait(500)
|
||||||
|
|
||||||
@@ -508,7 +510,7 @@ describe('lexicalMain', () => {
|
|||||||
await expect(reloadedUploadNode).toBeVisible()
|
await expect(reloadedUploadNode).toBeVisible()
|
||||||
await reloadedUploadNode.locator('.lexical-upload__upload-drawer-toggler').first().click()
|
await reloadedUploadNode.locator('.lexical-upload__upload-drawer-toggler').first().click()
|
||||||
const reloadedUploadExtraFieldsDrawer = page
|
const reloadedUploadExtraFieldsDrawer = page
|
||||||
.locator('dialog[id^=drawer_1_upload-drawer-]')
|
.locator('dialog[id^=drawer_1_lexical-upload-drawer-]')
|
||||||
.first()
|
.first()
|
||||||
await expect(reloadedUploadExtraFieldsDrawer).toBeVisible()
|
await expect(reloadedUploadExtraFieldsDrawer).toBeVisible()
|
||||||
await wait(500)
|
await wait(500)
|
||||||
|
|||||||
Reference in New Issue
Block a user