feat(richtext-lexical): LexicalPluginToLexical migration feature

This commit is contained in:
Alessio Gravili
2023-10-14 22:36:16 +02:00
parent 52f89c0136
commit d81d4eb075
17 changed files with 1497 additions and 1 deletions

View File

@@ -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'],
}

View File

@@ -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'],
}

View File

@@ -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'],
}

View File

@@ -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'],
}

View File

@@ -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'],
}

View File

@@ -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'],
}

View File

@@ -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'],
}

View File

@@ -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,
]

View File

@@ -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
}

View File

@@ -0,0 +1,26 @@
import type { SerializedEditorState, SerializedLexicalNode } from 'lexical'
export type LexicalPluginNodeConverter<T extends SerializedLexicalNode = SerializedLexicalNode> = {
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
}

View File

@@ -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',
}
}

View File

@@ -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;
}
}

View File

@@ -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<JSX.Element> {
__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 (
<div>
Unknown converted payload-plugin-lexical node: <strong>{this.__data?.nodeType}</strong>
</div>
)
}
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
}

View File

@@ -58,7 +58,11 @@ export class UnknownConvertedNode extends DecoratorNode<JSX.Element> {
}
decorate(): JSX.Element | null {
return <div>Unknown converted Slate node: {this.__data?.nodeType}</div>
return (
<div>
Unknown converted Slate node: <strong>{this.__data?.nodeType}</strong>
</div>
)
}
exportJSON(): SerializedUnknownConvertedNode {

View File

@@ -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,

View File

@@ -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',
},
},
}

View File

@@ -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,
}