From d81d4eb075a19afbc18b21f32a33ca103fc2997b Mon Sep 17 00:00:00 2001 From: Alessio Gravili Date: Sat, 14 Oct 2023 22:36:16 +0200 Subject: [PATCH] feat(richtext-lexical): LexicalPluginToLexical migration feature --- .../converter/converters/heading.ts | 21 + .../converter/converters/link.ts | 36 + .../converter/converters/list.ts | 23 + .../converter/converters/listItem.ts | 23 + .../converter/converters/quote.ts | 21 + .../converter/converters/unknown.ts | 26 + .../converter/converters/upload.ts | 24 + .../converter/defaultConverters.ts | 19 + .../LexicalPluginToLexical/converter/index.ts | 98 ++ .../LexicalPluginToLexical/converter/types.ts | 26 + .../LexicalPluginToLexical/index.ts | 56 + .../nodes/unknownConvertedNode/index.scss | 16 + .../nodes/unknownConvertedNode/index.tsx | 101 ++ .../nodes/unknownConvertedNode/index.tsx | 6 +- packages/richtext-lexical/src/index.ts | 2 + .../generatePayloadPluginLexicalData.ts | 958 ++++++++++++++++++ test/fields/collections/Lexical/index.ts | 42 + 17 files changed, 1497 insertions(+), 1 deletion(-) create mode 100644 packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/converter/converters/heading.ts create mode 100644 packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/converter/converters/link.ts create mode 100644 packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/converter/converters/list.ts create mode 100644 packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/converter/converters/listItem.ts create mode 100644 packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/converter/converters/quote.ts create mode 100644 packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/converter/converters/unknown.ts create mode 100644 packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/converter/converters/upload.ts create mode 100644 packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/converter/defaultConverters.ts create mode 100644 packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/converter/index.ts create mode 100644 packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/converter/types.ts create mode 100644 packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/index.ts create mode 100644 packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/nodes/unknownConvertedNode/index.scss create mode 100644 packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/nodes/unknownConvertedNode/index.tsx create mode 100644 test/fields/collections/Lexical/generatePayloadPluginLexicalData.ts diff --git a/packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/converter/converters/heading.ts b/packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/converter/converters/heading.ts new file mode 100644 index 0000000000..056279cd58 --- /dev/null +++ b/packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/converter/converters/heading.ts @@ -0,0 +1,21 @@ +import type { SerializedHeadingNode } from '@lexical/rich-text' + +import type { LexicalPluginNodeConverter } from '../types' + +import { convertLexicalPluginNodesToLexical } from '..' + +export const HeadingConverter: LexicalPluginNodeConverter = { + converter({ converters, lexicalPluginNode }) { + return { + ...lexicalPluginNode, + children: convertLexicalPluginNodesToLexical({ + converters, + lexicalPluginNodes: (lexicalPluginNode as any).children || [], + parentNodeType: 'heading', + }), + type: 'heading', + version: 1, + } as const as SerializedHeadingNode + }, + nodeTypes: ['heading'], +} diff --git a/packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/converter/converters/link.ts b/packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/converter/converters/link.ts new file mode 100644 index 0000000000..c3dd6a76a4 --- /dev/null +++ b/packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/converter/converters/link.ts @@ -0,0 +1,36 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import type { SerializedLinkNode } from '../../../../Link/nodes/LinkNode' +import type { LexicalPluginNodeConverter } from '../types' + +import { convertLexicalPluginNodesToLexical } from '..' + +export const LinkConverter: LexicalPluginNodeConverter = { + converter({ converters, lexicalPluginNode }) { + return { + children: convertLexicalPluginNodesToLexical({ + converters, + lexicalPluginNodes: (lexicalPluginNode as any).children || [], + parentNodeType: 'link', + }), + direction: (lexicalPluginNode as any).direction || 'ltr', + fields: { + doc: (lexicalPluginNode as any).attributes?.doc + ? { + relationTo: (lexicalPluginNode as any).attributes?.doc?.relationTo, + value: { + id: (lexicalPluginNode as any).attributes?.doc?.value, + }, + } + : undefined, + linkType: (lexicalPluginNode as any).attributes?.linkType || 'custom', + newTab: (lexicalPluginNode as any).attributes?.newTab || false, + url: (lexicalPluginNode as any).attributes?.url || undefined, + }, + format: (lexicalPluginNode as any).format || '', + indent: (lexicalPluginNode as any).indent || 0, + type: 'link', + version: 1, + } as const as SerializedLinkNode + }, + nodeTypes: ['link'], +} diff --git a/packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/converter/converters/list.ts b/packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/converter/converters/list.ts new file mode 100644 index 0000000000..9a7c41f99d --- /dev/null +++ b/packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/converter/converters/list.ts @@ -0,0 +1,23 @@ +import type { SerializedListNode } from '@lexical/list' + +import type { LexicalPluginNodeConverter } from '../types' + +import { convertLexicalPluginNodesToLexical } from '..' + +export const ListConverter: LexicalPluginNodeConverter = { + converter({ converters, lexicalPluginNode }) { + return { + ...lexicalPluginNode, + children: convertLexicalPluginNodesToLexical({ + converters, + lexicalPluginNodes: (lexicalPluginNode as any).children || [], + parentNodeType: 'list', + }), + listType: (lexicalPluginNode as any)?.listType || 'number', + tag: (lexicalPluginNode as any)?.tag || 'ol', + type: 'list', + version: 1, + } as const as SerializedListNode + }, + nodeTypes: ['list'], +} diff --git a/packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/converter/converters/listItem.ts b/packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/converter/converters/listItem.ts new file mode 100644 index 0000000000..4c83af22d3 --- /dev/null +++ b/packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/converter/converters/listItem.ts @@ -0,0 +1,23 @@ +import type { SerializedListItemNode } from '@lexical/list' + +import type { LexicalPluginNodeConverter } from '../types' + +import { convertLexicalPluginNodesToLexical } from '..' + +export const ListItemConverter: LexicalPluginNodeConverter = { + converter({ childIndex, converters, lexicalPluginNode }) { + return { + ...lexicalPluginNode, + checked: undefined, + children: convertLexicalPluginNodesToLexical({ + converters, + lexicalPluginNodes: (lexicalPluginNode as any)?.children || [], + parentNodeType: 'listitem', + }), + type: 'listitem', + value: childIndex + 1, + version: 1, + } as const as SerializedListItemNode + }, + nodeTypes: ['listitem'], +} diff --git a/packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/converter/converters/quote.ts b/packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/converter/converters/quote.ts new file mode 100644 index 0000000000..cb6b956f93 --- /dev/null +++ b/packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/converter/converters/quote.ts @@ -0,0 +1,21 @@ +import type { SerializedHeadingNode } from '@lexical/rich-text' + +import type { LexicalPluginNodeConverter } from '../types' + +import { convertLexicalPluginNodesToLexical } from '..' + +export const QuoteConverter: LexicalPluginNodeConverter = { + converter({ converters, lexicalPluginNode }) { + return { + ...lexicalPluginNode, + children: convertLexicalPluginNodesToLexical({ + converters, + lexicalPluginNodes: (lexicalPluginNode as any).children || [], + parentNodeType: 'quote', + }), + type: 'quote', + version: 1, + } as const as SerializedHeadingNode + }, + nodeTypes: ['quote'], +} diff --git a/packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/converter/converters/unknown.ts b/packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/converter/converters/unknown.ts new file mode 100644 index 0000000000..d19b015558 --- /dev/null +++ b/packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/converter/converters/unknown.ts @@ -0,0 +1,26 @@ +import type { SerializedUnknownConvertedNode } from '../../nodes/unknownConvertedNode' +import type { LexicalPluginNodeConverter } from '../types' + +import { convertLexicalPluginNodesToLexical } from '..' + +export const UnknownConverter: LexicalPluginNodeConverter = { + converter({ converters, lexicalPluginNode }) { + return { + children: convertLexicalPluginNodesToLexical({ + converters, + lexicalPluginNodes: (lexicalPluginNode as any)?.children || [], + parentNodeType: 'unknownConverted', + }), + data: { + nodeData: lexicalPluginNode, + nodeType: lexicalPluginNode.type, + }, + direction: 'ltr', + format: '', + indent: 0, + type: 'unknownConverted', + version: 1, + } as const as SerializedUnknownConvertedNode + }, + nodeTypes: ['unknown'], +} diff --git a/packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/converter/converters/upload.ts b/packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/converter/converters/upload.ts new file mode 100644 index 0000000000..1a7f4753dd --- /dev/null +++ b/packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/converter/converters/upload.ts @@ -0,0 +1,24 @@ +import type { SerializedUploadNode } from '../../../../../..' +import type { LexicalPluginNodeConverter } from '../types' + +export const UploadConverter: LexicalPluginNodeConverter = { + converter({ lexicalPluginNode }) { + let fields = {} + if ((lexicalPluginNode as any)?.caption?.editorState) { + fields = { + caption: (lexicalPluginNode as any)?.caption, + } + } + return { + fields, + format: (lexicalPluginNode as any)?.format || '', + relationTo: (lexicalPluginNode as any)?.rawImagePayload?.relationTo, + type: 'upload', + value: { + id: (lexicalPluginNode as any)?.rawImagePayload?.value?.id || '', + }, + version: 1, + } as const as SerializedUploadNode + }, + nodeTypes: ['upload'], +} diff --git a/packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/converter/defaultConverters.ts b/packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/converter/defaultConverters.ts new file mode 100644 index 0000000000..a93e28d70e --- /dev/null +++ b/packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/converter/defaultConverters.ts @@ -0,0 +1,19 @@ +import type { LexicalPluginNodeConverter } from './types' + +import { HeadingConverter } from './converters/heading' +import { LinkConverter } from './converters/link' +import { ListConverter } from './converters/list' +import { ListItemConverter } from './converters/listItem' +import { QuoteConverter } from './converters/quote' +import { UnknownConverter } from './converters/unknown' +import { UploadConverter } from './converters/upload' + +export const defaultConverters: LexicalPluginNodeConverter[] = [ + UnknownConverter, + UploadConverter, + ListConverter, + ListItemConverter, + LinkConverter, + HeadingConverter, + QuoteConverter, +] diff --git a/packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/converter/index.ts b/packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/converter/index.ts new file mode 100644 index 0000000000..14cc193418 --- /dev/null +++ b/packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/converter/index.ts @@ -0,0 +1,98 @@ +import type { + SerializedEditorState, + SerializedLexicalNode, + SerializedParagraphNode, + SerializedTextNode, +} from 'lexical' + +import type { LexicalPluginNodeConverter, PayloadPluginLexicalData } from './types' + +export function convertLexicalPluginToLexical({ + converters, + lexicalPluginData, +}: { + converters: LexicalPluginNodeConverter[] + lexicalPluginData: PayloadPluginLexicalData +}): SerializedEditorState { + return { + root: { + children: convertLexicalPluginNodesToLexical({ + converters, + lexicalPluginNodes: lexicalPluginData?.jsonContent?.root?.children || [], + parentNodeType: 'root', + }), + direction: lexicalPluginData?.jsonContent?.root?.direction || 'ltr', + format: lexicalPluginData?.jsonContent?.root?.format || '', + indent: lexicalPluginData?.jsonContent?.root?.indent || 0, + type: 'root', + version: 1, + }, + } +} + +export function convertLexicalPluginNodesToLexical({ + converters, + lexicalPluginNodes, + parentNodeType, +}: { + converters: LexicalPluginNodeConverter[] + lexicalPluginNodes: SerializedLexicalNode[] + /** + * Type of the parent lexical node (not the type of the original, parent payload-plugin-lexical type) + */ + parentNodeType: string +}): SerializedLexicalNode[] { + const unknownConverter = converters.find((converter) => converter.nodeTypes.includes('unknown')) + return ( + lexicalPluginNodes.map((lexicalPluginNode, i) => { + if (lexicalPluginNode.type === 'paragraph') { + return convertParagraphNode(converters, lexicalPluginNode) + } + if (lexicalPluginNode.type === 'text' || !lexicalPluginNode.type) { + return convertTextNode(lexicalPluginNode) + } + + const converter = converters.find((converter) => + converter.nodeTypes.includes(lexicalPluginNode.type), + ) + + if (converter) { + return converter.converter({ childIndex: i, converters, lexicalPluginNode, parentNodeType }) + } + + console.warn( + 'lexicalPluginToLexical > No converter found for node type: ' + lexicalPluginNode.type, + ) + return unknownConverter?.converter({ + childIndex: i, + converters, + lexicalPluginNode, + parentNodeType, + }) + }) || [] + ) +} + +export function convertParagraphNode( + converters: LexicalPluginNodeConverter[], + node: SerializedLexicalNode, +): SerializedParagraphNode { + return { + ...node, + children: convertLexicalPluginNodesToLexical({ + converters, + lexicalPluginNodes: (node as any).children || [], + parentNodeType: 'paragraph', + }), + + type: 'paragraph', + version: 1, + } as SerializedParagraphNode +} +export function convertTextNode(node: SerializedLexicalNode): SerializedTextNode { + return node as SerializedTextNode +} + +export function convertNodeToFormat(node: SerializedLexicalNode): number { + return (node as any).format +} diff --git a/packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/converter/types.ts b/packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/converter/types.ts new file mode 100644 index 0000000000..3d2f9b1879 --- /dev/null +++ b/packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/converter/types.ts @@ -0,0 +1,26 @@ +import type { SerializedEditorState, SerializedLexicalNode } from 'lexical' + +export type LexicalPluginNodeConverter = { + converter: ({ + childIndex, + converters, + lexicalPluginNode, + parentNodeType, + }: { + childIndex: number + converters: LexicalPluginNodeConverter[] + lexicalPluginNode: SerializedLexicalNode + parentNodeType: string + }) => T + nodeTypes: string[] +} + +export type PayloadPluginLexicalData = { + characters: number + comments: unknown[] + html?: string + jsonContent: SerializedEditorState + markdown?: string + preview: string + words: number +} diff --git a/packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/index.ts b/packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/index.ts new file mode 100644 index 0000000000..15cb3719a0 --- /dev/null +++ b/packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/index.ts @@ -0,0 +1,56 @@ +import type { FeatureProvider } from '../../types' +import type { LexicalPluginNodeConverter, PayloadPluginLexicalData } from './converter/types' + +import { convertLexicalPluginToLexical } from './converter' +import { defaultConverters } from './converter/defaultConverters' +import { UnknownConvertedNode } from './nodes/unknownConvertedNode' + +type Props = { + converters?: + | (({ + defaultConverters, + }: { + defaultConverters: LexicalPluginNodeConverter[] + }) => LexicalPluginNodeConverter[]) + | LexicalPluginNodeConverter[] +} + +export const LexicalPluginToLexicalFeature = (props?: Props): FeatureProvider => { + if (!props) { + props = {} + } + + props.converters = + props?.converters && typeof props?.converters === 'function' + ? props.converters({ defaultConverters: defaultConverters }) + : (props?.converters as LexicalPluginNodeConverter[]) || defaultConverters + + return { + feature: ({ resolvedFeatures, unsanitizedEditorConfig }) => { + return { + hooks: { + load({ incomingEditorState }) { + if (!incomingEditorState || !('jsonContent' in incomingEditorState)) { + // incomingEditorState null or not from Lexical Plugin + return incomingEditorState + } + // Lexical Plugin => convert to lexical + + return convertLexicalPluginToLexical({ + converters: props.converters as LexicalPluginNodeConverter[], + lexicalPluginData: incomingEditorState as unknown as PayloadPluginLexicalData, + }) + }, + }, + nodes: [ + { + node: UnknownConvertedNode, + type: UnknownConvertedNode.getType(), + }, + ], + props, + } + }, + key: 'lexicalPluginToLexical', + } +} diff --git a/packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/nodes/unknownConvertedNode/index.scss b/packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/nodes/unknownConvertedNode/index.scss new file mode 100644 index 0000000000..7eedad7329 --- /dev/null +++ b/packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/nodes/unknownConvertedNode/index.scss @@ -0,0 +1,16 @@ +@import 'payload/scss'; + +span.unknownConverted { + text-transform: uppercase; + font-family: 'Roboto Mono', monospace; + letter-spacing: 2px; + font-size: base(0.5); + margin: 0 0 base(1); + background: red; + color: white; + display: inline-block; + + div { + background: red; + } +} diff --git a/packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/nodes/unknownConvertedNode/index.tsx b/packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/nodes/unknownConvertedNode/index.tsx new file mode 100644 index 0000000000..a37db47f2f --- /dev/null +++ b/packages/richtext-lexical/src/field/features/migrations/LexicalPluginToLexical/nodes/unknownConvertedNode/index.tsx @@ -0,0 +1,101 @@ +import type { SerializedLexicalNode, Spread } from 'lexical' + +import { addClassNamesToElement } from '@lexical/utils' +import { DecoratorNode, type EditorConfig, type LexicalNode, type NodeKey } from 'lexical' +import React from 'react' + +import './index.scss' + +export type UnknownConvertedNodeData = { + nodeData: unknown + nodeType: string +} + +export type SerializedUnknownConvertedNode = Spread< + { + data: UnknownConvertedNodeData + }, + SerializedLexicalNode +> + +/** @noInheritDoc */ +export class UnknownConvertedNode extends DecoratorNode { + __data: UnknownConvertedNodeData + + constructor({ data, key }: { data: UnknownConvertedNodeData; key?: NodeKey }) { + super(key) + this.__data = data + } + + static clone(node: UnknownConvertedNode): UnknownConvertedNode { + return new UnknownConvertedNode({ + data: node.__data, + key: node.__key, + }) + } + + static getType(): string { + return 'unknownConverted' + } + + static importJSON(serializedNode: SerializedUnknownConvertedNode): UnknownConvertedNode { + const node = $createUnknownConvertedNode({ data: serializedNode.data }) + return node + } + + canInsertTextAfter(): true { + return true + } + + canInsertTextBefore(): true { + return true + } + + createDOM(config: EditorConfig): HTMLElement { + const element = document.createElement('span') + addClassNamesToElement(element, 'unknownConverted') + return element + } + + decorate(): JSX.Element | null { + return ( +
+ Unknown converted payload-plugin-lexical node: {this.__data?.nodeType} +
+ ) + } + + exportJSON(): SerializedUnknownConvertedNode { + return { + data: this.__data, + type: this.getType(), + version: 1, + } + } + + // Mutation + + isInline(): boolean { + return true + } + + updateDOM(prevNode: UnknownConvertedNode, dom: HTMLElement): boolean { + return false + } +} + +export function $createUnknownConvertedNode({ + data, +}: { + data: UnknownConvertedNodeData +}): UnknownConvertedNode { + return new UnknownConvertedNode({ + data, + }) +} + +export function $isUnknownConvertedNode( + node: LexicalNode | null | undefined, +): node is UnknownConvertedNode { + return node instanceof UnknownConvertedNode +} diff --git a/packages/richtext-lexical/src/field/features/migrations/SlateToLexical/nodes/unknownConvertedNode/index.tsx b/packages/richtext-lexical/src/field/features/migrations/SlateToLexical/nodes/unknownConvertedNode/index.tsx index dffbc0cae3..6ed05e93a0 100644 --- a/packages/richtext-lexical/src/field/features/migrations/SlateToLexical/nodes/unknownConvertedNode/index.tsx +++ b/packages/richtext-lexical/src/field/features/migrations/SlateToLexical/nodes/unknownConvertedNode/index.tsx @@ -58,7 +58,11 @@ export class UnknownConvertedNode extends DecoratorNode { } decorate(): JSX.Element | null { - return
Unknown converted Slate node: {this.__data?.nodeType}
+ return ( +
+ Unknown converted Slate node: {this.__data?.nodeType} +
+ ) } exportJSON(): SerializedUnknownConvertedNode { diff --git a/packages/richtext-lexical/src/index.ts b/packages/richtext-lexical/src/index.ts index 3f53196969..f0b524774d 100644 --- a/packages/richtext-lexical/src/index.ts +++ b/packages/richtext-lexical/src/index.ts @@ -153,7 +153,9 @@ export { IndentFeature } from './field/features/indent' export { CheckListFeature } from './field/features/lists/CheckList' export { OrderedListFeature } from './field/features/lists/OrderedList' export { UnoderedListFeature } from './field/features/lists/UnorderedList' +export { LexicalPluginToLexicalFeature } from './field/features/migrations/LexicalPluginToLexical' export { SlateToLexicalFeature } from './field/features/migrations/SlateToLexical' + export type { AfterReadPromise, Feature, diff --git a/test/fields/collections/Lexical/generatePayloadPluginLexicalData.ts b/test/fields/collections/Lexical/generatePayloadPluginLexicalData.ts new file mode 100644 index 0000000000..31aacd6b40 --- /dev/null +++ b/test/fields/collections/Lexical/generatePayloadPluginLexicalData.ts @@ -0,0 +1,958 @@ +export const payloadPluginLexicalData = { + words: 49, + preview: + 'paragraph text bold italic underline and all subscript superscript code internal link external link…', + comments: [], + characters: 493, + jsonContent: { + root: { + type: 'root', + format: '', + indent: 0, + version: 1, + children: [ + { + children: [ + { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: 'paragraph text ', + type: 'text', + version: 1, + }, + { + detail: 0, + format: 1, + mode: 'normal', + style: '', + text: 'bold', + type: 'text', + version: 1, + }, + { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: ' ', + type: 'text', + version: 1, + }, + { + detail: 0, + format: 2, + mode: 'normal', + style: '', + text: 'italic', + type: 'text', + version: 1, + }, + { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: ' ', + type: 'text', + version: 1, + }, + { + detail: 0, + format: 8, + mode: 'normal', + style: '', + text: 'underline', + type: 'text', + version: 1, + }, + { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: ' and ', + type: 'text', + version: 1, + }, + { + detail: 0, + format: 11, + mode: 'normal', + style: '', + text: 'all', + type: 'text', + version: 1, + }, + { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: ' ', + type: 'text', + version: 1, + }, + { + detail: 0, + format: 32, + mode: 'normal', + style: '', + text: 'subscript', + type: 'text', + version: 1, + }, + { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: ' ', + type: 'text', + version: 1, + }, + { + detail: 0, + format: 64, + mode: 'normal', + style: '', + text: 'superscript', + type: 'text', + version: 1, + }, + { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: ' ', + type: 'text', + version: 1, + }, + { + detail: 0, + format: 16, + mode: 'normal', + style: '', + text: 'code', + type: 'text', + version: 1, + }, + { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: ' ', + type: 'text', + version: 1, + }, + { + children: [ + { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: 'internal link', + type: 'text', + version: 1, + }, + ], + direction: 'ltr', + format: '', + indent: 0, + type: 'link', + version: 2, + attributes: { + newTab: true, + linkType: 'internal', + doc: { + value: '{{TEXT_DOC_ID}}', + relationTo: 'text-fields', + data: {}, // populated data + }, + text: 'internal link', + }, + }, + { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: ' ', + type: 'text', + version: 1, + }, + { + children: [ + { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: 'external link', + type: 'text', + version: 1, + }, + ], + direction: 'ltr', + format: '', + indent: 0, + type: 'link', + version: 2, + attributes: { + newTab: true, + nofollow: false, + url: 'https://fewfwef.de', + linkType: 'custom', + text: 'external link', + }, + }, + { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: ' s. ', + type: 'text', + version: 1, + }, + { + detail: 0, + format: 4, + mode: 'normal', + style: '', + text: 'strikethrough', + type: 'text', + version: 1, + }, + ], + direction: 'ltr', + format: '', + indent: 0, + type: 'paragraph', + version: 1, + }, + { + children: [ + { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: 'heading 1', + type: 'text', + version: 1, + }, + ], + direction: 'ltr', + format: '', + indent: 0, + type: 'heading', + version: 1, + tag: 'h1', + }, + { + children: [ + { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: 'heading 2', + type: 'text', + version: 1, + }, + ], + direction: 'ltr', + format: '', + indent: 0, + type: 'heading', + version: 1, + tag: 'h2', + }, + { + children: [ + { + children: [ + { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: 'bullet list ', + type: 'text', + version: 1, + }, + ], + direction: 'ltr', + format: '', + indent: 0, + type: 'listitem', + version: 1, + value: 1, + }, + { + children: [ + { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: 'item 2', + type: 'text', + version: 1, + }, + ], + direction: 'ltr', + format: '', + indent: 0, + type: 'listitem', + version: 1, + value: 2, + }, + { + children: [ + { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: 'item 3', + type: 'text', + version: 1, + }, + ], + direction: 'ltr', + format: '', + indent: 0, + type: 'listitem', + version: 1, + value: 3, + }, + ], + direction: 'ltr', + format: '', + indent: 0, + type: 'list', + version: 1, + listType: 'bullet', + start: 1, + tag: 'ul', + }, + { + children: [ + { + children: [ + { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: 'ordered list', + type: 'text', + version: 1, + }, + ], + direction: 'ltr', + format: '', + indent: 0, + type: 'listitem', + version: 1, + value: 1, + }, + { + children: [ + { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: 'item 2', + type: 'text', + version: 1, + }, + ], + direction: 'ltr', + format: '', + indent: 0, + type: 'listitem', + version: 1, + value: 2, + }, + { + children: [ + { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: 'item 3', + type: 'text', + version: 1, + }, + ], + direction: 'ltr', + format: '', + indent: 0, + type: 'listitem', + version: 1, + value: 3, + }, + ], + direction: 'ltr', + format: '', + indent: 0, + type: 'list', + version: 1, + listType: 'number', + start: 1, + tag: 'ol', + }, + { + children: [ + { + children: [ + { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: 'check list', + type: 'text', + version: 1, + }, + ], + direction: 'ltr', + format: '', + indent: 0, + type: 'listitem', + version: 1, + value: 1, + }, + { + children: [ + { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: 'item 2', + type: 'text', + version: 1, + }, + ], + direction: 'ltr', + format: '', + indent: 0, + type: 'listitem', + version: 1, + value: 2, + }, + { + children: [ + { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: 'item 3', + type: 'text', + version: 1, + }, + ], + direction: 'ltr', + format: '', + indent: 0, + type: 'listitem', + version: 1, + value: 3, + }, + ], + direction: 'ltr', + format: '', + indent: 0, + type: 'list', + version: 1, + listType: 'check', + start: 1, + tag: 'ul', + }, + { + children: [ + { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: 'quoteeee', + type: 'text', + version: 1, + }, + ], + direction: 'ltr', + format: '', + indent: 0, + type: 'quote', + version: 1, + }, + { + children: [ + { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: 'code block line ', + type: 'code-highlight', + version: 1, + }, + { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: '1', + type: 'code-highlight', + version: 1, + highlightType: 'number', + }, + { + type: 'linebreak', + version: 1, + }, + { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: 'code block line ', + type: 'code-highlight', + version: 1, + }, + { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: '2', + type: 'code-highlight', + version: 1, + highlightType: 'number', + }, + ], + direction: 'ltr', + format: '', + indent: 0, + type: 'code', + version: 1, + language: 'javascript', + }, + { + children: [ + { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: 'Upload:', + type: 'text', + version: 1, + }, + ], + direction: 'ltr', + format: '', + indent: 0, + type: 'paragraph', + version: 1, + }, + { + children: [ + { + type: 'upload', + version: 1, + rawImagePayload: { + value: { + id: '{{UPLOAD_DOC_ID}}', + }, + relationTo: 'uploads', + }, + caption: { + editorState: { + root: { + children: [ + { + children: [ + { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: 'upload caption', + type: 'text', + version: 1, + }, + ], + direction: 'ltr', + format: '', + indent: 0, + type: 'paragraph', + version: 1, + }, + ], + direction: 'ltr', + format: '', + indent: 0, + type: 'root', + version: 1, + }, + }, + }, + showCaption: true, + data: {}, // populated upload data + }, + ], + direction: null, + format: '', + indent: 0, + type: 'paragraph', + version: 1, + }, + { + children: [], + direction: null, + format: '', + indent: 0, + type: 'paragraph', + version: 1, + }, + { + children: [ + { + children: [ + { + children: [ + { + children: [ + { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: '2x2 table top left', + type: 'text', + version: 1, + }, + ], + direction: 'ltr', + format: '', + indent: 0, + type: 'paragraph', + version: 1, + }, + ], + direction: null, + format: '', + indent: 0, + type: 'tablecell', + version: 1, + colSpan: 1, + rowSpan: 1, + backgroundColor: null, + headerState: 3, + }, + { + children: [ + { + children: [ + { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: '2x2 table top right', + type: 'text', + version: 1, + }, + ], + direction: 'ltr', + format: '', + indent: 0, + type: 'paragraph', + version: 1, + }, + ], + direction: null, + format: '', + indent: 0, + type: 'tablecell', + version: 1, + colSpan: 1, + rowSpan: 1, + backgroundColor: null, + headerState: 1, + }, + ], + direction: null, + format: '', + indent: 0, + type: 'tablerow', + version: 1, + }, + { + children: [ + { + children: [ + { + children: [ + { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: '2x2 table bottom left', + type: 'text', + version: 1, + }, + ], + direction: 'ltr', + format: '', + indent: 0, + type: 'paragraph', + version: 1, + }, + ], + direction: null, + format: '', + indent: 0, + type: 'tablecell', + version: 1, + colSpan: 1, + rowSpan: 1, + backgroundColor: null, + headerState: 2, + }, + { + children: [ + { + children: [ + { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: '2x2 table bottom right', + type: 'text', + version: 1, + }, + ], + direction: 'ltr', + format: '', + indent: 0, + type: 'paragraph', + version: 1, + }, + ], + direction: null, + format: '', + indent: 0, + type: 'tablecell', + version: 1, + colSpan: 1, + rowSpan: 1, + backgroundColor: null, + headerState: 0, + }, + ], + direction: null, + format: '', + indent: 0, + type: 'tablerow', + version: 1, + }, + ], + direction: null, + format: '', + indent: 0, + type: 'table', + version: 1, + }, + { + rows: [ + { + cells: [ + { + colSpan: 1, + id: 'kafuj', + json: '{"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}', + type: 'header', + width: null, + }, + { + colSpan: 1, + id: 'iussu', + json: '{"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}', + type: 'header', + width: null, + }, + ], + height: null, + id: 'tnied', + }, + { + cells: [ + { + colSpan: 1, + id: 'hpnnv', + json: '{"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}', + type: 'header', + width: null, + }, + { + colSpan: 1, + id: 'ndteg', + json: '{"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}', + type: 'normal', + width: null, + }, + ], + height: null, + id: 'rxyey', + }, + { + cells: [ + { + colSpan: 1, + id: 'rtueq', + json: '{"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}', + type: 'header', + width: null, + }, + { + colSpan: 1, + id: 'vrzoi', + json: '{"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}', + type: 'normal', + width: null, + }, + ], + height: null, + id: 'qzglv', + }, + ], + type: 'tablesheet', + version: 1, + }, + { + children: [ + { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: 'youtube:', + type: 'text', + version: 1, + }, + ], + direction: 'ltr', + format: '', + indent: 0, + type: 'paragraph', + version: 1, + }, + { + format: '', + type: 'youtube', + version: 1, + videoID: '3Nwt3qu0_UY', + }, + { + children: [ + { + equation: '3+3', + inline: true, + type: 'equation', + version: 1, + }, + ], + direction: null, + format: '', + indent: 0, + type: 'paragraph', + version: 1, + }, + { + children: [ + { + children: [ + { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: 'collapsible title', + type: 'text', + version: 1, + }, + ], + direction: 'ltr', + format: '', + indent: 0, + type: 'collapsible-title', + version: 1, + }, + { + children: [ + { + children: [ + { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: 'collabsible conteent', + type: 'text', + version: 1, + }, + ], + direction: 'ltr', + format: '', + indent: 0, + type: 'paragraph', + version: 1, + }, + ], + direction: 'ltr', + format: '', + indent: 0, + type: 'collapsible-content', + version: 1, + }, + ], + direction: 'ltr', + format: '', + indent: 0, + type: 'collapsible-container', + version: 1, + open: true, + }, + { + children: [], + direction: null, + format: '', + indent: 0, + type: 'paragraph', + version: 1, + }, + { + type: 'horizontalrule', + version: 1, + }, + ], + direction: 'ltr', + }, + }, +} diff --git a/test/fields/collections/Lexical/index.ts b/test/fields/collections/Lexical/index.ts index 914442b0fa..31408e7085 100644 --- a/test/fields/collections/Lexical/index.ts +++ b/test/fields/collections/Lexical/index.ts @@ -2,6 +2,7 @@ import type { CollectionConfig } from '../../../../packages/payload/src/collecti import { BlocksFeature, + LexicalPluginToLexicalFeature, LinkFeature, TreeviewFeature, UploadFeature, @@ -16,6 +17,7 @@ import { UploadAndRichTextBlock, } from './blocks' import { generateLexicalRichText } from './generateLexicalRichText' +import { payloadPluginLexicalData } from './generatePayloadPluginLexicalData' export const LexicalFields: CollectionConfig = { slug: 'lexical-fields', @@ -32,6 +34,45 @@ export const LexicalFields: CollectionConfig = { type: 'text', required: true, }, + { + name: 'richTextLexicalWithLexicalPluginData', + type: 'richText', + editor: lexicalEditor({ + features: ({ defaultFeatures }) => [ + ...defaultFeatures, + LexicalPluginToLexicalFeature(), + TreeviewFeature(), + LinkFeature({ + fields: [ + { + name: 'rel', + label: 'Rel Attribute', + type: 'select', + hasMany: true, + options: ['noopener', 'noreferrer', 'nofollow'], + admin: { + description: + 'The rel attribute defines the relationship between a linked resource and the current document. This is a custom link field.', + }, + }, + ], + }), + UploadFeature({ + collections: { + uploads: { + fields: [ + { + name: 'caption', + type: 'richText', + editor: lexicalEditor(), + }, + ], + }, + }, + }), + ], + }), + }, { name: 'richTextLexicalCustomFields', type: 'richText', @@ -87,4 +128,5 @@ export const LexicalFields: CollectionConfig = { export const LexicalRichTextDoc = { title: 'Rich Text', richTextLexicalCustomFields: generateLexicalRichText(), + richTextLexicalWithLexicalPluginData: payloadPluginLexicalData, }