feat(richtext-lexical): slateToLexicalFeature

This commit is contained in:
Alessio Gravili
2024-03-01 13:38:39 -05:00
parent 1a4b6a66ae
commit 27ea4f76f0
39 changed files with 312 additions and 156 deletions

View File

@@ -1,7 +1,7 @@
'use client'
import type { FeatureProviderProviderClient } from '../../types'
import type { PayloadPluginLexicalData } from './converter/types'
import type { LexicalPluginNodeConverter, PayloadPluginLexicalData } from './converter/types'
import { createClientComponent } from '../../createClientComponent'
import { convertLexicalPluginToLexical } from './converter'
@@ -11,7 +11,7 @@ const LexicalPluginToLexicalFeatureClient: FeatureProviderProviderClient<null> =
return {
clientFeatureProps: props,
feature: ({ clientFunctions, resolvedFeatures }) => {
const converters = Object.values(clientFunctions)
const converters: LexicalPluginNodeConverter[] = Object.values(clientFunctions)
return {
clientFeatureProps: props,

View File

@@ -29,7 +29,9 @@ export const LexicalPluginToLexicalFeature: FeatureProviderProviderServer<
if (props?.converters && typeof props?.converters === 'function') {
converters = props.converters({ defaultConverters })
} else if (!props?.converters) {
} else if (props.converters && typeof props?.converters !== 'function') {
converters = props.converters
} else {
converters = defaultConverters
}

View File

@@ -0,0 +1,6 @@
'use client'
import { createFeaturePropComponent } from '../../../../../createFeaturePropComponent'
import { _SlateBlockquoteConverter } from './converter'
export const BlockQuoteConverterClient = createFeaturePropComponent(_SlateBlockquoteConverter)

View File

@@ -1,10 +1,10 @@
import type { SerializedQuoteNode } from '@lexical/rich-text'
import type { SlateNodeConverter } from '../types'
import type { SlateNodeConverter } from '../../types'
import { convertSlateNodesToLexical } from '../index'
import { convertSlateNodesToLexical } from '../..'
export const SlateBlockquoteConverter: SlateNodeConverter = {
export const _SlateBlockquoteConverter: SlateNodeConverter = {
converter({ converters, slateNode }) {
return {
type: 'quote',
@@ -12,7 +12,7 @@ export const SlateBlockquoteConverter: SlateNodeConverter = {
canContainParagraphs: false,
converters,
parentNodeType: 'quote',
slateNodes: slateNode.children || [],
slateNodes: slateNode.children,
}),
direction: 'ltr',
format: '',

View File

@@ -0,0 +1,9 @@
import type { SlateNodeConverterProvider } from '../../types'
import { BlockQuoteConverterClient } from './client'
import { _SlateBlockquoteConverter } from './converter'
export const SlateBlockquoteConverter: SlateNodeConverterProvider = {
ClientComponent: BlockQuoteConverterClient,
converter: _SlateBlockquoteConverter,
}

View File

@@ -0,0 +1,5 @@
'use client'
import { createFeaturePropComponent } from '../../../../../createFeaturePropComponent'
import { _SlateHeadingConverter } from './converter'
export const HeadingConverterClient = createFeaturePropComponent(_SlateHeadingConverter)

View File

@@ -1,10 +1,10 @@
import type { SerializedHeadingNode } from '@lexical/rich-text'
import type { SlateNodeConverter } from '../types'
import type { SlateNodeConverter } from '../../types'
import { convertSlateNodesToLexical } from '..'
import { convertSlateNodesToLexical } from '../..'
export const SlateHeadingConverter: SlateNodeConverter = {
export const _SlateHeadingConverter: SlateNodeConverter = {
converter({ converters, slateNode }) {
return {
type: 'heading',
@@ -12,7 +12,7 @@ export const SlateHeadingConverter: SlateNodeConverter = {
canContainParagraphs: false,
converters,
parentNodeType: 'heading',
slateNodes: slateNode.children || [],
slateNodes: slateNode.children,
}),
direction: 'ltr',
format: '',

View File

@@ -0,0 +1,9 @@
import type { SlateNodeConverterProvider } from '../../types'
import { HeadingConverterClient } from './client'
import { _SlateHeadingConverter } from './converter'
export const SlateHeadingConverter: SlateNodeConverterProvider = {
ClientComponent: HeadingConverterClient,
converter: _SlateHeadingConverter,
}

View File

@@ -1,65 +0,0 @@
import type { SerializedLexicalNode, SerializedParagraphNode } from 'lexical'
import type { SlateNodeConverter } from '../types'
import { convertSlateNodesToLexical } from '..'
export const SlateIndentConverter: SlateNodeConverter = {
converter({ converters, slateNode }) {
console.log('slateToLexical > IndentConverter > converter', JSON.stringify(slateNode, null, 2))
const convertChildren = (node: any, indentLevel: number = 0): SerializedLexicalNode => {
if (
(node?.type && (!node.children || node.type !== 'indent')) ||
(!node?.type && node?.text)
) {
console.log(
'slateToLexical > IndentConverter > convertChildren > node',
JSON.stringify(node, null, 2),
)
console.log(
'slateToLexical > IndentConverter > convertChildren > nodeOutput',
JSON.stringify(
convertSlateNodesToLexical({
canContainParagraphs: false,
converters,
parentNodeType: 'indent',
slateNodes: [node],
}),
null,
2,
),
)
return {
...convertSlateNodesToLexical({
canContainParagraphs: false,
converters,
parentNodeType: 'indent',
slateNodes: [node],
})[0],
indent: indentLevel,
} as const as SerializedLexicalNode
}
const children = node.children.map((child: any) => convertChildren(child, indentLevel + 1))
console.log('slateToLexical > IndentConverter > children', JSON.stringify(children, null, 2))
return {
type: 'paragraph',
children: children,
direction: 'ltr',
format: '',
indent: indentLevel,
version: 1,
} as const as SerializedParagraphNode
}
console.log(
'slateToLexical > IndentConverter > output',
JSON.stringify(convertChildren(slateNode), null, 2),
)
return convertChildren(slateNode)
},
nodeTypes: ['indent'],
}

View File

@@ -0,0 +1,6 @@
'use client'
import { createFeaturePropComponent } from '../../../../../createFeaturePropComponent'
import { _SlateIndentConverter } from './converter'
export const IndentConverterClient = createFeaturePropComponent(_SlateIndentConverter)

View File

@@ -0,0 +1,39 @@
import type { SerializedLexicalNode, SerializedParagraphNode } from 'lexical'
import type { SlateNodeConverter } from '../../types'
import { convertSlateNodesToLexical } from '../..'
export const _SlateIndentConverter: SlateNodeConverter = {
converter({ converters, slateNode }) {
const convertChildren = (node: any, indentLevel: number = 0): SerializedLexicalNode => {
if (
(node?.type && (!node.children || node.type !== 'indent')) ||
(!node?.type && node?.text)
) {
return {
...convertSlateNodesToLexical({
canContainParagraphs: false,
converters,
parentNodeType: 'indent',
slateNodes: [node],
})[0],
indent: indentLevel,
} as const as SerializedLexicalNode
}
const children = node.children.map((child: any) => convertChildren(child, indentLevel + 1))
return {
type: 'paragraph',
children,
direction: 'ltr',
format: '',
indent: indentLevel,
version: 1,
} as const as SerializedParagraphNode
}
return convertChildren(slateNode)
},
nodeTypes: ['indent'],
}

View File

@@ -0,0 +1,9 @@
import type { SlateNodeConverterProvider } from '../../types'
import { IndentConverterClient } from './client'
import { _SlateIndentConverter } from './converter'
export const SlateIndentConverter: SlateNodeConverterProvider = {
ClientComponent: IndentConverterClient,
converter: _SlateIndentConverter,
}

View File

@@ -0,0 +1,6 @@
'use client'
import { createFeaturePropComponent } from '../../../../../createFeaturePropComponent'
import { _SlateLinkConverter } from './converter'
export const LinkConverterClient = createFeaturePropComponent(_SlateLinkConverter)

View File

@@ -1,9 +1,9 @@
import type { SerializedLinkNode } from '../../../../link/nodes/LinkNode'
import type { SlateNodeConverter } from '../types'
import type { SerializedLinkNode } from '../../../../../link/nodes/types'
import type { SlateNodeConverter } from '../../types'
import { convertSlateNodesToLexical } from '..'
import { convertSlateNodesToLexical } from '../..'
export const SlateLinkConverter: SlateNodeConverter = {
export const _SlateLinkConverter: SlateNodeConverter = {
converter({ converters, slateNode }) {
return {
type: 'link',
@@ -11,7 +11,7 @@ export const SlateLinkConverter: SlateNodeConverter = {
canContainParagraphs: false,
converters,
parentNodeType: 'link',
slateNodes: slateNode.children || [],
slateNodes: slateNode.children,
}),
direction: 'ltr',
fields: {

View File

@@ -0,0 +1,9 @@
import type { SlateNodeConverterProvider } from '../../types'
import { LinkConverterClient } from './client'
import { _SlateLinkConverter } from './converter'
export const SlateLinkConverter: SlateNodeConverterProvider = {
ClientComponent: LinkConverterClient,
converter: _SlateLinkConverter,
}

View File

@@ -0,0 +1,6 @@
'use client'
import { createFeaturePropComponent } from '../../../../../createFeaturePropComponent'
import { _SlateListItemConverter } from './converter'
export const ListItemConverterClient = createFeaturePropComponent(_SlateListItemConverter)

View File

@@ -1,10 +1,10 @@
import type { SerializedListItemNode } from '@lexical/list'
import type { SlateNodeConverter } from '../types'
import type { SlateNodeConverter } from '../../types'
import { convertSlateNodesToLexical } from '..'
import { convertSlateNodesToLexical } from '../..'
export const SlateListItemConverter: SlateNodeConverter = {
export const _SlateListItemConverter: SlateNodeConverter = {
converter({ childIndex, converters, slateNode }) {
return {
type: 'listitem',
@@ -13,7 +13,7 @@ export const SlateListItemConverter: SlateNodeConverter = {
canContainParagraphs: false,
converters,
parentNodeType: 'listitem',
slateNodes: slateNode.children || [],
slateNodes: slateNode.children,
}),
direction: 'ltr',
format: '',

View File

@@ -0,0 +1,9 @@
import type { SlateNodeConverterProvider } from '../../types'
import { ListItemConverterClient } from './client'
import { _SlateListItemConverter } from './converter'
export const SlateListItemConverter: SlateNodeConverterProvider = {
ClientComponent: ListItemConverterClient,
converter: _SlateListItemConverter,
}

View File

@@ -0,0 +1,6 @@
'use client'
import { createFeaturePropComponent } from '../../../../../createFeaturePropComponent'
import { _SlateOrderedListConverter } from './converter'
export const OrderedListConverterClient = createFeaturePropComponent(_SlateOrderedListConverter)

View File

@@ -1,10 +1,10 @@
import type { SerializedListNode } from '@lexical/list'
import type { SlateNodeConverter } from '../types'
import type { SlateNodeConverter } from '../../types'
import { convertSlateNodesToLexical } from '..'
import { convertSlateNodesToLexical } from '../..'
export const SlateOrderedListConverter: SlateNodeConverter = {
export const _SlateOrderedListConverter: SlateNodeConverter = {
converter({ converters, slateNode }) {
return {
type: 'list',
@@ -12,7 +12,7 @@ export const SlateOrderedListConverter: SlateNodeConverter = {
canContainParagraphs: false,
converters,
parentNodeType: 'list',
slateNodes: slateNode.children || [],
slateNodes: slateNode.children,
}),
direction: 'ltr',
format: '',

View File

@@ -0,0 +1,9 @@
import type { SlateNodeConverterProvider } from '../../types'
import { OrderedListConverterClient } from './client'
import { _SlateOrderedListConverter } from './converter'
export const SlateOrderedListConverter: SlateNodeConverterProvider = {
ClientComponent: OrderedListConverterClient,
converter: _SlateOrderedListConverter,
}

View File

@@ -0,0 +1,6 @@
'use client'
import { createFeaturePropComponent } from '../../../../../createFeaturePropComponent'
import { _SlateRelationshipConverter } from './converter'
export const RelationshipConverterClient = createFeaturePropComponent(_SlateRelationshipConverter)

View File

@@ -1,7 +1,7 @@
import type { SerializedRelationshipNode } from '../../../../../..'
import type { SlateNodeConverter } from '../types'
import type { SerializedRelationshipNode } from '../../../../../relationship/nodes/RelationshipNode'
import type { SlateNodeConverter } from '../../types'
export const SlateRelationshipConverter: SlateNodeConverter = {
export const _SlateRelationshipConverter: SlateNodeConverter = {
converter({ slateNode }) {
return {
type: 'relationship',

View File

@@ -0,0 +1,9 @@
import type { SlateNodeConverterProvider } from '../../types'
import { RelationshipConverterClient } from './client'
import { _SlateRelationshipConverter } from './converter'
export const SlateRelationshipConverter: SlateNodeConverterProvider = {
ClientComponent: RelationshipConverterClient,
converter: _SlateRelationshipConverter,
}

View File

@@ -0,0 +1,6 @@
'use client'
import { createFeaturePropComponent } from '../../../../../createFeaturePropComponent'
import { _SlateUnknownConverter } from './converter'
export const UnknownConverterClient = createFeaturePropComponent(_SlateUnknownConverter)

View File

@@ -1,9 +1,9 @@
import type { SerializedUnknownConvertedNode } from '../../nodes/unknownConvertedNode'
import type { SlateNodeConverter } from '../types'
import type { SerializedUnknownConvertedNode } from '../../../nodes/unknownConvertedNode'
import type { SlateNodeConverter } from '../../types'
import { convertSlateNodesToLexical } from '..'
import { convertSlateNodesToLexical } from '../..'
export const SlateUnknownConverter: SlateNodeConverter = {
export const _SlateUnknownConverter: SlateNodeConverter = {
converter({ converters, slateNode }) {
return {
type: 'unknownConverted',
@@ -11,7 +11,7 @@ export const SlateUnknownConverter: SlateNodeConverter = {
canContainParagraphs: false,
converters,
parentNodeType: 'unknownConverted',
slateNodes: slateNode.children || [],
slateNodes: slateNode.children,
}),
data: {
nodeData: slateNode,

View File

@@ -0,0 +1,9 @@
import type { SlateNodeConverterProvider } from '../../types'
import { UnknownConverterClient } from './client'
import { _SlateUnknownConverter } from './converter'
export const SlateUnknownConverter: SlateNodeConverterProvider = {
ClientComponent: UnknownConverterClient,
converter: _SlateUnknownConverter,
}

View File

@@ -0,0 +1,6 @@
'use client'
import { createFeaturePropComponent } from '../../../../../createFeaturePropComponent'
import { _SlateUnorderedListConverter } from './converter'
export const UnorderedListConverterClient = createFeaturePropComponent(_SlateUnorderedListConverter)

View File

@@ -1,10 +1,10 @@
import type { SerializedListNode } from '@lexical/list'
import type { SlateNodeConverter } from '../types'
import type { SlateNodeConverter } from '../../types'
import { convertSlateNodesToLexical } from '..'
import { convertSlateNodesToLexical } from '../..'
export const SlateUnorderedListConverter: SlateNodeConverter = {
export const _SlateUnorderedListConverter: SlateNodeConverter = {
converter({ converters, slateNode }) {
return {
type: 'list',
@@ -12,7 +12,7 @@ export const SlateUnorderedListConverter: SlateNodeConverter = {
canContainParagraphs: false,
converters,
parentNodeType: 'list',
slateNodes: slateNode.children || [],
slateNodes: slateNode.children,
}),
direction: 'ltr',
format: '',

View File

@@ -0,0 +1,9 @@
import type { SlateNodeConverterProvider } from '../../types'
import { UnorderedListConverterClient } from './client'
import { _SlateUnorderedListConverter } from './converter'
export const SlateUnorderedListConverter: SlateNodeConverterProvider = {
ClientComponent: UnorderedListConverterClient,
converter: _SlateUnorderedListConverter,
}

View File

@@ -0,0 +1,6 @@
'use client'
import { createFeaturePropComponent } from '../../../../../createFeaturePropComponent'
import { _SlateUploadConverter } from './converter'
export const UploadConverterClient = createFeaturePropComponent(_SlateUploadConverter)

View File

@@ -1,7 +1,7 @@
import type { SerializedUploadNode } from '../../../../../..'
import type { SlateNodeConverter } from '../types'
import type { SerializedUploadNode } from '../../../../../upload/nodes/UploadNode'
import type { SlateNodeConverter } from '../../types'
export const SlateUploadConverter: SlateNodeConverter = {
export const _SlateUploadConverter: SlateNodeConverter = {
converter({ slateNode }) {
return {
type: 'upload',

View File

@@ -0,0 +1,9 @@
import type { SlateNodeConverterProvider } from '../../types'
import { UploadConverterClient } from './client'
import { _SlateUploadConverter } from './converter'
export const SlateUploadConverter: SlateNodeConverterProvider = {
ClientComponent: UploadConverterClient,
converter: _SlateUploadConverter,
}

View File

@@ -1,4 +1,4 @@
import type { SlateNodeConverter } from './types'
import type { SlateNodeConverterProvider } from './types'
import { SlateBlockquoteConverter } from './converters/blockquote'
import { SlateHeadingConverter } from './converters/heading'
@@ -11,15 +11,15 @@ import { SlateUnknownConverter } from './converters/unknown'
import { SlateUnorderedListConverter } from './converters/unorderedList'
import { SlateUploadConverter } from './converters/upload'
export const defaultSlateConverters: SlateNodeConverter[] = [
SlateUnknownConverter,
SlateUploadConverter,
SlateUnorderedListConverter,
SlateOrderedListConverter,
SlateRelationshipConverter,
SlateListItemConverter,
SlateLinkConverter,
export const defaultSlateConverters: SlateNodeConverterProvider[] = [
SlateBlockquoteConverter,
SlateHeadingConverter,
SlateIndentConverter,
SlateLinkConverter,
SlateListItemConverter,
SlateOrderedListConverter,
SlateRelationshipConverter,
SlateUnorderedListConverter,
SlateUploadConverter,
SlateUnknownConverter,
]

View File

@@ -40,13 +40,16 @@ export function convertSlateNodesToLexical({
slateNodes,
}: {
canContainParagraphs: boolean
converters: SlateNodeConverter[]
converters: SlateNodeConverter[] | undefined
/**
* Type of the parent lexical node (not the type of the original, parent slate type)
*/
parentNodeType: string
slateNodes: SlateNode[]
}): SerializedLexicalNode[] {
if (!converters?.length) {
return []
}
const unknownConverter = converters.find((converter) => converter.nodeTypes.includes('unknown'))
return (
slateNodes.map((slateNode, i) => {

View File

@@ -1,4 +1,5 @@
import type { SerializedLexicalNode } from 'lexical'
import type React from 'react'
export type SlateNodeConverter<T extends SerializedLexicalNode = SerializedLexicalNode> = {
converter: ({
@@ -20,3 +21,13 @@ export type SlateNode = {
children?: SlateNode[]
type?: string // doesn't always have type, e.g. for paragraphs
}
export type SlateNodeConverterClientComponent = React.FC<{
componentKey: string
featureKey: string
}>
export type SlateNodeConverterProvider = {
ClientComponent: SlateNodeConverterClientComponent
converter: SlateNodeConverter
}

View File

@@ -7,16 +7,13 @@ import { createClientComponent } from '../../createClientComponent'
import { convertSlateToLexical } from './converter'
import { UnknownConvertedNode } from './nodes/unknownConvertedNode'
export type SlateToLexicalFeatureClientProps = {
converters: SlateNodeConverter[]
}
const SlateToLexicalFeatureClient: FeatureProviderProviderClient<
SlateToLexicalFeatureClientProps
> = (props) => {
const SlateToLexicalFeatureClient: FeatureProviderProviderClient<undefined> = (props) => {
return {
clientFeatureProps: props,
feature: () => ({
feature: ({ clientFunctions }) => {
const converters: SlateNodeConverter[] = Object.values(clientFunctions)
return {
clientFeatureProps: props,
hooks: {
load({ incomingEditorState }) {
@@ -31,13 +28,14 @@ const SlateToLexicalFeatureClient: FeatureProviderProviderClient<
// Slate => convert to lexical
return convertSlateToLexical({
converters: props.converters,
converters,
slateData: incomingEditorState,
})
},
},
nodes: [UnknownConvertedNode],
}),
}
},
}
}

View File

@@ -1,6 +1,7 @@
import type React from 'react'
import type { FeatureProviderProviderServer } from '../../types'
import type { SlateNodeConverter } from './converter/types'
import type { SlateToLexicalFeatureClientProps } from './feature.client'
import type { SlateNodeConverterProvider } from './converter/types'
import { defaultSlateConverters } from './converter/defaultConverters'
import { SlateToLexicalFeatureClientComponent } from './feature.client'
@@ -8,32 +9,52 @@ import { UnknownConvertedNode } from './nodes/unknownConvertedNode'
export type SlateToLexicalFeatureProps = {
converters?:
| (({ defaultConverters }: { defaultConverters: SlateNodeConverter[] }) => SlateNodeConverter[])
| SlateNodeConverter[]
| (({
defaultConverters,
}: {
defaultConverters: SlateNodeConverterProvider[]
}) => SlateNodeConverterProvider[])
| SlateNodeConverterProvider[]
}
export const SlateToLexicalFeature: FeatureProviderProviderServer<
SlateToLexicalFeatureProps,
SlateToLexicalFeatureClientProps
undefined
> = (props) => {
if (!props) {
props = {}
}
props.converters =
props?.converters && typeof props?.converters === 'function'
? props.converters({ defaultConverters: defaultSlateConverters })
: (props?.converters as SlateNodeConverter[]) || defaultSlateConverters
const clientProps: SlateToLexicalFeatureClientProps = {
converters: props.converters,
let converters: SlateNodeConverterProvider[] = []
if (props?.converters && typeof props?.converters === 'function') {
converters = props.converters({ defaultConverters: defaultSlateConverters })
} else if (props.converters && typeof props?.converters !== 'function') {
converters = props.converters
} else {
converters = defaultSlateConverters
}
props.converters = converters
return {
feature: () => {
return {
ClientComponent: SlateToLexicalFeatureClientComponent,
clientFeatureProps: clientProps,
clientFeatureProps: null,
generateComponentMap: () => {
const map: {
[key: string]: React.FC
} = {}
for (const converter of converters) {
if (converter.ClientComponent) {
const key = converter.converter.nodeTypes.join('-')
map[key] = converter.ClientComponent
}
}
return map
},
nodes: [
{
node: UnknownConvertedNode,

View File

@@ -8,7 +8,6 @@ import {
IndentFeature,
InlineCodeFeature,
ItalicFeature,
LexicalPluginToLexicalFeature,
LinkFeature,
OrderedListFeature,
StrikethroughFeature,
@@ -108,7 +107,6 @@ export function buildConfigWithDefaults(testConfig?: Partial<Config>): Promise<S
SuperscriptFeature(),
InlineCodeFeature(),
TreeViewFeature(),
LexicalPluginToLexicalFeature(),
HeadingFeature(),
IndentFeature(),
BlocksFeature({