diff --git a/packages/richtext-lexical/src/field/RenderLexical/renderLexical.tsx b/packages/richtext-lexical/src/field/RenderLexical/renderLexical.tsx index 9ef5cf9d27..dc38578f11 100644 --- a/packages/richtext-lexical/src/field/RenderLexical/renderLexical.tsx +++ b/packages/richtext-lexical/src/field/RenderLexical/renderLexical.tsx @@ -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 + // 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(), - 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(), + fieldSchemaMap: schemaMap, forceRender: true, formState: {}, i18n: req.i18n, diff --git a/packages/richtext-lexical/src/field/RenderLexical/useRenderEditor.tsx b/packages/richtext-lexical/src/field/RenderLexical/useRenderEditor.tsx index f22458168e..07757b482a 100644 --- a/packages/richtext-lexical/src/field/RenderLexical/useRenderEditor.tsx +++ b/packages/richtext-lexical/src/field/RenderLexical/useRenderEditor.tsx @@ -18,18 +18,18 @@ import type { * @experimental - may break in minor releases */ export const useRenderEditor_internal_ = ( - args: Omit, + args: Omit, ) => { - const { name, admin, editorTarget, path, schemaPath } = args + const { name, admin, editorTarget, path } = args const [Component, setComponent] = React.useState(null) const serverFunctionContext = useServerFunctions() const { serverFunction } = serverFunctionContext + const [entityType, entitySlug, ...fieldPath] = editorTarget.split('.') + const renderLexical = useCallback( (args?: Pick) => { 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 } } diff --git a/packages/ui/src/exports/client/index.ts b/packages/ui/src/exports/client/index.ts index f051cba016..0ed77cf7d1 100644 --- a/packages/ui/src/exports/client/index.ts +++ b/packages/ui/src/exports/client/index.ts @@ -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' diff --git a/packages/ui/src/providers/ServerFunctions/index.tsx b/packages/ui/src/providers/ServerFunctions/index.tsx index 91d391e51b..46f4ec1fa2 100644 --- a/packages/ui/src/providers/ServerFunctions/index.tsx +++ b/packages/ui/src/providers/ServerFunctions/index.tsx @@ -100,7 +100,7 @@ type GetFolderResultsComponentAndDataClient = ( } & Omit, ) => ReturnType -type ServerFunctionsContextType = { +export type ServerFunctionsContextType = { copyDataFromLocale: CopyDataFromLocaleClient getDocumentSlots: GetDocumentSlots getFolderResultsComponentAndData: GetFolderResultsComponentAndDataClient diff --git a/test/lexical/collections/OnDemandDefault/Component.tsx b/test/lexical/collections/OnDemandDefault/Component.tsx index 731e249311..9de9e546d3 100644 --- a/test/lexical/collections/OnDemandDefault/Component.tsx +++ b/test/lexical/collections/OnDemandDefault/Component.tsx @@ -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) diff --git a/test/lexical/collections/OnDemandFullyFeatured/Component.tsx b/test/lexical/collections/OnDemandFullyFeatured/Component.tsx index fe961be742..2f89459f68 100644 --- a/test/lexical/collections/OnDemandFullyFeatured/Component.tsx +++ b/test/lexical/collections/OnDemandFullyFeatured/Component.tsx @@ -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)