feat(richtext-lexical): lexicalPluginToLexical

This commit is contained in:
Alessio Gravili
2024-03-01 13:04:58 -05:00
parent 707c5cba41
commit cf14b32355
37 changed files with 385 additions and 127 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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: {

View File

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

View File

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

View File

@@ -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) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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({