chore(richtext-lexical): split up feature types in client & server feature types (#6921)

This commit is contained in:
Alessio Gravili
2024-06-25 11:33:37 -04:00
committed by GitHub
parent 1a9c63bf26
commit d63bc5c20c
55 changed files with 644 additions and 666 deletions

View File

@@ -7,7 +7,7 @@ import { useClientFunctions, useTableCell } from '@payloadcms/ui'
import { $getRoot } from 'lexical'
import React, { useEffect, useState } from 'react'
import type { FeatureProviderClient } from '../features/types.js'
import type { FeatureProviderClient } from '../features/typesClient.js'
import type { SanitizedClientEditorConfig } from '../lexical/config/types.js'
import type { GeneratedFeatureProviderComponent, LexicalFieldAdminProps } from '../types.js'

View File

@@ -32,12 +32,12 @@ export { RelationshipFeatureClient } from '../../features/relationship/feature.c
export { toolbarAddDropdownGroupWithItems } from '../../features/shared/toolbar/addDropdownGroup.js'
export { toolbarFeatureButtonsGroupWithItems } from '../../features/shared/toolbar/featureButtonsGroup.js'
export { toolbarTextDropdownGroupWithItems } from '../../features/shared/toolbar/textDropdownGroup.js'
export { FixedToolbarFeatureClientComponent } from '../../features/toolbars/fixed/feature.client.js'
export { InlineToolbarFeatureClientComponent } from '../../features/toolbars/inline/feature.client.js'
export { FixedToolbarFeatureClient } from '../../features/toolbars/fixed/feature.client.js'
export { InlineToolbarFeatureClient } from '../../features/toolbars/inline/feature.client.js'
export { ToolbarButton } from '../../features/toolbars/shared/ToolbarButton/index.js'
export { ToolbarDropdown } from '../../features/toolbars/shared/ToolbarDropdown/index.js'
export { UploadFeatureClientComponent } from '../../features/upload/feature.client.js'
export { UploadFeatureClient } from '../../features/upload/feature.client.js'
export { RichTextField } from '../../field/index.js'
export {

View File

@@ -25,7 +25,7 @@ import { getTranslation } from '@payloadcms/translations'
import { getFormState } from '@payloadcms/ui/shared'
import { v4 as uuid } from 'uuid'
import type { ClientComponentProps } from '../../types.js'
import type { ClientComponentProps } from '../../typesClient.js'
import type { BlocksFeatureClientProps } from '../feature.client.js'
import { useEditorConfigContext } from '../../../lexical/config/client/EditorConfigProvider.js'

View File

@@ -12,7 +12,7 @@ import {
import { $getNodeByKey, COMMAND_PRIORITY_EDITOR, createCommand } from 'lexical'
import React, { useCallback, useEffect, useState } from 'react'
import type { ClientComponentProps } from '../../types.js'
import type { ClientComponentProps } from '../../typesClient.js'
import type { BlocksFeatureClientProps } from '../feature.client.js'
import { useEditorConfigContext } from '../../../lexical/config/client/EditorConfigProvider.js'

View File

@@ -1,4 +1,4 @@
import type { PopulationPromise } from '../types.js'
import type { PopulationPromise } from '../typesServer.js'
import type { BlocksFeatureProps } from './feature.server.js'
import type { SerializedBlockNode } from './nodes/BlocksNode.js'

View File

@@ -10,7 +10,7 @@ import {
} from 'lexical'
import React, { useEffect } from 'react'
import type { PluginComponent } from '../../types.js'
import type { PluginComponent } from '../../typesClient.js'
import type { BlocksFeatureClientProps } from '../feature.client.js'
import type { BlockFields } from '../nodes/BlocksNode.js'

View File

@@ -1,6 +1,6 @@
import { buildStateFromSchema } from '@payloadcms/ui/forms/buildStateFromSchema'
import type { NodeValidation } from '../types.js'
import type { NodeValidation } from '../typesServer.js'
import type { BlocksFeatureProps } from './feature.server.js'
import type { BlockFields, SerializedBlockNode } from './nodes/BlocksNode.js'

View File

@@ -1,6 +1,7 @@
'use client'
import type { FeatureProviderProviderClient, ServerFeature } from './types.js'
import type { FeatureProviderProviderClient } from './typesClient.js'
import type { ServerFeature } from './typesServer.js'
import { useLexicalFeature } from '../utilities/useLexicalFeature.js'

View File

@@ -6,7 +6,7 @@ import { $createParagraphNode, $createTextNode, $getRoot } from 'lexical'
import * as React from 'react'
import { type JSX, useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'
import type { PluginComponent } from '../../../types.js'
import type { PluginComponent } from '../../../typesClient.js'
import { IS_APPLE } from '../../../../lexical/utils/environment.js'
import './index.scss'

View File

@@ -3,7 +3,7 @@ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext
import { TreeView } from '@lexical/react/LexicalTreeView.js'
import * as React from 'react'
import type { PluginComponent } from '../../../types.js'
import type { PluginComponent } from '../../../typesClient.js'
import './index.scss'

View File

@@ -5,7 +5,7 @@ import { $insertNodeToNearestRoot } from '@lexical/utils'
import { $getSelection, $isRangeSelection, COMMAND_PRIORITY_EDITOR } from 'lexical'
import { useEffect } from 'react'
import type { PluginComponent } from '../../types.js'
import type { PluginComponent } from '../../typesClient.js'
import {
$createHorizontalRuleNode,

View File

@@ -1,4 +1,4 @@
import type { PopulationPromise } from '../types.js'
import type { PopulationPromise } from '../typesServer.js'
import type { LinkFeatureServerProps } from './feature.server.js'
import type { SerializedLinkNode } from './nodes/types.js'

View File

@@ -16,7 +16,7 @@ import {
} from 'lexical'
import { useEffect } from 'react'
import type { PluginComponent } from '../../../types.js'
import type { PluginComponent } from '../../../typesClient.js'
import type { ClientProps } from '../../feature.client.js'
import type { LinkFields } from '../../nodes/types.js'

View File

@@ -2,7 +2,7 @@
import { ClickableLinkPlugin as LexicalClickableLinkPlugin } from '@lexical/react/LexicalClickableLinkPlugin.js'
import React from 'react'
import type { PluginComponent } from '../../../types.js'
import type { PluginComponent } from '../../../typesClient.js'
import type { ClientProps } from '../../feature.client.js'
export const ClickableLinkPlugin: PluginComponent<ClientProps> = () => {

View File

@@ -2,7 +2,7 @@
import * as React from 'react'
import { createPortal } from 'react-dom'
import type { PluginComponentWithAnchor } from '../../../types.js'
import type { PluginComponentWithAnchor } from '../../../typesClient.js'
import type { ClientProps } from '../../feature.client.js'
import { LinkEditor } from './LinkEditor/index.js'

View File

@@ -10,7 +10,7 @@ import {
} from 'lexical'
import { useEffect } from 'react'
import type { PluginComponent } from '../../../types.js'
import type { PluginComponent } from '../../../typesClient.js'
import type { ClientProps } from '../../feature.client.js'
import type { LinkFields } from '../../nodes/types.js'
import type { LinkPayload } from '../floatingLinkEditor/types.js'

View File

@@ -2,7 +2,7 @@ import type { Field } from 'payload'
import { buildStateFromSchema } from '@payloadcms/ui/forms/buildStateFromSchema'
import type { NodeValidation } from '../types.js'
import type { NodeValidation } from '../typesServer.js'
import type { LinkFeatureServerProps } from './feature.server.js'
import type { SerializedAutoLinkNode, SerializedLinkNode } from './nodes/types.js'

View File

@@ -3,7 +3,7 @@ import { $isListNode, INSERT_CHECK_LIST_COMMAND, ListItemNode, ListNode } from '
import { $isRangeSelection } from 'lexical'
import type { ToolbarGroup } from '../../toolbars/types.js'
import type { ClientFeature } from '../../types.js'
import type { ClientFeature } from '../../typesClient.js'
import { ChecklistIcon } from '../../../lexical/ui/icons/Checklist/index.js'
import { createClientFeature } from '../../../utilities/createClientFeature.js'

View File

@@ -2,7 +2,7 @@
import { CheckListPlugin } from '@lexical/react/LexicalCheckListPlugin.js'
import React from 'react'
import type { PluginComponent } from '../../../types.js'
import type { PluginComponent } from '../../../typesClient.js'
export const LexicalCheckListPlugin: PluginComponent<undefined> = () => {
return <CheckListPlugin />

View File

@@ -2,7 +2,7 @@
import { ListPlugin } from '@lexical/react/LexicalListPlugin.js'
import React from 'react'
import type { PluginComponent } from '../../types.js'
import type { PluginComponent } from '../../typesClient.js'
export const LexicalListPlugin: PluginComponent<undefined> = () => {
return <ListPlugin />

View File

@@ -1,4 +1,4 @@
import type { PopulationPromise } from '../types.js'
import type { PopulationPromise } from '../typesServer.js'
import type { RelationshipFeatureProps } from './feature.server.js'
import type { SerializedRelationshipNode } from './nodes/RelationshipNode.js'

View File

@@ -14,7 +14,7 @@ import {
} from 'lexical'
import React, { useEffect } from 'react'
import type { PluginComponent } from '../../types.js'
import type { PluginComponent } from '../../typesClient.js'
import type { RelationshipFeatureProps } from '../feature.server.js'
import type { RelationshipData } from '../nodes/RelationshipNode.js'

View File

@@ -8,7 +8,7 @@ import { useMemo } from 'react'
import type { EditorConfigContextType } from '../../../../lexical/config/client/EditorConfigProvider.js'
import type { SanitizedClientEditorConfig } from '../../../../lexical/config/types.js'
import type { PluginComponentWithAnchor } from '../../../types.js'
import type { PluginComponentWithAnchor } from '../../../typesClient.js'
import type { ToolbarGroup, ToolbarGroupItem } from '../../types.js'
import type { FixedToolbarFeatureProps } from '../feature.server.js'

View File

@@ -1,26 +1,15 @@
'use client'
import type { FeatureProviderProviderClient } from '../../types.js'
import type { FixedToolbarFeatureProps } from './feature.server.js'
import { createClientComponent } from '../../createClientComponent.js'
import { createClientFeature } from '../../../utilities/createClientFeature.js'
import { FixedToolbarPlugin } from './Toolbar/index.js'
const FixedToolbarFeatureClient: FeatureProviderProviderClient<FixedToolbarFeatureProps> = (
props,
) => {
return {
clientFeatureProps: props,
feature: {
plugins: [
{
Component: FixedToolbarPlugin,
position: 'aboveContainer',
},
],
sanitizedClientFeatureProps: props,
export const FixedToolbarFeatureClient = createClientFeature<FixedToolbarFeatureProps>({
plugins: [
{
Component: FixedToolbarPlugin,
position: 'aboveContainer',
},
}
}
export const FixedToolbarFeatureClientComponent = createClientComponent(FixedToolbarFeatureClient)
],
})

View File

@@ -1,7 +1,6 @@
import type { FeatureProviderProviderServer } from '../../types.js'
// eslint-disable-next-line payload/no-imports-from-exports-dir
import { FixedToolbarFeatureClientComponent } from '../../../exports/client/index.js'
import { FixedToolbarFeatureClient } from '../../../exports/client/index.js'
import { createServerFeature } from '../../../utilities/createServerFeature.js'
export type FixedToolbarFeatureProps = {
/**
@@ -20,29 +19,26 @@ export type FixedToolbarFeatureProps = {
disableIfParentHasFixedToolbar?: boolean
}
export const FixedToolbarFeature: FeatureProviderProviderServer<
export const FixedToolbarFeature = createServerFeature<
FixedToolbarFeatureProps,
FixedToolbarFeatureProps,
FixedToolbarFeatureProps
> = (props) => {
return {
feature: () => {
const sanitizedProps: FixedToolbarFeatureProps = {
applyToFocusedEditor:
props?.applyToFocusedEditor === undefined ? false : props.applyToFocusedEditor,
disableIfParentHasFixedToolbar:
props?.disableIfParentHasFixedToolbar === undefined
? false
: props.disableIfParentHasFixedToolbar,
}
>({
feature: ({ props }) => {
const sanitizedProps: FixedToolbarFeatureProps = {
applyToFocusedEditor:
props?.applyToFocusedEditor === undefined ? false : props.applyToFocusedEditor,
disableIfParentHasFixedToolbar:
props?.disableIfParentHasFixedToolbar === undefined
? false
: props.disableIfParentHasFixedToolbar,
}
return {
ClientFeature: FixedToolbarFeatureClientComponent,
clientFeatureProps: sanitizedProps,
sanitizedServerFeatureProps: sanitizedProps,
}
},
key: 'toolbarFixed',
serverFeatureProps: props,
}
}
return {
ClientFeature: FixedToolbarFeatureClient,
clientFeatureProps: sanitizedProps,
sanitizedServerFeatureProps: sanitizedProps,
}
},
key: 'toolbarFixed',
})

View File

@@ -14,7 +14,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'
import * as React from 'react'
import { createPortal } from 'react-dom'
import type { PluginComponentWithAnchor } from '../../../types.js'
import type { PluginComponentWithAnchor } from '../../../typesClient.js'
import type { ToolbarGroup, ToolbarGroupItem } from '../../types.js'
import { useEditorConfigContext } from '../../../../lexical/config/client/EditorConfigProvider.js'

View File

@@ -1,23 +1,13 @@
'use client'
import type { FeatureProviderProviderClient } from '../../types.js'
import { createClientComponent } from '../../createClientComponent.js'
import { createClientFeature } from '../../../utilities/createClientFeature.js'
import { InlineToolbarPlugin } from './Toolbar/index.js'
const InlineToolbarFeatureClient: FeatureProviderProviderClient<undefined> = (props) => {
return {
clientFeatureProps: props,
feature: () => ({
plugins: [
{
Component: InlineToolbarPlugin,
position: 'floatingAnchorElem',
},
],
sanitizedClientFeatureProps: props,
}),
}
}
export const InlineToolbarFeatureClientComponent = createClientComponent(InlineToolbarFeatureClient)
export const InlineToolbarFeatureClient = createClientFeature({
plugins: [
{
Component: InlineToolbarPlugin,
position: 'floatingAnchorElem',
},
],
})

View File

@@ -1,17 +1,10 @@
import type { FeatureProviderProviderServer } from '../../types.js'
// eslint-disable-next-line payload/no-imports-from-exports-dir
import { InlineToolbarFeatureClientComponent } from '../../../exports/client/index.js'
import { InlineToolbarFeatureClient } from '../../../exports/client/index.js'
import { createServerFeature } from '../../../utilities/createServerFeature.js'
export const InlineToolbarFeature: FeatureProviderProviderServer<undefined, undefined> = (
props,
) => {
return {
feature: {
ClientFeature: InlineToolbarFeatureClientComponent,
sanitizedServerFeatureProps: props,
},
key: 'toolbarInline',
serverFeatureProps: props,
}
}
export const InlineToolbarFeature = createServerFeature({
feature: {
ClientFeature: InlineToolbarFeatureClient,
},
key: 'toolbarInline',
})

View File

@@ -1,6 +1,6 @@
import type { LexicalNode } from 'lexical'
import type { NodeWithHooks } from './types.js'
import type { NodeWithHooks } from './typesServer.js'
/**
* Utility function to create a node with hooks. You don't have to use this utility, but it improves type inference

View File

@@ -0,0 +1,251 @@
import type { Transformer } from '@lexical/markdown'
import type {
Klass,
LexicalEditor,
LexicalNode,
LexicalNodeReplacement,
SerializedEditorState,
} from 'lexical'
import type React from 'react'
import type { ClientEditorConfig } from '../lexical/config/types.js'
import type { SlashMenuGroup } from '../lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/types.js'
import type { ToolbarGroup } from './toolbars/types.js'
export type FeatureProviderProviderClient<
UnSanitizedClientFeatureProps = undefined,
ClientFeatureProps = UnSanitizedClientFeatureProps,
> = (props: ClientComponentProps<ClientFeatureProps>) => FeatureProviderClient<ClientFeatureProps>
/**
* No dependencies => Features need to be sorted on the server first, then sent to client in right order
*/
export type FeatureProviderClient<
UnSanitizedClientFeatureProps = undefined,
ClientFeatureProps = UnSanitizedClientFeatureProps,
> = {
/**
* Return props, to make it easy to retrieve passed in props to this Feature for the client if anyone wants to
*/
clientFeatureProps: ClientComponentProps<UnSanitizedClientFeatureProps>
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
resolvedFeatures: ResolvedClientFeatureMap
// unSanitized EditorConfig,
unSanitizedEditorConfig: ClientEditorConfig
}) => ClientFeature<ClientFeatureProps>)
| ClientFeature<ClientFeatureProps>
}
export type PluginComponent<ClientFeatureProps = any> = React.FC<{
clientProps: ClientFeatureProps
}>
export type PluginComponentWithAnchor<ClientFeatureProps = any> = React.FC<{
anchorElem: HTMLElement
clientProps: ClientFeatureProps
}>
export type ClientFeature<ClientFeatureProps> = {
hooks?: {
load?: ({
incomingEditorState,
}: {
incomingEditorState: SerializedEditorState
}) => SerializedEditorState
save?: ({
incomingEditorState,
}: {
incomingEditorState: SerializedEditorState
}) => SerializedEditorState
}
markdownTransformers?: Transformer[]
nodes?: Array<Klass<LexicalNode> | LexicalNodeReplacement>
/**
* Plugins are react components which get added to the editor. You can use them to interact with lexical, e.g. to create a command which creates a node, or opens a modal, or some other more "outside" functionality
*/
plugins?: Array<
| {
// plugins are anything which is not directly part of the editor. Like, creating a command which creates a node, or opens a modal, or some other more "outside" functionality
Component: PluginComponent<ClientFeatureProps>
position: 'aboveContainer' // Determines at which position the Component will be added.
}
| {
// plugins are anything which is not directly part of the editor. Like, creating a command which creates a node, or opens a modal, or some other more "outside" functionality
Component: PluginComponent<ClientFeatureProps>
position: 'bottom' // Determines at which position the Component will be added.
}
| {
// plugins are anything which is not directly part of the editor. Like, creating a command which creates a node, or opens a modal, or some other more "outside" functionality
Component: PluginComponent<ClientFeatureProps>
position: 'normal' // Determines at which position the Component will be added.
}
| {
// plugins are anything which is not directly part of the editor. Like, creating a command which creates a node, or opens a modal, or some other more "outside" functionality
Component: PluginComponent<ClientFeatureProps>
position: 'top' // Determines at which position the Component will be added.
}
| {
// plugins are anything which is not directly part of the editor. Like, creating a command which creates a node, or opens a modal, or some other more "outside" functionality
Component: PluginComponentWithAnchor<ClientFeatureProps>
position: 'floatingAnchorElem' // Determines at which position the Component will be added.
}
| {
Component: PluginComponent<ClientFeatureProps>
position: 'belowContainer' // Determines at which position the Component will be added.
}
>
/**
* Return props, to make it easy to retrieve passed in props to this Feature for the client if anyone wants to
*/
sanitizedClientFeatureProps?: ClientComponentProps<ClientFeatureProps>
slashMenu?: {
/**
* Dynamic groups allow you to add different groups depending on the query string (so, the text after the slash).
* Thus, to re-calculate the available groups, this function will be called every time you type after the /.
*
* The groups provided by dynamicGroups will be merged with the static groups provided by the groups property.
*/
dynamicGroups?: ({
editor,
queryString,
}: {
editor: LexicalEditor
queryString: string
}) => SlashMenuGroup[]
/**
* Static array of groups together with the items in them. These will always be present.
* While typing after the /, they will be filtered by the query string and the keywords, key and display name of the items.
*/
groups?: SlashMenuGroup[]
}
/**
* An opt-in, classic fixed toolbar which stays at the top of the editor
*/
toolbarFixed?: {
groups: ToolbarGroup[]
}
/**
* The default, floating toolbar which appears when you select text.
*/
toolbarInline?: {
/**
* Array of toolbar groups / sections. Each section can contain multiple toolbar items.
*/
groups: ToolbarGroup[]
}
}
export type ClientComponentProps<ClientFeatureProps> = ClientFeatureProps extends undefined
? {
featureKey: string
order: number
}
: {
featureKey: string
order: number
} & ClientFeatureProps
export type ResolvedClientFeature<ClientFeatureProps> = ClientFeature<ClientFeatureProps> & {
key: string
order: number
}
export type ResolvedClientFeatureMap = Map<string, ResolvedClientFeature<unknown>>
export type ClientFeatureProviderMap = Map<string, FeatureProviderClient<unknown, unknown>>
/**
* Plugins are react components which get added to the editor. You can use them to interact with lexical, e.g. to create a command which creates a node, or opens a modal, or some other more "outside" functionality
*/
export type SanitizedPlugin =
| {
// plugins are anything which is not directly part of the editor. Like, creating a command which creates a node, or opens a modal, or some other more "outside" functionality
Component: PluginComponent
clientProps: any
key: string
position: 'bottom' // Determines at which position the Component will be added.
}
| {
// plugins are anything which is not directly part of the editor. Like, creating a command which creates a node, or opens a modal, or some other more "outside" functionality
Component: PluginComponent
clientProps: any
key: string
position: 'normal' // Determines at which position the Component will be added.
}
| {
// plugins are anything which is not directly part of the editor. Like, creating a command which creates a node, or opens a modal, or some other more "outside" functionality
Component: PluginComponent
clientProps: any
key: string
position: 'top' // Determines at which position the Component will be added.
}
| {
// plugins are anything which is not directly part of the editor. Like, creating a command which creates a node, or opens a modal, or some other more "outside" functionality
Component: PluginComponentWithAnchor
clientProps: any
desktopOnly?: boolean
key: string
position: 'floatingAnchorElem' // Determines at which position the Component will be added.
}
| {
Component: PluginComponent
clientProps: any
key: string
position: 'aboveContainer'
}
| {
Component: PluginComponent
clientProps: any
key: string
position: 'belowContainer'
}
export type SanitizedClientFeatures = Required<
Pick<
ResolvedClientFeature<unknown>,
'markdownTransformers' | 'nodes' | 'toolbarFixed' | 'toolbarInline'
>
> & {
/** The keys of all enabled features */
enabledFeatures: string[]
hooks: {
load: Array<
({
incomingEditorState,
}: {
incomingEditorState: SerializedEditorState
}) => SerializedEditorState
>
save: Array<
({
incomingEditorState,
}: {
incomingEditorState: SerializedEditorState
}) => SerializedEditorState
>
}
/**
* Plugins are react components which get added to the editor. You can use them to interact with lexical, e.g. to create a command which creates a node, or opens a modal, or some other more "outside" functionality
*/
plugins?: Array<SanitizedPlugin>
slashMenu: {
/**
* Dynamic groups allow you to add different groups depending on the query string (so, the text after the slash).
* Thus, to re-calculate the available groups, this function will be called every time you type after the /.
*
* The groups provided by dynamicGroups will be merged with the static groups provided by the groups property.
*/
dynamicGroups: Array<
({ editor, queryString }: { editor: LexicalEditor; queryString: string }) => SlashMenuGroup[]
>
/**
* Static array of groups together with the items in them. These will always be present.
* While typing after the /, they will be filtered by the query string and the keywords, key and display name of the items.
*/
groups: SlashMenuGroup[]
}
}

View File

@@ -3,7 +3,6 @@ import type { GenericLanguages, I18nClient } from '@payloadcms/translations'
import type { JSONSchema4 } from 'json-schema'
import type {
Klass,
LexicalEditor,
LexicalNode,
LexicalNodeReplacement,
SerializedEditorState,
@@ -20,11 +19,10 @@ import type {
} from 'payload'
import type React from 'react'
import type { ClientEditorConfig, ServerEditorConfig } from '../lexical/config/types.js'
import type { SlashMenuGroup } from '../lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/types.js'
import type { ServerEditorConfig } from '../lexical/config/types.js'
import type { AdapterProps } from '../types.js'
import type { HTMLConverter } from './converters/html/converter/types.js'
import type { ToolbarGroup } from './toolbars/types.js'
import type { ClientComponentProps } from './typesClient.js'
export type PopulationPromise<T extends SerializedLexicalNode = SerializedLexicalNode> = ({
context,
@@ -121,143 +119,6 @@ export type FeatureProviderServer<
serverFeatureProps: UnSanitizedServerFeatureProps
}
export type FeatureProviderProviderClient<
UnSanitizedClientFeatureProps = undefined,
ClientFeatureProps = UnSanitizedClientFeatureProps,
> = (props: ClientComponentProps<ClientFeatureProps>) => FeatureProviderClient<ClientFeatureProps>
/**
* No dependencies => Features need to be sorted on the server first, then sent to client in right order
*/
export type FeatureProviderClient<
UnSanitizedClientFeatureProps = undefined,
ClientFeatureProps = UnSanitizedClientFeatureProps,
> = {
/**
* Return props, to make it easy to retrieve passed in props to this Feature for the client if anyone wants to
*/
clientFeatureProps: ClientComponentProps<UnSanitizedClientFeatureProps>
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
resolvedFeatures: ResolvedClientFeatureMap
// unSanitized EditorConfig,
unSanitizedEditorConfig: ClientEditorConfig
}) => ClientFeature<ClientFeatureProps>)
| ClientFeature<ClientFeatureProps>
}
export type PluginComponent<ClientFeatureProps = any> = React.FC<{
clientProps: ClientFeatureProps
}>
export type PluginComponentWithAnchor<ClientFeatureProps = any> = React.FC<{
anchorElem: HTMLElement
clientProps: ClientFeatureProps
}>
export type ClientFeature<ClientFeatureProps> = {
hooks?: {
load?: ({
incomingEditorState,
}: {
incomingEditorState: SerializedEditorState
}) => SerializedEditorState
save?: ({
incomingEditorState,
}: {
incomingEditorState: SerializedEditorState
}) => SerializedEditorState
}
markdownTransformers?: Transformer[]
nodes?: Array<Klass<LexicalNode> | LexicalNodeReplacement>
/**
* Plugins are react components which get added to the editor. You can use them to interact with lexical, e.g. to create a command which creates a node, or opens a modal, or some other more "outside" functionality
*/
plugins?: Array<
| {
// plugins are anything which is not directly part of the editor. Like, creating a command which creates a node, or opens a modal, or some other more "outside" functionality
Component: PluginComponent<ClientFeatureProps>
position: 'aboveContainer' // Determines at which position the Component will be added.
}
| {
// plugins are anything which is not directly part of the editor. Like, creating a command which creates a node, or opens a modal, or some other more "outside" functionality
Component: PluginComponent<ClientFeatureProps>
position: 'bottom' // Determines at which position the Component will be added.
}
| {
// plugins are anything which is not directly part of the editor. Like, creating a command which creates a node, or opens a modal, or some other more "outside" functionality
Component: PluginComponent<ClientFeatureProps>
position: 'normal' // Determines at which position the Component will be added.
}
| {
// plugins are anything which is not directly part of the editor. Like, creating a command which creates a node, or opens a modal, or some other more "outside" functionality
Component: PluginComponent<ClientFeatureProps>
position: 'top' // Determines at which position the Component will be added.
}
| {
// plugins are anything which is not directly part of the editor. Like, creating a command which creates a node, or opens a modal, or some other more "outside" functionality
Component: PluginComponentWithAnchor<ClientFeatureProps>
position: 'floatingAnchorElem' // Determines at which position the Component will be added.
}
| {
Component: PluginComponent<ClientFeatureProps>
position: 'belowContainer' // Determines at which position the Component will be added.
}
>
/**
* Return props, to make it easy to retrieve passed in props to this Feature for the client if anyone wants to
*/
sanitizedClientFeatureProps?: ClientComponentProps<ClientFeatureProps>
slashMenu?: {
/**
* Dynamic groups allow you to add different groups depending on the query string (so, the text after the slash).
* Thus, to re-calculate the available groups, this function will be called every time you type after the /.
*
* The groups provided by dynamicGroups will be merged with the static groups provided by the groups property.
*/
dynamicGroups?: ({
editor,
queryString,
}: {
editor: LexicalEditor
queryString: string
}) => SlashMenuGroup[]
/**
* Static array of groups together with the items in them. These will always be present.
* While typing after the /, they will be filtered by the query string and the keywords, key and display name of the items.
*/
groups?: SlashMenuGroup[]
}
/**
* An opt-in, classic fixed toolbar which stays at the top of the editor
*/
toolbarFixed?: {
groups: ToolbarGroup[]
}
/**
* The default, floating toolbar which appears when you select text.
*/
toolbarInline?: {
/**
* Array of toolbar groups / sections. Each section can contain multiple toolbar items.
*/
groups: ToolbarGroup[]
}
}
export type ClientComponentProps<ClientFeatureProps> = ClientFeatureProps extends undefined
? {
featureKey: string
order: number
}
: {
featureKey: string
order: number
} & ClientFeatureProps
export type AfterReadNodeHookArgs<T extends SerializedLexicalNode> = {
/**
* Only available in `afterRead` hooks.
@@ -472,62 +333,9 @@ export type ResolvedServerFeature<ServerProps, ClientFeatureProps> = ServerFeatu
order: number
}
export type ResolvedClientFeature<ClientFeatureProps> = ClientFeature<ClientFeatureProps> & {
key: string
order: number
}
export type ResolvedServerFeatureMap = Map<string, ResolvedServerFeature<unknown, unknown>>
export type ResolvedClientFeatureMap = Map<string, ResolvedClientFeature<unknown>>
export type ServerFeatureProviderMap = Map<string, FeatureProviderServer<unknown, unknown, unknown>>
export type ClientFeatureProviderMap = Map<string, FeatureProviderClient<unknown, unknown>>
/**
* Plugins are react components which get added to the editor. You can use them to interact with lexical, e.g. to create a command which creates a node, or opens a modal, or some other more "outside" functionality
*/
export type SanitizedPlugin =
| {
// plugins are anything which is not directly part of the editor. Like, creating a command which creates a node, or opens a modal, or some other more "outside" functionality
Component: PluginComponent
clientProps: any
key: string
position: 'bottom' // Determines at which position the Component will be added.
}
| {
// plugins are anything which is not directly part of the editor. Like, creating a command which creates a node, or opens a modal, or some other more "outside" functionality
Component: PluginComponent
clientProps: any
key: string
position: 'normal' // Determines at which position the Component will be added.
}
| {
// plugins are anything which is not directly part of the editor. Like, creating a command which creates a node, or opens a modal, or some other more "outside" functionality
Component: PluginComponent
clientProps: any
key: string
position: 'top' // Determines at which position the Component will be added.
}
| {
// plugins are anything which is not directly part of the editor. Like, creating a command which creates a node, or opens a modal, or some other more "outside" functionality
Component: PluginComponentWithAnchor
clientProps: any
desktopOnly?: boolean
key: string
position: 'floatingAnchorElem' // Determines at which position the Component will be added.
}
| {
Component: PluginComponent
clientProps: any
key: string
position: 'aboveContainer'
}
| {
Component: PluginComponent
clientProps: any
key: string
position: 'belowContainer'
}
export type SanitizedServerFeatures = Required<
Pick<ResolvedServerFeature<unknown, unknown>, 'i18n' | 'markdownTransformers' | 'nodes'>
@@ -583,49 +391,3 @@ export type SanitizedServerFeatures = Required<
/** The node types mapped to their validations */
validations: Map<string, Array<NodeValidation>>
}
export type SanitizedClientFeatures = Required<
Pick<
ResolvedClientFeature<unknown>,
'markdownTransformers' | 'nodes' | 'toolbarFixed' | 'toolbarInline'
>
> & {
/** The keys of all enabled features */
enabledFeatures: string[]
hooks: {
load: Array<
({
incomingEditorState,
}: {
incomingEditorState: SerializedEditorState
}) => SerializedEditorState
>
save: Array<
({
incomingEditorState,
}: {
incomingEditorState: SerializedEditorState
}) => SerializedEditorState
>
}
/**
* Plugins are react components which get added to the editor. You can use them to interact with lexical, e.g. to create a command which creates a node, or opens a modal, or some other more "outside" functionality
*/
plugins?: Array<SanitizedPlugin>
slashMenu: {
/**
* Dynamic groups allow you to add different groups depending on the query string (so, the text after the slash).
* Thus, to re-calculate the available groups, this function will be called every time you type after the /.
*
* The groups provided by dynamicGroups will be merged with the static groups provided by the groups property.
*/
dynamicGroups: Array<
({ editor, queryString }: { editor: LexicalEditor; queryString: string }) => SlashMenuGroup[]
>
/**
* Static array of groups together with the items in them. These will always be present.
* While typing after the /, they will be filtered by the query string and the keywords, key and display name of the items.
*/
groups: SlashMenuGroup[]
}
}

View File

@@ -26,7 +26,7 @@ import {
} from 'lexical'
import React, { useCallback, useEffect, useReducer, useRef, useState } from 'react'
import type { ClientComponentProps } from '../../types.js'
import type { ClientComponentProps } from '../../typesClient.js'
import type { UploadFeaturePropsClient } from '../feature.client.js'
import type { UploadData } from '../nodes/UploadNode.js'

View File

@@ -2,10 +2,8 @@
import { $isNodeSelection } from 'lexical'
import type { FeatureProviderProviderClient } from '../types.js'
import { UploadIcon } from '../../lexical/ui/icons/Upload/index.js'
import { createClientComponent } from '../createClientComponent.js'
import { createClientFeature } from '../../utilities/createClientFeature.js'
import { slashMenuBasicGroupWithItems } from '../shared/slashMenu/basicGroup.js'
import { toolbarAddDropdownGroupWithItems } from '../shared/toolbar/addDropdownGroup.js'
import { INSERT_UPLOAD_WITH_DRAWER_COMMAND } from './drawer/commands.js'
@@ -20,65 +18,57 @@ export type UploadFeaturePropsClient = {
}
}
const UploadFeatureClient: FeatureProviderProviderClient<UploadFeaturePropsClient> = (props) => {
return {
clientFeatureProps: props,
feature: () => ({
nodes: [UploadNode],
plugins: [
export const UploadFeatureClient = createClientFeature<UploadFeaturePropsClient>({
nodes: [UploadNode],
plugins: [
{
Component: UploadPlugin,
position: 'normal',
},
],
slashMenu: {
groups: [
slashMenuBasicGroupWithItems([
{
Component: UploadPlugin,
position: 'normal',
Icon: UploadIcon,
key: 'upload',
keywords: ['upload', 'image', 'file', 'img', 'picture', 'photo', 'media'],
label: ({ i18n }) => {
return i18n.t('lexical:upload:label')
},
onSelect: ({ editor }) => {
editor.dispatchCommand(INSERT_UPLOAD_WITH_DRAWER_COMMAND, {
replace: false,
})
},
},
],
sanitizedClientFeatureProps: props,
slashMenu: {
groups: [
slashMenuBasicGroupWithItems([
{
Icon: UploadIcon,
key: 'upload',
keywords: ['upload', 'image', 'file', 'img', 'picture', 'photo', 'media'],
label: ({ i18n }) => {
return i18n.t('lexical:upload:label')
},
onSelect: ({ editor }) => {
editor.dispatchCommand(INSERT_UPLOAD_WITH_DRAWER_COMMAND, {
replace: false,
})
},
},
]),
],
},
toolbarFixed: {
groups: [
toolbarAddDropdownGroupWithItems([
{
ChildComponent: UploadIcon,
isActive: ({ selection }) => {
if (!$isNodeSelection(selection) || !selection.getNodes().length) {
return false
}
]),
],
},
toolbarFixed: {
groups: [
toolbarAddDropdownGroupWithItems([
{
ChildComponent: UploadIcon,
isActive: ({ selection }) => {
if (!$isNodeSelection(selection) || !selection.getNodes().length) {
return false
}
const firstNode = selection.getNodes()[0]
return $isUploadNode(firstNode)
},
key: 'upload',
label: ({ i18n }) => {
return i18n.t('lexical:upload:label')
},
onSelect: ({ editor }) => {
editor.dispatchCommand(INSERT_UPLOAD_WITH_DRAWER_COMMAND, {
replace: false,
})
},
},
]),
],
},
}),
}
}
export const UploadFeatureClientComponent = createClientComponent(UploadFeatureClient)
const firstNode = selection.getNodes()[0]
return $isUploadNode(firstNode)
},
key: 'upload',
label: ({ i18n }) => {
return i18n.t('lexical:upload:label')
},
onSelect: ({ editor }) => {
editor.dispatchCommand(INSERT_UPLOAD_WITH_DRAWER_COMMAND, {
replace: false,
})
},
},
]),
],
},
})

View File

@@ -11,12 +11,12 @@ import type {
import { traverseFields } from '@payloadcms/ui/utilities/buildFieldSchemaMap/traverseFields'
import { sanitizeFields } from 'payload'
import type { FeatureProviderProviderServer } from '../types.js'
import type { UploadFeaturePropsClient } from './feature.client.js'
// eslint-disable-next-line payload/no-imports-from-exports-dir
import { UploadFeatureClientComponent } from '../../exports/client/index.js'
import { UploadFeatureClient } from '../../exports/client/index.js'
import { populate } from '../../populateGraphQL/populate.js'
import { createServerFeature } from '../../utilities/createServerFeature.js'
import { createNode } from '../typeUtilities.js'
import { uploadPopulationPromiseHOC } from './graphQLPopulationPromise.js'
import { i18n } from './i18n.js'
@@ -45,235 +45,230 @@ function getAbsoluteURL(url: string, payload: Payload): string {
return url?.startsWith('http') ? url : (payload?.config?.serverURL || '') + url
}
export const UploadFeature: FeatureProviderProviderServer<
export const UploadFeature = createServerFeature<
UploadFeatureProps,
UploadFeatureProps,
UploadFeaturePropsClient
> = (props) => {
if (!props) {
props = { collections: {} }
}
const clientProps: UploadFeaturePropsClient = {
collections: {},
}
if (props.collections) {
for (const collection in props.collections) {
clientProps.collections[collection] = {
hasExtraFields: props.collections[collection].fields.length >= 1,
}
>({
feature: async ({ config: _config, isRoot, props }) => {
if (!props) {
props = { collections: {} }
}
}
return {
feature: async ({ config: _config, isRoot }) => {
const validRelationships = _config.collections.map((c) => c.slug) || []
const clientProps: UploadFeaturePropsClient = {
collections: {},
}
if (props.collections) {
for (const collection in props.collections) {
if (props.collections[collection].fields?.length) {
props.collections[collection].fields = await sanitizeFields({
config: _config as unknown as Config,
fields: props.collections[collection].fields,
requireFieldLevelRichTextEditor: isRoot,
validRelationships,
})
clientProps.collections[collection] = {
hasExtraFields: props.collections[collection].fields.length >= 1,
}
}
}
return {
ClientFeature: UploadFeatureClientComponent,
clientFeatureProps: clientProps,
generateSchemaMap: ({ config, i18n, props }) => {
if (!props?.collections) return null
const validRelationships = _config.collections.map((c) => c.slug) || []
const schemaMap = new Map<string, Field[]>()
const validRelationships = config.collections.map((c) => c.slug) || []
for (const collection in props.collections) {
if (props.collections[collection].fields?.length) {
schemaMap.set(collection, props.collections[collection].fields)
traverseFields({
config,
fields: props.collections[collection].fields,
i18n,
schemaMap,
schemaPath: collection,
validRelationships,
})
}
}
return schemaMap
},
i18n,
nodes: [
createNode({
converters: {
html: {
converter: async ({ node, req }) => {
// @ts-expect-error
const id = node?.value?.id || node?.value // for backwards-compatibility
if (req?.payload) {
const uploadDocument: {
value?: TypeWithID & FileData
} = {}
try {
await populate({
id,
collectionSlug: node.relationTo,
currentDepth: 0,
data: uploadDocument,
depth: 1,
draft: false,
key: 'value',
overrideAccess: false,
req,
showHiddenFields: false,
})
} catch (ignored) {
// eslint-disable-next-line no-console
console.error(
'Lexical upload node HTML converter: error fetching upload file',
ignored,
'Node:',
node,
)
return `<img />`
}
const url = getAbsoluteURL(uploadDocument?.value?.url, req?.payload)
/**
* If the upload is not an image, return a link to the upload
*/
if (!uploadDocument?.value?.mimeType?.startsWith('image')) {
return `<a href="${url}" rel="noopener noreferrer">${uploadDocument.value?.filename}</a>`
}
/**
* If the upload is a simple image with no different sizes, return a simple img tag
*/
if (
!uploadDocument?.value?.sizes ||
!Object.keys(uploadDocument?.value?.sizes).length
) {
return `<img src="${url}" alt="${uploadDocument?.value?.filename}" width="${uploadDocument?.value?.width}" height="${uploadDocument?.value?.height}"/>`
}
/**
* If the upload is an image with different sizes, return a picture element
*/
let pictureHTML = '<picture>'
// Iterate through each size in the data.sizes object
for (const size in uploadDocument.value?.sizes) {
const imageSize: FileSize & {
url?: string
} = uploadDocument.value?.sizes[size]
// Skip if any property of the size object is null
if (
!imageSize.width ||
!imageSize.height ||
!imageSize.mimeType ||
!imageSize.filesize ||
!imageSize.filename ||
!imageSize.url
) {
continue
}
const imageSizeURL = getAbsoluteURL(imageSize?.url, req?.payload)
pictureHTML += `<source srcset="${imageSizeURL}" media="(max-width: ${imageSize.width}px)" type="${imageSize.mimeType}">`
}
// Add the default img tag
pictureHTML += `<img src="${url}" alt="Image" width="${uploadDocument.value?.width}" height="${uploadDocument.value?.height}">`
pictureHTML += '</picture>'
return pictureHTML
} else {
return `<img src="${id}" />`
}
},
nodeTypes: [UploadNode.getType()],
},
},
getSubFields: ({ node, req }) => {
const collection = req.payload.collections[node?.relationTo]
if (collection) {
const collectionFieldSchema = props?.collections?.[node?.relationTo]?.fields
if (Array.isArray(collectionFieldSchema)) {
if (!collectionFieldSchema?.length) {
return null
}
return collectionFieldSchema
}
}
return null
},
getSubFieldsData: ({ node }) => {
return node?.fields
},
graphQLPopulationPromises: [uploadPopulationPromiseHOC(props)],
hooks: {
afterRead: [
({
currentDepth,
depth,
draft,
node,
overrideAccess,
populationPromises,
req,
showHiddenFields,
}) => {
if (!node?.value) {
return node
}
const collection = req.payload.collections[node?.relationTo]
if (!collection) {
return node
}
// @ts-expect-error
const id = node?.value?.id || node?.value // for backwards-compatibility
const populateDepth =
props?.maxDepth !== undefined && props?.maxDepth < depth
? props?.maxDepth
: depth
populationPromises.push(
populate({
id,
collectionSlug: collection.config.slug,
currentDepth,
data: node,
depth: populateDepth,
draft,
key: 'value',
overrideAccess,
req,
showHiddenFields,
}),
)
return node
},
],
},
node: UploadNode,
validations: [uploadValidation(props)],
}),
],
sanitizedServerFeatureProps: props,
for (const collection in props.collections) {
if (props.collections[collection].fields?.length) {
props.collections[collection].fields = await sanitizeFields({
config: _config as unknown as Config,
fields: props.collections[collection].fields,
requireFieldLevelRichTextEditor: isRoot,
validRelationships,
})
}
},
key: 'upload',
serverFeatureProps: props,
}
}
}
return {
ClientFeature: UploadFeatureClient,
clientFeatureProps: clientProps,
generateSchemaMap: ({ config, i18n, props }) => {
if (!props?.collections) return null
const schemaMap = new Map<string, Field[]>()
const validRelationships = config.collections.map((c) => c.slug) || []
for (const collection in props.collections) {
if (props.collections[collection].fields?.length) {
schemaMap.set(collection, props.collections[collection].fields)
traverseFields({
config,
fields: props.collections[collection].fields,
i18n,
schemaMap,
schemaPath: collection,
validRelationships,
})
}
}
return schemaMap
},
i18n,
nodes: [
createNode({
converters: {
html: {
converter: async ({ node, req }) => {
// @ts-expect-error
const id = node?.value?.id || node?.value // for backwards-compatibility
if (req?.payload) {
const uploadDocument: {
value?: TypeWithID & FileData
} = {}
try {
await populate({
id,
collectionSlug: node.relationTo,
currentDepth: 0,
data: uploadDocument,
depth: 1,
draft: false,
key: 'value',
overrideAccess: false,
req,
showHiddenFields: false,
})
} catch (ignored) {
// eslint-disable-next-line no-console
console.error(
'Lexical upload node HTML converter: error fetching upload file',
ignored,
'Node:',
node,
)
return `<img />`
}
const url = getAbsoluteURL(uploadDocument?.value?.url, req?.payload)
/**
* If the upload is not an image, return a link to the upload
*/
if (!uploadDocument?.value?.mimeType?.startsWith('image')) {
return `<a href="${url}" rel="noopener noreferrer">${uploadDocument.value?.filename}</a>`
}
/**
* If the upload is a simple image with no different sizes, return a simple img tag
*/
if (
!uploadDocument?.value?.sizes ||
!Object.keys(uploadDocument?.value?.sizes).length
) {
return `<img src="${url}" alt="${uploadDocument?.value?.filename}" width="${uploadDocument?.value?.width}" height="${uploadDocument?.value?.height}"/>`
}
/**
* If the upload is an image with different sizes, return a picture element
*/
let pictureHTML = '<picture>'
// Iterate through each size in the data.sizes object
for (const size in uploadDocument.value?.sizes) {
const imageSize: FileSize & {
url?: string
} = uploadDocument.value?.sizes[size]
// Skip if any property of the size object is null
if (
!imageSize.width ||
!imageSize.height ||
!imageSize.mimeType ||
!imageSize.filesize ||
!imageSize.filename ||
!imageSize.url
) {
continue
}
const imageSizeURL = getAbsoluteURL(imageSize?.url, req?.payload)
pictureHTML += `<source srcset="${imageSizeURL}" media="(max-width: ${imageSize.width}px)" type="${imageSize.mimeType}">`
}
// Add the default img tag
pictureHTML += `<img src="${url}" alt="Image" width="${uploadDocument.value?.width}" height="${uploadDocument.value?.height}">`
pictureHTML += '</picture>'
return pictureHTML
} else {
return `<img src="${id}" />`
}
},
nodeTypes: [UploadNode.getType()],
},
},
getSubFields: ({ node, req }) => {
const collection = req.payload.collections[node?.relationTo]
if (collection) {
const collectionFieldSchema = props?.collections?.[node?.relationTo]?.fields
if (Array.isArray(collectionFieldSchema)) {
if (!collectionFieldSchema?.length) {
return null
}
return collectionFieldSchema
}
}
return null
},
getSubFieldsData: ({ node }) => {
return node?.fields
},
graphQLPopulationPromises: [uploadPopulationPromiseHOC(props)],
hooks: {
afterRead: [
({
currentDepth,
depth,
draft,
node,
overrideAccess,
populationPromises,
req,
showHiddenFields,
}) => {
if (!node?.value) {
return node
}
const collection = req.payload.collections[node?.relationTo]
if (!collection) {
return node
}
// @ts-expect-error
const id = node?.value?.id || node?.value // for backwards-compatibility
const populateDepth =
props?.maxDepth !== undefined && props?.maxDepth < depth ? props?.maxDepth : depth
populationPromises.push(
populate({
id,
collectionSlug: collection.config.slug,
currentDepth,
data: node,
depth: populateDepth,
draft,
key: 'value',
overrideAccess,
req,
showHiddenFields,
}),
)
return node
},
],
},
node: UploadNode,
validations: [uploadValidation(props)],
}),
],
sanitizedServerFeatureProps: props,
}
},
key: 'upload',
})

View File

@@ -1,4 +1,4 @@
import type { PopulationPromise } from '../types.js'
import type { PopulationPromise } from '../typesServer.js'
import type { UploadFeatureProps } from './feature.server.js'
import type { SerializedUploadNode } from './nodes/UploadNode.js'

View File

@@ -14,7 +14,7 @@ import {
} from 'lexical'
import React, { useEffect } from 'react'
import type { PluginComponentWithAnchor } from '../../types.js'
import type { PluginComponentWithAnchor } from '../../typesClient.js'
import type { UploadFeaturePropsClient } from '../feature.client.js'
import type { UploadData } from '../nodes/UploadNode.js'

View File

@@ -1,7 +1,7 @@
import { buildStateFromSchema } from '@payloadcms/ui/forms/buildStateFromSchema'
import { isValidID } from 'payload'
import type { NodeValidation } from '../types.js'
import type { NodeValidation } from '../typesServer.js'
import type { UploadFeatureProps } from './feature.server.js'
import type { SerializedUploadNode } from './nodes/UploadNode.js'

View File

@@ -5,7 +5,7 @@ import type { EditorConfig as LexicalEditorConfig } from 'lexical'
import { ShimmerEffect, useClientFunctions, useFieldProps } from '@payloadcms/ui'
import React, { Suspense, lazy, useEffect, useState } from 'react'
import type { FeatureProviderClient } from '../features/types.js'
import type { FeatureProviderClient } from '../features/typesClient.js'
import type { SanitizedClientEditorConfig } from '../lexical/config/types.js'
import type { GeneratedFeatureProviderComponent, LexicalFieldAdminProps } from '../types.js'

View File

@@ -17,7 +17,7 @@ import {
withNullableJSONSchemaType,
} from 'payload'
import type { FeatureProviderServer, ResolvedServerFeatureMap } from './features/types.js'
import type { FeatureProviderServer, ResolvedServerFeatureMap } from './features/typesServer.js'
import type { SanitizedServerEditorConfig } from './lexical/config/types.js'
import type {
AdapterProps,
@@ -897,6 +897,20 @@ export { InlineToolbarFeature } from './features/toolbars/inline/feature.server.
export type { ToolbarGroup, ToolbarGroupItem } from './features/toolbars/types.js'
export { createNode } from './features/typeUtilities.js' // Only useful in feature.server.ts
export type {
ClientComponentProps,
ClientFeature,
ClientFeatureProviderMap,
FeatureProviderClient,
FeatureProviderProviderClient,
PluginComponent,
PluginComponentWithAnchor,
ResolvedClientFeature,
ResolvedClientFeatureMap,
SanitizedClientFeatures,
SanitizedPlugin,
} from './features/typesClient.js'
export type {
AfterChangeNodeHook,
AfterChangeNodeHookArgs,
@@ -907,28 +921,17 @@ export type {
BeforeChangeNodeHookArgs,
BeforeValidateNodeHook,
BeforeValidateNodeHookArgs,
ClientComponentProps,
ClientFeature,
ClientFeatureProviderMap,
FeatureProviderClient,
FeatureProviderProviderClient,
FeatureProviderProviderServer,
FeatureProviderServer,
NodeValidation,
NodeWithHooks,
PluginComponent,
PluginComponentWithAnchor,
PopulationPromise,
ResolvedClientFeature,
ResolvedClientFeatureMap,
ResolvedServerFeature,
ResolvedServerFeatureMap,
SanitizedClientFeatures,
SanitizedPlugin,
SanitizedServerFeatures,
ServerFeature,
ServerFeatureProviderMap,
} from './features/types.js'
} from './features/typesServer.js'
export { UploadFeature } from './features/upload/feature.server.js'

View File

@@ -1,6 +1,6 @@
import React from 'react'
import type { SanitizedPlugin } from '../features/types.js'
import type { SanitizedPlugin } from '../features/typesClient.js'
export const EditorPlugin: React.FC<{
anchorElem?: HTMLDivElement

View File

@@ -4,7 +4,7 @@ import type {
ClientFeatureProviderMap,
FeatureProviderClient,
ResolvedClientFeatureMap,
} from '../../../features/types.js'
} from '../../../features/typesClient.js'
import type { ClientEditorConfig } from '../types.js'
/**

View File

@@ -2,7 +2,10 @@
import type { EditorConfig as LexicalEditorConfig } from 'lexical'
import type { ResolvedClientFeatureMap, SanitizedClientFeatures } from '../../../features/types.js'
import type {
ResolvedClientFeatureMap,
SanitizedClientFeatures,
} from '../../../features/typesClient.js'
import type { LexicalFieldAdminProps } from '../../../types.js'
import type { SanitizedClientEditorConfig } from '../types.js'

View File

@@ -1,6 +1,6 @@
import type { EditorConfig as LexicalEditorConfig } from 'lexical'
import type { FeatureProviderServer } from '../../../features/types.js'
import type { FeatureProviderServer } from '../../../features/typesServer.js'
import type { ServerEditorConfig } from '../types.js'
import { AlignFeature } from '../../../features/align/feature.server.js'

View File

@@ -4,7 +4,7 @@ import type {
FeatureProviderServer,
ResolvedServerFeatureMap,
ServerFeatureProviderMap,
} from '../../../features/types.js'
} from '../../../features/typesServer.js'
import type { ServerEditorConfig } from '../types.js'
type DependencyGraph = {

View File

@@ -1,6 +1,9 @@
import type { SanitizedConfig } from 'payload'
import type { ResolvedServerFeatureMap, SanitizedServerFeatures } from '../../../features/types.js'
import type {
ResolvedServerFeatureMap,
SanitizedServerFeatures,
} from '../../../features/typesServer.js'
import type { SanitizedServerEditorConfig, ServerEditorConfig } from '../types.js'
import { loadFeatures } from './loader.js'

View File

@@ -2,12 +2,14 @@ import type { EditorConfig as LexicalEditorConfig } from 'lexical'
import type {
FeatureProviderClient,
FeatureProviderServer,
ResolvedClientFeatureMap,
ResolvedServerFeatureMap,
SanitizedClientFeatures,
} from '../../features/typesClient.js'
import type {
FeatureProviderServer,
ResolvedServerFeatureMap,
SanitizedServerFeatures,
} from '../../features/types.js'
} from '../../features/typesServer.js'
import type { LexicalFieldAdminProps } from '../../types.js'
export type ServerEditorConfig = {

View File

@@ -1,7 +1,7 @@
import type { SerializedEditorState } from 'lexical'
import type { RichTextAdapter } from 'payload'
import type { PopulationPromise } from '../features/types.js'
import type { PopulationPromise } from '../features/typesServer.js'
import type { AdapterProps } from '../types.js'
import { recurseNodes } from '../utilities/forEachNodeRecursively.js'

View File

@@ -2,7 +2,7 @@ import type { Field, PayloadRequestWithData, RequestContext } from 'payload'
import { afterReadTraverseFields } from 'payload'
import type { PopulationPromise } from '../features/types.js'
import type { PopulationPromise } from '../features/typesServer.js'
type NestedRichTextFieldsArgs = {
context: RequestContext

View File

@@ -8,7 +8,7 @@ import type {
} from 'payload'
import type React from 'react'
import type { FeatureProviderServer } from './features/types.js'
import type { FeatureProviderServer } from './features/typesServer.js'
import type { SanitizedServerEditorConfig } from './lexical/config/types.js'
export type LexicalFieldAdminProps = {

View File

@@ -7,7 +7,7 @@ import type {
FeatureProviderClient,
FeatureProviderProviderClient,
ResolvedClientFeatureMap,
} from '../features/types.js'
} from '../features/typesClient.js'
import type { ClientEditorConfig } from '../lexical/config/types.js'
import { createClientComponent } from '../features/createClientComponent.js'

View File

@@ -6,7 +6,7 @@ import type {
ResolvedServerFeatureMap,
ServerFeature,
ServerFeatureProviderMap,
} from '../features/types.js'
} from '../features/typesServer.js'
import type { ServerEditorConfig } from '../lexical/config/types.js'
export type CreateServerFeatureArgs<UnSanitizedProps, SanitizedProps, ClientProps> = Pick<

View File

@@ -3,7 +3,7 @@ import type { RichTextAdapter } from 'payload'
import { mapFields } from '@payloadcms/ui/utilities/buildComponentMap'
import React from 'react'
import type { ResolvedServerFeatureMap } from '../features/types.js'
import type { ResolvedServerFeatureMap } from '../features/typesServer.js'
import type { GeneratedFeatureProviderComponent } from '../types.js'
export const getGenerateComponentMap =

View File

@@ -1,6 +1,6 @@
import type { RichTextAdapter } from 'payload'
import type { ResolvedServerFeatureMap } from '../features/types.js'
import type { ResolvedServerFeatureMap } from '../features/typesServer.js'
export const getGenerateSchemaMap =
(args: { resolvedFeatureMap: ResolvedServerFeatureMap }): RichTextAdapter['generateSchemaMap'] =>

View File

@@ -2,7 +2,7 @@
import { useAddClientFunction, useFieldProps, useTableCell } from '@payloadcms/ui'
import type { FeatureProviderClient } from '../features/types.js'
import type { FeatureProviderClient } from '../features/typesClient.js'
export const useLexicalFeature = <ClientFeatureProps,>(
featureKey: string,

View File

@@ -1,7 +1,7 @@
import type { SerializedEditorState, SerializedLexicalNode } from 'lexical'
import type { RichTextField, ValidateOptions } from 'payload'
import type { NodeValidation } from '../features/types.js'
import type { NodeValidation } from '../features/typesServer.js'
export async function validateNodes({
nodeValidations,