feat(richtext-lexical): lexicalPluginToLexical
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
'use client'
|
||||
|
||||
import type React from 'react'
|
||||
|
||||
import { useAddClientFunction, useFieldPath } from '@payloadcms/ui'
|
||||
|
||||
const useLexicalFeatureProp = <T,>(featureKey: string, componentKey: string, prop: T) => {
|
||||
const { schemaPath } = useFieldPath()
|
||||
|
||||
useAddClientFunction(
|
||||
`lexicalFeature.${schemaPath}.${featureKey}.components.${componentKey}`,
|
||||
prop,
|
||||
)
|
||||
}
|
||||
|
||||
export const createFeaturePropComponent = <T = unknown,>(
|
||||
prop: T,
|
||||
): React.FC<{ componentKey: string; featureKey: string }> => {
|
||||
return (props) => {
|
||||
useLexicalFeatureProp(props.featureKey, props.componentKey, prop)
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { createFeaturePropComponent } from '../../../../../createFeaturePropComponent'
|
||||
import { _HeadingConverter } from './converter'
|
||||
|
||||
export const HeadingConverterClient = createFeaturePropComponent(_HeadingConverter)
|
||||
@@ -1,17 +1,17 @@
|
||||
import type { SerializedHeadingNode } from '@lexical/rich-text'
|
||||
|
||||
import type { LexicalPluginNodeConverter } from '../types'
|
||||
import type { LexicalPluginNodeConverter } from '../../types'
|
||||
|
||||
import { convertLexicalPluginNodesToLexical } from '..'
|
||||
import { convertLexicalPluginNodesToLexical } from '../../index'
|
||||
|
||||
export const HeadingConverter: LexicalPluginNodeConverter = {
|
||||
export const _HeadingConverter: LexicalPluginNodeConverter = {
|
||||
converter({ converters, lexicalPluginNode }) {
|
||||
return {
|
||||
...lexicalPluginNode,
|
||||
type: 'heading',
|
||||
children: convertLexicalPluginNodesToLexical({
|
||||
converters,
|
||||
lexicalPluginNodes: (lexicalPluginNode as any).children || [],
|
||||
lexicalPluginNodes: lexicalPluginNode.children,
|
||||
parentNodeType: 'heading',
|
||||
}),
|
||||
version: 1,
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { LexicalPluginNodeConverterProvider } from '../../types'
|
||||
|
||||
import { HeadingConverterClient } from './client'
|
||||
import { _HeadingConverter } from './converter'
|
||||
|
||||
export const HeadingConverter: LexicalPluginNodeConverterProvider = {
|
||||
ClientComponent: HeadingConverterClient,
|
||||
converter: _HeadingConverter,
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
'use client'
|
||||
import { createFeaturePropComponent } from '../../../../../createFeaturePropComponent'
|
||||
import { _LinkConverter } from './converter'
|
||||
|
||||
export const LinkConverterClient = createFeaturePropComponent(_LinkConverter)
|
||||
@@ -1,16 +1,18 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import type { SerializedLinkNode } from '../../../../link/nodes/LinkNode'
|
||||
import type { LexicalPluginNodeConverter } from '../types'
|
||||
|
||||
import { convertLexicalPluginNodesToLexical } from '..'
|
||||
import type { SerializedLinkNode } from '@payloadcms/richtext-lexical'
|
||||
|
||||
export const LinkConverter: LexicalPluginNodeConverter = {
|
||||
import type { LexicalPluginNodeConverter } from '../../types'
|
||||
|
||||
import { convertLexicalPluginNodesToLexical } from '../../index'
|
||||
|
||||
export const _LinkConverter: LexicalPluginNodeConverter = {
|
||||
converter({ converters, lexicalPluginNode }) {
|
||||
return {
|
||||
type: 'link',
|
||||
children: convertLexicalPluginNodesToLexical({
|
||||
converters,
|
||||
lexicalPluginNodes: (lexicalPluginNode as any).children || [],
|
||||
lexicalPluginNodes: lexicalPluginNode.children,
|
||||
parentNodeType: 'link',
|
||||
}),
|
||||
direction: (lexicalPluginNode as any).direction || 'ltr',
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { LexicalPluginNodeConverterProvider } from '../../types'
|
||||
|
||||
import { LinkConverterClient } from './client'
|
||||
import { _LinkConverter } from './converter'
|
||||
|
||||
export const LinkConverter: LexicalPluginNodeConverterProvider = {
|
||||
ClientComponent: LinkConverterClient,
|
||||
converter: _LinkConverter,
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { createFeaturePropComponent } from '../../../../../createFeaturePropComponent'
|
||||
import { _ListConverter } from './converter'
|
||||
|
||||
export const ListConverterClient = createFeaturePropComponent(_ListConverter)
|
||||
@@ -1,17 +1,19 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import type { SerializedListNode } from '@lexical/list'
|
||||
|
||||
import type { LexicalPluginNodeConverter } from '../types'
|
||||
import type { LexicalPluginNodeConverter } from '../../types'
|
||||
|
||||
import { convertLexicalPluginNodesToLexical } from '..'
|
||||
import { convertLexicalPluginNodesToLexical } from '../../index'
|
||||
|
||||
export const ListConverter: LexicalPluginNodeConverter = {
|
||||
export const _ListConverter: LexicalPluginNodeConverter = {
|
||||
converter({ converters, lexicalPluginNode }) {
|
||||
return {
|
||||
...lexicalPluginNode,
|
||||
type: 'list',
|
||||
children: convertLexicalPluginNodesToLexical({
|
||||
converters,
|
||||
lexicalPluginNodes: (lexicalPluginNode as any).children || [],
|
||||
lexicalPluginNodes: lexicalPluginNode.children,
|
||||
parentNodeType: 'list',
|
||||
}),
|
||||
listType: (lexicalPluginNode as any)?.listType || 'number',
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { LexicalPluginNodeConverterProvider } from '../../types'
|
||||
|
||||
import { ListConverterClient } from './client'
|
||||
import { _ListConverter } from './converter'
|
||||
|
||||
export const ListConverter: LexicalPluginNodeConverterProvider = {
|
||||
ClientComponent: ListConverterClient,
|
||||
converter: _ListConverter,
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
'use client'
|
||||
import { createFeaturePropComponent } from '../../../../../createFeaturePropComponent'
|
||||
import { _ListItemConverter } from './converter'
|
||||
|
||||
export const ListItemConverterClient = createFeaturePropComponent(_ListItemConverter)
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { SerializedListItemNode } from '@lexical/list'
|
||||
|
||||
import type { LexicalPluginNodeConverter } from '../types'
|
||||
import type { LexicalPluginNodeConverter } from '../../types'
|
||||
|
||||
import { convertLexicalPluginNodesToLexical } from '..'
|
||||
import { convertLexicalPluginNodesToLexical } from '../../index'
|
||||
|
||||
export const ListItemConverter: LexicalPluginNodeConverter = {
|
||||
export const _ListItemConverter: LexicalPluginNodeConverter = {
|
||||
converter({ childIndex, converters, lexicalPluginNode }) {
|
||||
return {
|
||||
...lexicalPluginNode,
|
||||
@@ -12,7 +12,7 @@ export const ListItemConverter: LexicalPluginNodeConverter = {
|
||||
checked: undefined,
|
||||
children: convertLexicalPluginNodesToLexical({
|
||||
converters,
|
||||
lexicalPluginNodes: (lexicalPluginNode as any)?.children || [],
|
||||
lexicalPluginNodes: lexicalPluginNode.children,
|
||||
parentNodeType: 'listitem',
|
||||
}),
|
||||
value: childIndex + 1,
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { LexicalPluginNodeConverterProvider } from '../../types'
|
||||
|
||||
import { ListItemConverterClient } from './client'
|
||||
import { _ListItemConverter } from './converter'
|
||||
|
||||
export const ListItemConverter: LexicalPluginNodeConverterProvider = {
|
||||
ClientComponent: ListItemConverterClient,
|
||||
converter: _ListItemConverter,
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import type { SerializedHeadingNode } from '@lexical/rich-text'
|
||||
|
||||
import type { LexicalPluginNodeConverter } from '../types'
|
||||
|
||||
import { convertLexicalPluginNodesToLexical } from '..'
|
||||
|
||||
export const QuoteConverter: LexicalPluginNodeConverter = {
|
||||
converter({ converters, lexicalPluginNode }) {
|
||||
return {
|
||||
...lexicalPluginNode,
|
||||
type: 'quote',
|
||||
children: convertLexicalPluginNodesToLexical({
|
||||
converters,
|
||||
lexicalPluginNodes: (lexicalPluginNode as any).children || [],
|
||||
parentNodeType: 'quote',
|
||||
}),
|
||||
version: 1,
|
||||
} as const as SerializedHeadingNode
|
||||
},
|
||||
nodeTypes: ['quote'],
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { createFeaturePropComponent } from '../../../../../createFeaturePropComponent'
|
||||
import { _QuoteConverter } from './converter'
|
||||
|
||||
export const QuoteConverterClient = createFeaturePropComponent(_QuoteConverter)
|
||||
@@ -0,0 +1,21 @@
|
||||
import type { SerializedQuoteNode } from '@lexical/rich-text'
|
||||
|
||||
import type { LexicalPluginNodeConverter } from '../../types'
|
||||
|
||||
import { convertLexicalPluginNodesToLexical } from '../../index'
|
||||
|
||||
export const _QuoteConverter: LexicalPluginNodeConverter = {
|
||||
converter({ converters, lexicalPluginNode }) {
|
||||
return {
|
||||
...lexicalPluginNode,
|
||||
type: 'quote',
|
||||
children: convertLexicalPluginNodesToLexical({
|
||||
converters,
|
||||
lexicalPluginNodes: lexicalPluginNode.children,
|
||||
parentNodeType: 'quote',
|
||||
}),
|
||||
version: 1,
|
||||
} as const as SerializedQuoteNode
|
||||
},
|
||||
nodeTypes: ['quote'],
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { LexicalPluginNodeConverterProvider } from '../../types'
|
||||
|
||||
import { QuoteConverterClient } from './client'
|
||||
import { _QuoteConverter } from './converter'
|
||||
|
||||
export const QuoteConverter: LexicalPluginNodeConverterProvider = {
|
||||
ClientComponent: QuoteConverterClient,
|
||||
converter: _QuoteConverter,
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
'use client'
|
||||
import { createFeaturePropComponent } from '../../../../../createFeaturePropComponent'
|
||||
import { _UnknownConverter } from './converter'
|
||||
|
||||
export const UnknownConverterClient = createFeaturePropComponent(_UnknownConverter)
|
||||
@@ -1,15 +1,15 @@
|
||||
import type { SerializedUnknownConvertedNode } from '../../nodes/unknownConvertedNode'
|
||||
import type { LexicalPluginNodeConverter } from '../types'
|
||||
import type { SerializedUnknownConvertedNode } from '../../../nodes/unknownConvertedNode'
|
||||
import type { LexicalPluginNodeConverter } from '../../types'
|
||||
|
||||
import { convertLexicalPluginNodesToLexical } from '..'
|
||||
import { convertLexicalPluginNodesToLexical } from '../../index'
|
||||
|
||||
export const UnknownConverter: LexicalPluginNodeConverter = {
|
||||
export const _UnknownConverter: LexicalPluginNodeConverter = {
|
||||
converter({ converters, lexicalPluginNode }) {
|
||||
return {
|
||||
type: 'unknownConverted',
|
||||
children: convertLexicalPluginNodesToLexical({
|
||||
converters,
|
||||
lexicalPluginNodes: (lexicalPluginNode as any)?.children || [],
|
||||
lexicalPluginNodes: lexicalPluginNode.children,
|
||||
parentNodeType: 'unknownConverted',
|
||||
}),
|
||||
data: {
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { LexicalPluginNodeConverterProvider } from '../../types'
|
||||
|
||||
import { UnknownConverterClient } from './client'
|
||||
import { _UnknownConverter } from './converter'
|
||||
|
||||
export const UnknownConverter: LexicalPluginNodeConverterProvider = {
|
||||
ClientComponent: UnknownConverterClient,
|
||||
converter: _UnknownConverter,
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { createFeaturePropComponent } from '../../../../../createFeaturePropComponent'
|
||||
import { _UploadConverter } from './converter'
|
||||
|
||||
export const UploadConverterClient = createFeaturePropComponent(_UploadConverter)
|
||||
@@ -1,7 +1,10 @@
|
||||
import type { SerializedUploadNode } from '../../../../../..'
|
||||
import type { LexicalPluginNodeConverter } from '../types'
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
export const UploadConverter: LexicalPluginNodeConverter = {
|
||||
import type { SerializedUploadNode } from '@payloadcms/richtext-lexical'
|
||||
|
||||
import type { LexicalPluginNodeConverter } from '../../types'
|
||||
|
||||
export const _UploadConverter: LexicalPluginNodeConverter = {
|
||||
converter({ lexicalPluginNode }) {
|
||||
let fields = {}
|
||||
if ((lexicalPluginNode as any)?.caption?.editorState) {
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { LexicalPluginNodeConverterProvider } from '../../types'
|
||||
|
||||
import { UploadConverterClient } from './client'
|
||||
import { _UploadConverter } from './converter'
|
||||
|
||||
export const UploadConverter: LexicalPluginNodeConverterProvider = {
|
||||
ClientComponent: UploadConverterClient,
|
||||
converter: _UploadConverter,
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { LexicalPluginNodeConverter } from './types'
|
||||
import type { LexicalPluginNodeConverterProvider } from './types'
|
||||
|
||||
import { HeadingConverter } from './converters/heading'
|
||||
import { LinkConverter } from './converters/link'
|
||||
@@ -8,12 +8,12 @@ import { QuoteConverter } from './converters/quote'
|
||||
import { UnknownConverter } from './converters/unknown'
|
||||
import { UploadConverter } from './converters/upload'
|
||||
|
||||
export const defaultConverters: LexicalPluginNodeConverter[] = [
|
||||
UnknownConverter,
|
||||
UploadConverter,
|
||||
export const defaultConverters: LexicalPluginNodeConverterProvider[] = [
|
||||
HeadingConverter,
|
||||
LinkConverter,
|
||||
ListConverter,
|
||||
ListItemConverter,
|
||||
LinkConverter,
|
||||
HeadingConverter,
|
||||
QuoteConverter,
|
||||
UnknownConverter,
|
||||
UploadConverter,
|
||||
]
|
||||
|
||||
@@ -36,12 +36,15 @@ export function convertLexicalPluginNodesToLexical({
|
||||
parentNodeType,
|
||||
}: {
|
||||
converters: LexicalPluginNodeConverter[]
|
||||
lexicalPluginNodes: SerializedLexicalNode[]
|
||||
lexicalPluginNodes: SerializedLexicalNode[] | undefined
|
||||
/**
|
||||
* Type of the parent lexical node (not the type of the original, parent payload-plugin-lexical type)
|
||||
*/
|
||||
parentNodeType: string
|
||||
}): SerializedLexicalNode[] {
|
||||
if (!lexicalPluginNodes?.length) {
|
||||
return []
|
||||
}
|
||||
const unknownConverter = converters.find((converter) => converter.nodeTypes.includes('unknown'))
|
||||
return (
|
||||
lexicalPluginNodes.map((lexicalPluginNode, i) => {
|
||||
@@ -92,7 +95,3 @@ export function convertParagraphNode(
|
||||
export function convertTextNode(node: SerializedLexicalNode): SerializedTextNode {
|
||||
return node as SerializedTextNode
|
||||
}
|
||||
|
||||
export function convertNodeToFormat(node: SerializedLexicalNode): number {
|
||||
return (node as any).format
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { SerializedEditorState, SerializedLexicalNode } from 'lexical'
|
||||
import type React from 'react'
|
||||
|
||||
export type LexicalPluginNodeConverter<T extends SerializedLexicalNode = SerializedLexicalNode> = {
|
||||
converter: ({
|
||||
@@ -9,7 +10,7 @@ export type LexicalPluginNodeConverter<T extends SerializedLexicalNode = Seriali
|
||||
}: {
|
||||
childIndex: number
|
||||
converters: LexicalPluginNodeConverter[]
|
||||
lexicalPluginNode: SerializedLexicalNode
|
||||
lexicalPluginNode: SerializedLexicalNode & { children?: SerializedLexicalNode[] }
|
||||
parentNodeType: string
|
||||
}) => T
|
||||
nodeTypes: string[]
|
||||
@@ -24,3 +25,13 @@ export type PayloadPluginLexicalData = {
|
||||
preview: string
|
||||
words: number
|
||||
}
|
||||
|
||||
export type LexicalPluginNodeConverterClientComponent = React.FC<{
|
||||
componentKey: string
|
||||
featureKey: string
|
||||
}>
|
||||
|
||||
export type LexicalPluginNodeConverterProvider = {
|
||||
ClientComponent: LexicalPluginNodeConverterClientComponent
|
||||
converter: LexicalPluginNodeConverter
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
'use client'
|
||||
|
||||
import type { FeatureProviderProviderClient } from '../../types'
|
||||
import type { PayloadPluginLexicalData } from './converter/types'
|
||||
|
||||
import { createClientComponent } from '../../createClientComponent'
|
||||
import { convertLexicalPluginToLexical } from './converter'
|
||||
import { UnknownConvertedNode } from './nodes/unknownConvertedNode'
|
||||
|
||||
const LexicalPluginToLexicalFeatureClient: FeatureProviderProviderClient<null> = (props) => {
|
||||
return {
|
||||
clientFeatureProps: props,
|
||||
feature: ({ clientFunctions, resolvedFeatures }) => {
|
||||
const converters = Object.values(clientFunctions)
|
||||
|
||||
return {
|
||||
clientFeatureProps: props,
|
||||
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,
|
||||
lexicalPluginData: incomingEditorState as unknown as PayloadPluginLexicalData,
|
||||
})
|
||||
},
|
||||
},
|
||||
nodes: [UnknownConvertedNode],
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export const LexicalPluginToLexicalFeatureClientComponent = createClientComponent(
|
||||
LexicalPluginToLexicalFeatureClient,
|
||||
)
|
||||
@@ -0,0 +1,68 @@
|
||||
import type React from 'react'
|
||||
|
||||
import type { FeatureProviderProviderServer } from '../../types'
|
||||
import type { LexicalPluginNodeConverterProvider } from './converter/types'
|
||||
|
||||
import { defaultConverters } from './converter/defaultConverters'
|
||||
import { LexicalPluginToLexicalFeatureClientComponent } from './feature.client'
|
||||
import { UnknownConvertedNode } from './nodes/unknownConvertedNode'
|
||||
|
||||
export type LexicalPluginToLexicalFeatureProps = {
|
||||
converters?:
|
||||
| (({
|
||||
defaultConverters,
|
||||
}: {
|
||||
defaultConverters: LexicalPluginNodeConverterProvider[]
|
||||
}) => LexicalPluginNodeConverterProvider[])
|
||||
| LexicalPluginNodeConverterProvider[]
|
||||
}
|
||||
|
||||
export const LexicalPluginToLexicalFeature: FeatureProviderProviderServer<
|
||||
LexicalPluginToLexicalFeatureProps,
|
||||
null
|
||||
> = (props) => {
|
||||
if (!props) {
|
||||
props = {}
|
||||
}
|
||||
|
||||
let converters: LexicalPluginNodeConverterProvider[] = []
|
||||
|
||||
if (props?.converters && typeof props?.converters === 'function') {
|
||||
converters = props.converters({ defaultConverters })
|
||||
} else if (!props?.converters) {
|
||||
converters = defaultConverters
|
||||
}
|
||||
|
||||
props.converters = converters
|
||||
|
||||
return {
|
||||
feature: () => {
|
||||
return {
|
||||
ClientComponent: LexicalPluginToLexicalFeatureClientComponent,
|
||||
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,
|
||||
},
|
||||
],
|
||||
serverFeatureProps: props,
|
||||
}
|
||||
},
|
||||
key: 'lexicalPluginToLexical',
|
||||
serverFeatureProps: props,
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
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: [
|
||||
{
|
||||
type: UnknownConvertedNode.getType(),
|
||||
node: UnknownConvertedNode,
|
||||
},
|
||||
],
|
||||
props,
|
||||
}
|
||||
},
|
||||
key: 'lexicalPluginToLexical',
|
||||
}
|
||||
}
|
||||
@@ -98,6 +98,7 @@ export type FeatureProviderClient<ClientFeatureProps> = {
|
||||
*/
|
||||
clientFeatureProps: ClientComponentProps<ClientFeatureProps>
|
||||
feature: (props: {
|
||||
clientFunctions: Record<string, any>
|
||||
/** unSanitizedEditorConfig.features, but mapped */
|
||||
featureProviderMap: ClientFeatureProviderMap
|
||||
// other resolved features, which have been loaded before this one. All features declared in 'dependencies' should be available here
|
||||
@@ -180,7 +181,7 @@ export type ServerFeature<ServerProps, ClientFeatureProps> = {
|
||||
props: ServerProps
|
||||
schemaPath: string
|
||||
}) => {
|
||||
[key: string]: React.FC
|
||||
[key: string]: React.FC<{ componentKey: string; featureKey: string }>
|
||||
}
|
||||
generateSchemaMap?: (args: {
|
||||
config: SanitizedConfig
|
||||
|
||||
@@ -39,17 +39,26 @@ export const RichTextField: React.FC<
|
||||
// order by order
|
||||
featureProviderComponents = featureProviderComponents.sort((a, b) => a.order - b.order)
|
||||
|
||||
const featureComponentsWithFeaturesLength =
|
||||
Array.from(richTextComponentMap.keys()).filter(
|
||||
(key) => key.startsWith(`feature.`) && !key.includes('.fields.'),
|
||||
).length + featureProviderComponents.length
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasLoadedFeatures) {
|
||||
const featureProvidersLocal: FeatureProviderClient<unknown>[] = []
|
||||
let featureProvidersAndComponentsLoaded = 0
|
||||
|
||||
Object.entries(clientFunctions).forEach(([key, plugin]) => {
|
||||
if (key.startsWith(`lexicalFeature.${schemaPath}.`)) {
|
||||
featureProvidersLocal.push(plugin)
|
||||
if (!key.includes('.components.')) {
|
||||
featureProvidersLocal.push(plugin)
|
||||
}
|
||||
featureProvidersAndComponentsLoaded++
|
||||
}
|
||||
})
|
||||
|
||||
if (featureProvidersLocal.length === featureProviderComponents.length) {
|
||||
if (featureProvidersAndComponentsLoaded === featureComponentsWithFeaturesLength) {
|
||||
setFeatureProviders(featureProvidersLocal)
|
||||
setHasLoadedFeatures(true)
|
||||
|
||||
@@ -58,6 +67,8 @@ export const RichTextField: React.FC<
|
||||
*/
|
||||
|
||||
const resolvedClientFeatures = loadClientFeatures({
|
||||
clientFunctions,
|
||||
schemaPath,
|
||||
unSanitizedEditorConfig: {
|
||||
features: featureProvidersLocal,
|
||||
lexical: lexicalEditorConfig,
|
||||
@@ -80,16 +91,30 @@ export const RichTextField: React.FC<
|
||||
featureProviders,
|
||||
finalSanitizedEditorConfig,
|
||||
lexicalEditorConfig,
|
||||
featureComponentsWithFeaturesLength,
|
||||
])
|
||||
|
||||
if (!hasLoadedFeatures) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{Array.isArray(featureProviderComponents) &&
|
||||
featureProviderComponents.map((FeatureProvider) => {
|
||||
featureProviderComponents.map((featureProvider) => {
|
||||
// get all components starting with key feature.${FeatureProvider.key}.components.{featureComponentKey}
|
||||
const featureComponentKeys = Array.from(richTextComponentMap.keys()).filter((key) =>
|
||||
key.startsWith(`feature.${featureProvider.key}.components.`),
|
||||
)
|
||||
const featureComponents: React.ReactNode[] = featureComponentKeys.map((key) => {
|
||||
return richTextComponentMap.get(key)
|
||||
})
|
||||
|
||||
return (
|
||||
<React.Fragment key={FeatureProvider.key}>
|
||||
{FeatureProvider.ClientComponent}
|
||||
<React.Fragment key={featureProvider.key}>
|
||||
{featureComponents?.length
|
||||
? featureComponents.map((FeatureComponent) => {
|
||||
return FeatureComponent
|
||||
})
|
||||
: null}
|
||||
{featureProvider.ClientComponent}
|
||||
</React.Fragment>
|
||||
)
|
||||
})}
|
||||
|
||||
@@ -12,8 +12,12 @@ import type {
|
||||
* @param unSanitizedEditorConfig
|
||||
*/
|
||||
export function loadClientFeatures({
|
||||
clientFunctions,
|
||||
schemaPath,
|
||||
unSanitizedEditorConfig,
|
||||
}: {
|
||||
clientFunctions?: Record<string, any>
|
||||
schemaPath: string
|
||||
unSanitizedEditorConfig: ClientEditorConfig
|
||||
}): ResolvedClientFeatureMap {
|
||||
for (const featureProvider of unSanitizedEditorConfig.features) {
|
||||
@@ -44,7 +48,25 @@ export function loadClientFeatures({
|
||||
// Make sure all dependencies declared in the respective features exist
|
||||
let loaded = 0
|
||||
for (const featureProvider of unSanitizedEditorConfig.features) {
|
||||
/**
|
||||
* Load relevant clientFunctions scoped to this feature and then pass them to the client feature
|
||||
*/
|
||||
const relevantClientFunctions: Record<string, any> = {}
|
||||
Object.entries(clientFunctions).forEach(([key, plugin]) => {
|
||||
if (
|
||||
key.startsWith(
|
||||
`lexicalFeature.${schemaPath}.${featureProvider.clientFeatureProps.featureKey}.components.`,
|
||||
)
|
||||
) {
|
||||
const featureComponentKey = key.split(
|
||||
`${schemaPath}.${featureProvider.clientFeatureProps.featureKey}.components.`,
|
||||
)[1]
|
||||
relevantClientFunctions[featureComponentKey] = plugin
|
||||
}
|
||||
})
|
||||
|
||||
const feature = featureProvider.feature({
|
||||
clientFunctions: relevantClientFunctions,
|
||||
featureProviderMap,
|
||||
resolvedFeatures,
|
||||
unSanitizedEditorConfig,
|
||||
|
||||
@@ -7,6 +7,5 @@ import { useEditorConfigContext } from '../../config/client/EditorConfigProvider
|
||||
export const MarkdownShortcutPlugin: React.FC = () => {
|
||||
const { editorConfig } = useEditorConfigContext()
|
||||
|
||||
console.log('traaaaa', editorConfig.features.markdownTransformers)
|
||||
return <LexicalMarkdownShortcutPlugin transformers={editorConfig.features.markdownTransformers} />
|
||||
}
|
||||
|
||||
@@ -44,7 +44,14 @@ export const getGenerateComponentMap =
|
||||
for (const componentKey in components) {
|
||||
const Component = components[componentKey]
|
||||
if (Component) {
|
||||
componentMap.set(`feature.${featureKey}.components.${componentKey}`, <Component />)
|
||||
componentMap.set(
|
||||
`feature.${featureKey}.components.${componentKey}`,
|
||||
<Component
|
||||
componentKey={componentKey}
|
||||
featureKey={resolvedFeature.key}
|
||||
key={`${resolvedFeature.key}-${componentKey}`}
|
||||
/>,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,10 +101,15 @@ export const getGenerateComponentMap =
|
||||
<ClientComponent
|
||||
{...clientComponentProps}
|
||||
featureKey={resolvedFeature.key}
|
||||
key={resolvedFeature.key}
|
||||
order={resolvedFeature.order}
|
||||
/>
|
||||
) : (
|
||||
<ClientComponent featureKey={resolvedFeature.key} order={resolvedFeature.order} />
|
||||
<ClientComponent
|
||||
featureKey={resolvedFeature.key}
|
||||
key={resolvedFeature.key}
|
||||
order={resolvedFeature.order}
|
||||
/>
|
||||
),
|
||||
key: resolvedFeature.key,
|
||||
order: resolvedFeature.order,
|
||||
|
||||
@@ -295,7 +295,7 @@ export type {
|
||||
export { CheckListFeature } from './field/features/lists/checklist/feature.server'
|
||||
export { OrderedListFeature } from './field/features/lists/orderedlist/feature.server'
|
||||
export { UnorderedListFeature } from './field/features/lists/unorderedlist/feature.server'
|
||||
export { LexicalPluginToLexicalFeature } from './field/features/migrations/lexicalPluginToLexical'
|
||||
export { LexicalPluginToLexicalFeature } from './field/features/migrations/lexicalPluginToLexical/feature.server'
|
||||
export { SlateBlockquoteConverter } from './field/features/migrations/slateToLexical/converter/converters/blockquote'
|
||||
export { SlateHeadingConverter } from './field/features/migrations/slateToLexical/converter/converters/heading'
|
||||
export { SlateIndentConverter } from './field/features/migrations/slateToLexical/converter/converters/indent'
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
'use client'
|
||||
import { useAddClientFunction } from '@payloadcms/ui/providers'
|
||||
|
||||
import type { FeatureProvider } from './field/features/types'
|
||||
import type { FeatureProviderClient } from './field/features/types'
|
||||
|
||||
import { useFieldPath } from '../../ui/src/forms/FieldPathProvider'
|
||||
|
||||
export const useLexicalFeature = (key: string, feature: FeatureProvider) => {
|
||||
export const useLexicalFeature = <ClientFeatureProps,>(
|
||||
featureKey: string,
|
||||
feature: FeatureProviderClient<ClientFeatureProps>,
|
||||
) => {
|
||||
const { schemaPath } = useFieldPath()
|
||||
|
||||
useAddClientFunction(`lexicalFeature.${schemaPath}.${key}`, feature)
|
||||
useAddClientFunction(`lexicalFeature.${schemaPath}.${featureKey}`, feature)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
IndentFeature,
|
||||
InlineCodeFeature,
|
||||
ItalicFeature,
|
||||
LexicalPluginToLexicalFeature,
|
||||
LinkFeature,
|
||||
OrderedListFeature,
|
||||
StrikethroughFeature,
|
||||
@@ -107,6 +108,7 @@ export function buildConfigWithDefaults(testConfig?: Partial<Config>): Promise<S
|
||||
SuperscriptFeature(),
|
||||
InlineCodeFeature(),
|
||||
TreeViewFeature(),
|
||||
LexicalPluginToLexicalFeature(),
|
||||
HeadingFeature(),
|
||||
IndentFeature(),
|
||||
BlocksFeature({
|
||||
|
||||
Reference in New Issue
Block a user