diff --git a/packages/richtext-lexical/src/features/blocks/client/component/index.scss b/packages/richtext-lexical/src/features/blocks/client/component/index.scss index 5411fe00257..0ef7e67f0b6 100644 --- a/packages/richtext-lexical/src/features/blocks/client/component/index.scss +++ b/packages/richtext-lexical/src/features/blocks/client/component/index.scss @@ -6,6 +6,10 @@ z-index: 1; } + [data-lexical-decorator='true']:has(.lexical-block) { + width: auto; + } + .lexical-block-not-found { color: var(--theme-error-500); font-size: 1.1rem; diff --git a/packages/richtext-lexical/src/features/blocks/client/componentInline/index.scss b/packages/richtext-lexical/src/features/blocks/client/componentInline/index.scss index ef9673301dc..53b333d04ae 100644 --- a/packages/richtext-lexical/src/features/blocks/client/componentInline/index.scss +++ b/packages/richtext-lexical/src/features/blocks/client/componentInline/index.scss @@ -3,6 +3,8 @@ @layer payload-default { .inline-block-container { display: inline-block; + margin-right: base(0.2); + margin-left: base(0.2); } .inline-block.inline-block-not-found { @@ -22,8 +24,6 @@ border-radius: $style-radius-s; max-width: calc(var(--base) * 15); font-family: var(--font-body); - margin-right: base(0.2); - margin-left: base(0.2); &::selection { background: transparent; @@ -38,11 +38,6 @@ overflow: hidden; } - &--selected { - background: var(--theme-success-100); - outline: 1px solid var(--theme-success-400); - } - &__editButton.btn { margin: 0; } diff --git a/packages/richtext-lexical/src/features/blocks/client/componentInline/index.tsx b/packages/richtext-lexical/src/features/blocks/client/componentInline/index.tsx index 27b3add8a75..c06176b3253 100644 --- a/packages/richtext-lexical/src/features/blocks/client/componentInline/index.tsx +++ b/packages/richtext-lexical/src/features/blocks/client/componentInline/index.tsx @@ -6,8 +6,6 @@ const baseClass = 'inline-block' import type { BlocksFieldClient, Data, FormState } from 'payload' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' -import { useLexicalNodeSelection } from '@lexical/react/useLexicalNodeSelection' -import { mergeRegister } from '@lexical/utils' import { getTranslation } from '@payloadcms/translations' import { Button, @@ -24,15 +22,7 @@ import { useTranslation, } from '@payloadcms/ui' import { abortAndIgnore } from '@payloadcms/ui/shared' -import { - $getNodeByKey, - $getSelection, - $isNodeSelection, - CLICK_COMMAND, - COMMAND_PRIORITY_LOW, - KEY_BACKSPACE_COMMAND, - KEY_DELETE_COMMAND, -} from 'lexical' +import { $getNodeByKey } from 'lexical' import './index.scss' @@ -116,7 +106,6 @@ export const InlineBlockComponent: React.FC = (props) => { const { toggleDrawer } = useLexicalDrawer(drawerSlug, true) const inlineBlockElemElemRef = useRef(null) - const [isSelected, setSelected, clearSelection] = useLexicalNodeSelection(nodeKey) const { id, collectionSlug, getDocPreferences, globalSlug } = useDocumentInfo() const componentMapRenderedBlockPath = `${schemaPath}.lexical_internal_feature.blocks.lexical_inline_blocks.${formData.blockType}` @@ -153,56 +142,6 @@ export const InlineBlockComponent: React.FC = (props) => { }) }, [editor, nodeKey]) - const $onDelete = useCallback( - (event: KeyboardEvent) => { - const deleteSelection = $getSelection() - if (isSelected && $isNodeSelection(deleteSelection)) { - event.preventDefault() - editor.update(() => { - deleteSelection.getNodes().forEach((node) => { - if ($isInlineBlockNode(node)) { - node.remove() - } - }) - }) - } - return false - }, - [editor, isSelected], - ) - const onClick = useCallback( - (payload: MouseEvent) => { - const event = payload - // Check if inlineBlockElemElemRef.target or anything WITHIN inlineBlockElemElemRef.target was clicked - if ( - event.target === inlineBlockElemElemRef.current || - inlineBlockElemElemRef.current?.contains(event.target as Node) - ) { - if (event.shiftKey) { - setSelected(!isSelected) - } else { - if (!isSelected) { - clearSelection() - setSelected(true) - } - } - return true - } - - return false - }, - [isSelected, setSelected, clearSelection], - ) - - useEffect(() => { - return mergeRegister( - editor.registerCommand(CLICK_COMMAND, onClick, COMMAND_PRIORITY_LOW), - - editor.registerCommand(KEY_DELETE_COMMAND, $onDelete, COMMAND_PRIORITY_LOW), - editor.registerCommand(KEY_BACKSPACE_COMMAND, $onDelete, COMMAND_PRIORITY_LOW), - ) - }, [clearSelection, editor, isSelected, nodeKey, $onDelete, setSelected, onClick]) - const blockDisplayName = clientBlock?.labels?.singular ? getTranslation(clientBlock?.labels.singular, i18n) : clientBlock?.slug @@ -362,12 +301,7 @@ export const InlineBlockComponent: React.FC = (props) => { () => ({ children, className }: { children: React.ReactNode; className?: string }) => (
= (props) => { {children}
), - [formData.blockType, isSelected], + [formData.blockType], ) const Label = useMemo(() => { diff --git a/packages/richtext-lexical/src/features/horizontalRule/client/component/index.tsx b/packages/richtext-lexical/src/features/horizontalRule/client/component/index.tsx deleted file mode 100644 index 9737ca36ed0..00000000000 --- a/packages/richtext-lexical/src/features/horizontalRule/client/component/index.tsx +++ /dev/null @@ -1,85 +0,0 @@ -'use client' - -import type { NodeKey } from 'lexical' - -import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext.js' -import { useLexicalNodeSelection } from '@lexical/react/useLexicalNodeSelection.js' -import { addClassNamesToElement, mergeRegister, removeClassNamesFromElement } from '@lexical/utils' -import { - $getSelection, - $isNodeSelection, - CLICK_COMMAND, - COMMAND_PRIORITY_LOW, - KEY_BACKSPACE_COMMAND, - KEY_DELETE_COMMAND, -} from 'lexical' -import { useCallback, useEffect } from 'react' - -import { $isHorizontalRuleNode } from '../nodes/HorizontalRuleNode.js' - -const isSelectedClassName = 'selected' - -/** - * React component rendered in the lexical editor, WITHIN the hr element created by createDOM of the HorizontalRuleNode. - * - * @param nodeKey every node has a unique key (this key is not saved to the database and thus may differ between sessions). It's useful for working with the CURRENT lexical editor state - */ -export function HorizontalRuleComponent({ nodeKey }: { nodeKey: NodeKey }) { - const [editor] = useLexicalComposerContext() - const [isSelected, setSelected, clearSelection] = useLexicalNodeSelection(nodeKey) - - const $onDelete = useCallback( - (event: KeyboardEvent) => { - const deleteSelection = $getSelection() - if (isSelected && $isNodeSelection(deleteSelection)) { - event.preventDefault() - editor.update(() => { - deleteSelection.getNodes().forEach((node) => { - if ($isHorizontalRuleNode(node)) { - node.remove() - } - }) - }) - } - return false - }, - [editor, isSelected], - ) - - useEffect(() => { - return mergeRegister( - editor.registerCommand( - CLICK_COMMAND, - (event: MouseEvent) => { - const hrElem = editor.getElementByKey(nodeKey) - - if (event.target === hrElem) { - if (!event.shiftKey) { - clearSelection() - } - setSelected(!isSelected) - return true - } - - return false - }, - COMMAND_PRIORITY_LOW, - ), - editor.registerCommand(KEY_DELETE_COMMAND, $onDelete, COMMAND_PRIORITY_LOW), - editor.registerCommand(KEY_BACKSPACE_COMMAND, $onDelete, COMMAND_PRIORITY_LOW), - ) - }, [clearSelection, editor, isSelected, nodeKey, $onDelete, setSelected]) - - useEffect(() => { - const hrElem = editor.getElementByKey(nodeKey) - if (hrElem !== null) { - if (isSelected) { - addClassNamesToElement(hrElem, isSelectedClassName) - } else { - removeClassNamesFromElement(hrElem, isSelectedClassName) - } - } - }, [editor, isSelected, nodeKey]) - - return null -} diff --git a/packages/richtext-lexical/src/features/horizontalRule/client/nodes/HorizontalRuleNode.tsx b/packages/richtext-lexical/src/features/horizontalRule/client/nodes/HorizontalRuleNode.tsx index a753fa8d799..38da131c67a 100644 --- a/packages/richtext-lexical/src/features/horizontalRule/client/nodes/HorizontalRuleNode.tsx +++ b/packages/richtext-lexical/src/features/horizontalRule/client/nodes/HorizontalRuleNode.tsx @@ -8,12 +8,6 @@ import type { SerializedHorizontalRuleNode } from '../../server/nodes/Horizontal import { HorizontalRuleServerNode } from '../../server/nodes/HorizontalRuleNode.js' -const HorizontalRuleComponent = React.lazy(() => - import('../../client/component/index.js').then((module) => ({ - default: module.HorizontalRuleComponent, - })), -) - export class HorizontalRuleNode extends HorizontalRuleServerNode { static override clone(node: HorizontalRuleServerNode): HorizontalRuleServerNode { return super.clone(node) @@ -33,8 +27,8 @@ export class HorizontalRuleNode extends HorizontalRuleServerNode { /** * Allows you to render a React component within whatever createDOM returns. */ - override decorate(): React.ReactElement { - return + override decorate() { + return null } override exportJSON(): SerializedLexicalNode { diff --git a/packages/richtext-lexical/src/features/horizontalRule/client/plugin/index.scss b/packages/richtext-lexical/src/features/horizontalRule/client/plugin/index.scss index 72d343d4951..c4d71052aec 100644 --- a/packages/richtext-lexical/src/features/horizontalRule/client/plugin/index.scss +++ b/packages/richtext-lexical/src/features/horizontalRule/client/plugin/index.scss @@ -2,6 +2,7 @@ @layer payload-default { .LexicalEditorTheme__hr { + width: auto !important; padding: 2px 2px; border: none; margin: 1rem 0; diff --git a/packages/richtext-lexical/src/features/relationship/client/components/RelationshipComponent.tsx b/packages/richtext-lexical/src/features/relationship/client/components/RelationshipComponent.tsx index ff3b1fabf48..7b761d872a8 100644 --- a/packages/richtext-lexical/src/features/relationship/client/components/RelationshipComponent.tsx +++ b/packages/richtext-lexical/src/features/relationship/client/components/RelationshipComponent.tsx @@ -2,27 +2,16 @@ import type { ElementFormatType } from 'lexical' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext.js' -import { useLexicalNodeSelection } from '@lexical/react/useLexicalNodeSelection.js' -import { mergeRegister } from '@lexical/utils' import { getTranslation } from '@payloadcms/translations' import { Button, useConfig, usePayloadAPI, useTranslation } from '@payloadcms/ui' -import { - $getNodeByKey, - $getSelection, - $isNodeSelection, - CLICK_COMMAND, - COMMAND_PRIORITY_LOW, - KEY_BACKSPACE_COMMAND, - KEY_DELETE_COMMAND, -} from 'lexical' -import React, { useCallback, useEffect, useReducer, useRef, useState } from 'react' +import { $getNodeByKey } from 'lexical' +import React, { useCallback, useReducer, useRef, useState } from 'react' import type { RelationshipData } from '../../server/nodes/RelationshipNode.js' import { useEditorConfigContext } from '../../../../lexical/config/client/EditorConfigProvider.js' import { useLexicalDocumentDrawer } from '../../../../utilities/fieldsDrawer/useLexicalDocumentDrawer.js' import { INSERT_RELATIONSHIP_WITH_DRAWER_COMMAND } from '../drawer/commands.js' -import { $isRelationshipNode } from '../nodes/RelationshipNode.js' import './index.scss' const baseClass = 'lexical-relationship' @@ -53,7 +42,6 @@ const Component: React.FC = (props) => { const relationshipElemRef = useRef(null) const [editor] = useLexicalComposerContext() - const [isSelected, setSelected, clearSelection] = useLexicalNodeSelection(nodeKey!) const { fieldProps: { readOnly }, } = useEditorConfigContext() @@ -65,9 +53,7 @@ const Component: React.FC = (props) => { getEntityConfig, } = useConfig() - const [relatedCollection, setRelatedCollection] = useState(() => - getEntityConfig({ collectionSlug: relationTo }), - ) + const [relatedCollection] = useState(() => getEntityConfig({ collectionSlug: relationTo })) const { i18n, t } = useTranslation() const [cacheBust, dispatchCacheBust] = useReducer((state) => state + 1, 0) @@ -97,63 +83,8 @@ const Component: React.FC = (props) => { dispatchCacheBust() }, [cacheBust, setParams, closeDocumentDrawer]) - const $onDelete = useCallback( - (payload: KeyboardEvent) => { - const deleteSelection = $getSelection() - if (isSelected && $isNodeSelection(deleteSelection)) { - const event: KeyboardEvent = payload - event.preventDefault() - editor.update(() => { - deleteSelection.getNodes().forEach((node) => { - if ($isRelationshipNode(node)) { - node.remove() - } - }) - }) - } - return false - }, - [editor, isSelected], - ) - const onClick = useCallback( - (payload: MouseEvent) => { - const event = payload - // Check if relationshipElemRef.target or anything WITHIN relationshipElemRef.target was clicked - if ( - event.target === relationshipElemRef.current || - relationshipElemRef.current?.contains(event.target as Node) - ) { - if (event.shiftKey) { - setSelected(!isSelected) - } else { - if (!isSelected) { - clearSelection() - setSelected(true) - } - } - return true - } - - return false - }, - [isSelected, setSelected, clearSelection], - ) - - useEffect(() => { - return mergeRegister( - editor.registerCommand(CLICK_COMMAND, onClick, COMMAND_PRIORITY_LOW), - - editor.registerCommand(KEY_DELETE_COMMAND, $onDelete, COMMAND_PRIORITY_LOW), - editor.registerCommand(KEY_BACKSPACE_COMMAND, $onDelete, COMMAND_PRIORITY_LOW), - ) - }, [clearSelection, editor, isSelected, nodeKey, $onDelete, setSelected, onClick]) - return ( -
+

{t('fields:labelRelationship', { diff --git a/packages/richtext-lexical/src/features/relationship/client/components/index.scss b/packages/richtext-lexical/src/features/relationship/client/components/index.scss index a2c6e09d07f..060ec055708 100644 --- a/packages/richtext-lexical/src/features/relationship/client/components/index.scss +++ b/packages/richtext-lexical/src/features/relationship/client/components/index.scss @@ -43,11 +43,6 @@ overflow: hidden; } - &--selected { - box-shadow: $focus-box-shadow; - outline: none; - } - &__doc-drawer-toggler { text-decoration: underline; pointer-events: all; diff --git a/packages/richtext-lexical/src/features/upload/client/component/index.scss b/packages/richtext-lexical/src/features/upload/client/component/index.scss index 400e0b97d2d..022d3c0c33a 100644 --- a/packages/richtext-lexical/src/features/upload/client/component/index.scss +++ b/packages/richtext-lexical/src/features/upload/client/component/index.scss @@ -138,11 +138,6 @@ text-overflow: ellipsis; } - &--selected { - box-shadow: $focus-box-shadow; - outline: none; - } - @include small-break { &__topRowRightPanel { padding: calc(var(--base) * 0.75) calc(var(--base) * 0.5); diff --git a/packages/richtext-lexical/src/features/upload/client/component/index.tsx b/packages/richtext-lexical/src/features/upload/client/component/index.tsx index dffba5f6cd0..b6d4460f570 100644 --- a/packages/richtext-lexical/src/features/upload/client/component/index.tsx +++ b/packages/richtext-lexical/src/features/upload/client/component/index.tsx @@ -2,8 +2,6 @@ import type { ClientCollectionConfig, Data, FormState, JsonObject } from 'payload' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext.js' -import { useLexicalNodeSelection } from '@lexical/react/useLexicalNodeSelection.js' -import { mergeRegister } from '@lexical/utils' import { getTranslation } from '@payloadcms/translations' import { Button, @@ -14,16 +12,8 @@ import { usePayloadAPI, useTranslation, } from '@payloadcms/ui' -import { - $getNodeByKey, - $getSelection, - $isNodeSelection, - CLICK_COMMAND, - COMMAND_PRIORITY_LOW, - KEY_BACKSPACE_COMMAND, - KEY_DELETE_COMMAND, -} from 'lexical' -import React, { useCallback, useEffect, useId, useReducer, useRef, useState } from 'react' +import { $getNodeByKey } from 'lexical' +import React, { useCallback, useId, useReducer, useRef, useState } from 'react' import type { BaseClientFeatureProps } from '../../../typesClient.js' import type { UploadData } from '../../server/nodes/UploadNode.js' @@ -36,7 +26,6 @@ import { useLexicalDocumentDrawer } from '../../../../utilities/fieldsDrawer/use import { useLexicalDrawer } from '../../../../utilities/fieldsDrawer/useLexicalDrawer.js' import { EnabledRelationshipsCondition } from '../../../relationship/client/utils/EnabledRelationshipsCondition.js' import { INSERT_UPLOAD_WITH_DRAWER_COMMAND } from '../drawer/commands.js' -import { $isUploadNode } from '../nodes/UploadNode.js' import './index.scss' const baseClass = 'lexical-upload' @@ -73,7 +62,6 @@ const Component: React.FC = (props) => { const { uuid } = useEditorConfigContext() const editDepth = useEditDepth() const [editor] = useLexicalComposerContext() - const [isSelected, setSelected, clearSelection] = useLexicalNodeSelection(nodeKey) const { editorConfig, @@ -128,55 +116,6 @@ const Component: React.FC = (props) => { [setParams, cacheBust, closeDocumentDrawer], ) - const $onDelete = useCallback( - (event: KeyboardEvent) => { - const deleteSelection = $getSelection() - if (isSelected && $isNodeSelection(deleteSelection)) { - event.preventDefault() - editor.update(() => { - deleteSelection.getNodes().forEach((node) => { - if ($isUploadNode(node)) { - node.remove() - } - }) - }) - } - return false - }, - [editor, isSelected], - ) - - useEffect(() => { - return mergeRegister( - editor.registerCommand( - CLICK_COMMAND, - (event: MouseEvent) => { - // Check if uploadRef.target or anything WITHIN uploadRef.target was clicked - if ( - event.target === uploadRef.current || - uploadRef.current?.contains(event.target as Node) - ) { - if (event.shiftKey) { - setSelected(!isSelected) - } else { - if (!isSelected) { - clearSelection() - setSelected(true) - } - } - return true - } - - return false - }, - COMMAND_PRIORITY_LOW, - ), - - editor.registerCommand(KEY_DELETE_COMMAND, $onDelete, COMMAND_PRIORITY_LOW), - editor.registerCommand(KEY_BACKSPACE_COMMAND, $onDelete, COMMAND_PRIORITY_LOW), - ) - }, [clearSelection, editor, isSelected, nodeKey, $onDelete, setSelected]) - const hasExtraFields = ( editorConfig?.resolvedFeatureMap?.get('upload') ?.sanitizedClientFeatureProps as BaseClientFeatureProps @@ -200,11 +139,7 @@ const Component: React.FC = (props) => { ) return ( -

+
{/* TODO: migrate to use @payloadcms/ui/elements/Thumbnail component */} diff --git a/packages/richtext-lexical/src/lexical/LexicalEditor.tsx b/packages/richtext-lexical/src/lexical/LexicalEditor.tsx index 96e58be2afd..ead987a7436 100644 --- a/packages/richtext-lexical/src/lexical/LexicalEditor.tsx +++ b/packages/richtext-lexical/src/lexical/LexicalEditor.tsx @@ -19,6 +19,7 @@ import type { LexicalProviderProps } from './LexicalProvider.js' import { useEditorConfigContext } from './config/client/EditorConfigProvider.js' import { EditorPlugin } from './EditorPlugin.js' import './LexicalEditor.scss' +import { DecoratorPlugin } from './plugins/DecoratorPlugin/index.js' import { AddBlockHandlePlugin } from './plugins/handles/AddBlockHandlePlugin/index.js' import { DraggableBlockPlugin } from './plugins/handles/DraggableBlockPlugin/index.js' import { InsertParagraphAtEndPlugin } from './plugins/InsertParagraphAtEnd/index.js' @@ -112,6 +113,7 @@ export const LexicalEditor: React.FC< ErrorBoundary={LexicalErrorBoundary} /> + { + const selection = $getSelection() + if (!$isNodeSelection(selection)) { + return false + } + event.preventDefault() + selection.getNodes().forEach((node) => { + node.remove() + }) + return true + } + + useEffect(() => { + return mergeRegister( + editor.registerCommand( + CLICK_COMMAND, + (event) => { + document.querySelector('.decorator-selected')?.classList.remove('decorator-selected') + const decorator = $getDecorator(event) + if (!decorator) { + return true + } + const { decoratorElement, decoratorNode } = decorator + const { target } = event + const isInteractive = + !(target instanceof HTMLElement) || + target.isContentEditable || + target.closest( + 'button, textarea, input, .react-select, .code-editor, .no-select-decorator, [role="button"]', + ) + if (isInteractive) { + $setSelection(null) + } else { + const selection = $createNodeSelection() + selection.add(decoratorNode.getKey()) + $setSelection(selection) + decoratorElement.classList.add('decorator-selected') + } + return true + }, + COMMAND_PRIORITY_LOW, + ), + editor.registerCommand(KEY_DELETE_COMMAND, $onDelete, COMMAND_PRIORITY_LOW), + editor.registerCommand(KEY_BACKSPACE_COMMAND, $onDelete, COMMAND_PRIORITY_LOW), + ) + }, [editor]) + + return null +} + +function $getDecorator( + event: MouseEvent, +): { decoratorElement: Element; decoratorNode: DecoratorNode } | undefined { + if (!(event.target instanceof Element)) { + return undefined + } + const decoratorElement = event.target.closest('[data-lexical-decorator="true"]') + if (!decoratorElement) { + return undefined + } + const node = $getNearestNodeFromDOMNode(decoratorElement) + return $isDecoratorNode(node) ? { decoratorElement, decoratorNode: node } : undefined +} diff --git a/packages/richtext-lexical/src/lexical/plugins/InsertParagraphAtEnd/index.tsx b/packages/richtext-lexical/src/lexical/plugins/InsertParagraphAtEnd/index.tsx index 1edaf8418a2..5510bbecae5 100644 --- a/packages/richtext-lexical/src/lexical/plugins/InsertParagraphAtEnd/index.tsx +++ b/packages/richtext-lexical/src/lexical/plugins/InsertParagraphAtEnd/index.tsx @@ -1,4 +1,3 @@ -/* eslint-disable jsx-a11y/no-static-element-interactions */ /* eslint-disable jsx-a11y/click-events-have-key-events */ 'use client' @@ -27,7 +26,14 @@ export const InsertParagraphAtEndPlugin: React.FC = () => { } return ( -
+ // TODO: convert to button +
+
diff --git a/test/_community/collections/Posts/index.ts b/test/_community/collections/Posts/index.ts index ffed2abbd1e..b38b83d0284 100644 --- a/test/_community/collections/Posts/index.ts +++ b/test/_community/collections/Posts/index.ts @@ -1,5 +1,7 @@ import type { CollectionConfig } from 'payload' +import { lexicalEditor } from '@payloadcms/richtext-lexical' + export const postsSlug = 'posts' export const PostsCollection: CollectionConfig = { @@ -12,6 +14,13 @@ export const PostsCollection: CollectionConfig = { name: 'title', type: 'text', }, + { + name: 'content', + type: 'richText', + editor: lexicalEditor({ + features: ({ defaultFeatures }) => [...defaultFeatures], + }), + }, ], versions: { drafts: true, diff --git a/test/_community/payload-types.ts b/test/_community/payload-types.ts index fd6483846a1..353f19cace0 100644 --- a/test/_community/payload-types.ts +++ b/test/_community/payload-types.ts @@ -70,6 +70,21 @@ export interface UserAuthOperations { export interface Post { id: string; title?: string | null; + richText?: { + 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; _status?: ('draft' | 'published') | null; @@ -202,6 +217,7 @@ export interface PayloadMigration { */ export interface PostsSelect { title?: T; + richText?: T; updatedAt?: T; createdAt?: T; _status?: T; @@ -324,6 +340,23 @@ export interface MenuSelect { createdAt?: T; globalType?: T; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "ContactBlock". + */ +export interface ContactBlock { + /** + * ... + */ + first: string; + /** + * ... + */ + two: string; + id?: string | null; + blockName?: string | null; + blockType: 'contact'; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "auth". diff --git a/test/fields/collections/Lexical/e2e/main/e2e.spec.ts b/test/fields/collections/Lexical/e2e/main/e2e.spec.ts index 250b4c44fc3..3352788f115 100644 --- a/test/fields/collections/Lexical/e2e/main/e2e.spec.ts +++ b/test/fields/collections/Lexical/e2e/main/e2e.spec.ts @@ -4,7 +4,7 @@ import type { SerializedParagraphNode, SerializedTextNode, } from '@payloadcms/richtext-lexical/lexical' -import type { BrowserContext, Page } from '@playwright/test' +import type { BrowserContext, Locator, Page } from '@playwright/test' import { expect, test } from '@playwright/test' import path from 'path' @@ -28,6 +28,7 @@ import { RESTClient } from '../../../../../helpers/rest.js' import { POLL_TOPASS_TIMEOUT, TEST_TIMEOUT_LONG } from '../../../../../playwright.config.js' import { lexicalFieldsSlug } from '../../../../slugs.js' import { lexicalDocData } from '../../data.js' +import { except } from 'drizzle-orm/mysql-core' const filename = fileURLToPath(import.meta.url) const currentFolder = path.dirname(filename) @@ -1294,4 +1295,59 @@ describe('lexicalMain', () => { await navigateToLexicalFields(true, true) }) }) + + test('select decoratorNodes', async () => { + // utils + const decoratorLocator = page.locator('.decorator-selected') // [data-lexical-decorator="true"] + const expectInsideSelectedDecorator = async (innerLocator: Locator) => { + await expect(decoratorLocator).toBeVisible() + await expect(decoratorLocator.locator(innerLocator)).toBeVisible() + } + + // test + await navigateToLexicalFields() + const bottomOfUploadNode = page + .locator('div') + .filter({ hasText: /^payload\.jpg$/ }) + .first() + await bottomOfUploadNode.click() + await expectInsideSelectedDecorator(bottomOfUploadNode) + + const textNode = page.getByText('Upload Node:', { exact: true }) + await textNode.click() + await expect(decoratorLocator).not.toBeVisible() + + const closeTagInMultiSelect = page + .getByRole('button', { name: 'payload.jpg Edit payload.jpg' }) + .getByLabel('Remove') + await closeTagInMultiSelect.click() + await expect(decoratorLocator).not.toBeVisible() + + const labelInsideCollapsableBody = page.locator('label').getByText('Sub Blocks') + await labelInsideCollapsableBody.click() + await expectInsideSelectedDecorator(labelInsideCollapsableBody) + + const textNodeInNestedEditor = page.getByText('Some text below relationship node 1') + await textNodeInNestedEditor.click() + await expect(decoratorLocator).not.toBeVisible() + + await page.getByRole('button', { name: 'Tab2' }).click() + await expect(decoratorLocator).not.toBeVisible() + + const labelInsideCollapsableBody2 = page.getByText('Text2') + await labelInsideCollapsableBody2.click() + await expectInsideSelectedDecorator(labelInsideCollapsableBody2) + + // TEST DELETE! + await page.keyboard.press('Backspace') + await expect(labelInsideCollapsableBody2).not.toBeVisible() + + const monacoLabel = page.locator('label').getByText('Code') + await monacoLabel.click() + await expectInsideSelectedDecorator(monacoLabel) + + const monacoCode = page.getByText('Some code') + await monacoCode.click() + await expect(decoratorLocator).not.toBeVisible() + }) })