This commit is contained in:
Alessio Gravili
2025-09-01 22:24:37 -07:00
parent 4b9a5ae7c2
commit 5f7331cbe4
6 changed files with 53 additions and 55 deletions

View File

@@ -1,4 +1,6 @@
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
import { getClientConfig } from '@payloadcms/ui/utilities/getClientConfig'
import { getClientSchemaMap } from '@payloadcms/ui/utilities/getClientSchemaMap'
import { getSchemaMap } from '@payloadcms/ui/utilities/getSchemaMap'
import {
createClientField,
@@ -10,7 +12,6 @@ import {
import {
type DefaultTypedEditorState,
lexicalEditor,
type LexicalFieldAdminProps,
type LexicalRichTextAdapter,
type SanitizedServerEditorConfig,
@@ -19,11 +20,11 @@ import {
export type RenderLexicalServerFunctionArgs = {
admin?: LexicalFieldAdminProps
/**
* 'default' or {global|collections}.entitySlug.fieldSchemaPath
* {global|collection}.entitySlug.fieldSchemaPath
*
* @example collections.posts.richText
* @example collection.posts.richText
*/
editorTarget: 'default' | ({} & string)
editorTarget: string
/**
* Pass the value this richtext field will receive when rendering it on the server.
* This helps provide initial state for sub-fields that are immediately rendered (like blocks)
@@ -41,9 +42,8 @@ export type RenderLexicalServerFunctionArgs = {
path?: string
/**
* Schema path to the field to render.
* @default field name
*/
schemaPath?: string
schemaPath: string
}
export type RenderLexicalServerFunctionReturnType = { Component: React.ReactNode }
@@ -53,38 +53,31 @@ export type RenderLexicalServerFunctionReturnType = { Component: React.ReactNode
export const _internal_renderLexical: ServerFunction<
RenderLexicalServerFunctionArgs,
Promise<RenderLexicalServerFunctionReturnType>
// eslint-disable-next-line @typescript-eslint/require-await
> = async ({ name, admin, editorTarget, importMap, initialValue, path, req, schemaPath }) => {
if (!req.user) {
throw new Error('Unauthorized')
}
let sanitizedEditor: LexicalRichTextAdapter
const [entityType, entitySlug, ...fieldPath] = editorTarget.split('.')
if (editorTarget === 'default') {
sanitizedEditor = await lexicalEditor()({
config: req.payload.config,
isRoot: false,
parentIsLocalized: false,
})
} else {
const [entityType, entitySlug, ...fieldPath] = editorTarget.split('.')
const schemaMap = getSchemaMap({
collectionSlug: entityType === 'collection' ? entitySlug : undefined,
config: req.payload.config,
globalSlug: entityType === 'global' ? entitySlug : undefined,
i18n: req.i18n,
})
const schemaMap = getSchemaMap({
collectionSlug: entityType === 'collections' ? entitySlug : undefined,
config: req.payload.config,
globalSlug: entityType === 'globals' ? entitySlug : undefined,
i18n: req.i18n,
})
const targetField = schemaMap.get(`${entitySlug}.${fieldPath.join('.')}`) as
| RichTextField
| undefined
const field = schemaMap.get(`${entitySlug}.${fieldPath.join('.')}`) as RichTextField | undefined
if (!field?.editor || typeof field.editor === 'function') {
throw new Error(`No editor found for target: ${editorTarget}`)
}
sanitizedEditor = field.editor as LexicalRichTextAdapter
if (!targetField?.editor || typeof targetField.editor === 'function') {
throw new Error(`No editor found for target: ${editorTarget}`)
}
const sanitizedEditor = targetField.editor as LexicalRichTextAdapter
if (!sanitizedEditor) {
throw new Error(`No editor found for target: ${editorTarget}`)
}
@@ -95,6 +88,21 @@ export const _internal_renderLexical: ServerFunction<
editor: sanitizedEditor,
}
// Provide client schema map as it would have been provided if the target editor field would have been rendered.
// Only then will it contain all the lexical-internal entries
const clientSchemaMap = getClientSchemaMap({
collectionSlug: entityType === 'collection' ? entitySlug : undefined,
config: getClientConfig({
config: req.payload.config,
i18n: req.i18n,
importMap: req.payload.importMap,
}),
globalSlug: entityType === 'global' ? entitySlug : undefined,
i18n: req.i18n,
payload: req.payload,
schemaMap,
})
const FieldComponent = RenderServerComponent({
Component: sanitizedEditor.FieldComponent,
importMap,
@@ -106,11 +114,12 @@ export const _internal_renderLexical: ServerFunction<
i18n: req.i18n,
importMap,
}) as RichTextFieldClient,
clientFieldSchemaMap: new Map<string, RichTextFieldClient>(),
collectionSlug: '-',
clientFieldSchemaMap: clientSchemaMap,
// collectionSlug is typed incorrectly - @todo make it accept undefined in 4.0
collectionSlug: entityType === 'collection' && entitySlug ? entitySlug : '-',
data: {},
field,
fieldSchemaMap: new Map<string, RichTextField>(),
fieldSchemaMap: schemaMap,
forceRender: true,
formState: {},
i18n: req.i18n,

View File

@@ -18,18 +18,18 @@ import type {
* @experimental - may break in minor releases
*/
export const useRenderEditor_internal_ = (
args: Omit<RenderLexicalServerFunctionArgs, 'initialValue'>,
args: Omit<RenderLexicalServerFunctionArgs, 'initialValue' | 'schemaPath'>,
) => {
const { name, admin, editorTarget, path, schemaPath } = args
const { name, admin, editorTarget, path } = args
const [Component, setComponent] = React.useState<null | React.ReactNode>(null)
const serverFunctionContext = useServerFunctions()
const { serverFunction } = serverFunctionContext
const [entityType, entitySlug, ...fieldPath] = editorTarget.split('.')
const renderLexical = useCallback(
(args?: Pick<RenderLexicalServerFunctionArgs, 'initialValue'>) => {
async function render() {
const [entityType, entitySlug, ...fieldPath] = editorTarget.split('.')
const { Component } = (await serverFunction({
name: 'render-lexical',
args: {
@@ -46,7 +46,7 @@ export const useRenderEditor_internal_ = (
}
void render()
},
[serverFunction, admin, editorTarget, name, path, schemaPath],
[serverFunction, name, admin, editorTarget, path, entitySlug, fieldPath],
)
const WrappedComponent = React.useMemo(() => {
@@ -66,8 +66,6 @@ export const useRenderEditor_internal_ = (
return null
}
const [entityType, entitySlug, ...fieldPath] = editorTarget.split('.')
/**
* By default, the lexical will make form state requests (e.g. to get drawer fields), passing in the arguments
* of the current field. However, we need to override those arguments to get it to make requests based on the
@@ -76,23 +74,10 @@ export const useRenderEditor_internal_ = (
const lexicalServerFunctionContext: ServerFunctionsContextType = {
...serverFunctionContext,
getFormState: async (getFormStateArgs) => {
const currentSchemaPathWithoutEntitySlug = schemaPath ?? name
const editorTargetSchemaPath = `${entitySlug}.${fieldPath.join('.')}`
console.log('getFormStateArgs.schemaPath', getFormStateArgs.schemaPath)
const correctedSchemaPath = getFormStateArgs.schemaPath.startsWith(
currentSchemaPathWithoutEntitySlug,
)
? editorTargetSchemaPath +
getFormStateArgs.schemaPath.slice(currentSchemaPathWithoutEntitySlug.length)
: getFormStateArgs.schemaPath
return serverFunctionContext.getFormState({
...getFormStateArgs,
collectionSlug: entityType === 'collection' ? entitySlug : undefined,
globalSlug: entityType === 'global' ? entitySlug : undefined,
//schemaPath: correctedSchemaPath,
})
},
}
@@ -125,7 +110,7 @@ export const useRenderEditor_internal_ = (
}
return Memoized
}, [Component, name, path, serverFunctionContext, editorTarget, schemaPath, name])
}, [Component, serverFunctionContext, path, name, entityType, entitySlug])
return { Component: WrappedComponent, renderLexical }
}

View File

@@ -287,6 +287,8 @@ export { Warning as WarningIcon } from '../../providers/ToastContainer/icons/War
export {
type RenderDocumentResult,
type RenderDocumentServerFunction,
ServerFunctionsContext,
type ServerFunctionsContextType,
ServerFunctionsProvider,
useServerFunctions,
} from '../../providers/ServerFunctions/index.js'

View File

@@ -100,7 +100,7 @@ type GetFolderResultsComponentAndDataClient = (
} & Omit<GetFolderResultsComponentAndDataArgs, 'req'>,
) => ReturnType<typeof getFolderResultsComponentAndDataHandler>
type ServerFunctionsContextType = {
export type ServerFunctionsContextType = {
copyDataFromLocale: CopyDataFromLocaleClient
getDocumentSlots: GetDocumentSlots
getFolderResultsComponentAndData: GetFolderResultsComponentAndDataClient

View File

@@ -6,10 +6,12 @@ import type { JSONFieldClientComponent } from 'payload'
import { buildEditorState, useRenderEditor_internal_ } from '@payloadcms/richtext-lexical/client'
import React, { useEffect, useRef, useState } from 'react'
import { lexicalFullyFeaturedSlug } from '../../../lexical/slugs.js'
export const Component: JSONFieldClientComponent = (args) => {
const { Component, renderLexical } = useRenderEditor_internal_({
name: 'richText',
editorTarget: 'default',
editorTarget: `collection.${lexicalFullyFeaturedSlug}.richText`,
})
const mounted = useRef(false)

View File

@@ -10,7 +10,7 @@ import { lexicalFullyFeaturedSlug } from '../../slugs.js'
export const Component: JSONFieldClientComponent = (args) => {
const { Component, renderLexical } = useRenderEditor_internal_({
name: 'richText2',
editorTarget: `collections.${lexicalFullyFeaturedSlug}.richText`,
editorTarget: `collection.${lexicalFullyFeaturedSlug}.richText`,
})
const mounted = useRef(false)