diff --git a/packages/next/src/utilities/createClientConfig.ts b/packages/next/src/utilities/createClientConfig.ts index d7ee705f00..b8500a1863 100644 --- a/packages/next/src/utilities/createClientConfig.ts +++ b/packages/next/src/utilities/createClientConfig.ts @@ -13,6 +13,10 @@ export const sanitizeField = (f) => { field.fields = sanitizeFields(field.fields) } + if ('editor' in field) { + delete field.editor + } + if ('blocks' in field) { field.blocks = field.blocks.map((block) => { const sanitized = { ...block } @@ -94,6 +98,7 @@ export const createClientConfig = async ( delete clientConfig.endpoints delete clientConfig.db + delete clientConfig.editor 'localization' in clientConfig && clientConfig.localization && diff --git a/packages/payload/src/admin/RichText.ts b/packages/payload/src/admin/RichText.ts index b557801815..8ca301885f 100644 --- a/packages/payload/src/admin/RichText.ts +++ b/packages/payload/src/admin/RichText.ts @@ -26,6 +26,7 @@ type RichTextAdapterBase< incomingEditorState: Value siblingDoc: Record }) => Promise | null + generateComponentMap: () => Map | Promise> outputSchema?: ({ field, isRequired, diff --git a/packages/richtext-slate/src/field/RichText.tsx b/packages/richtext-slate/src/field/RichText.tsx index 2bd3133db0..952024cf20 100644 --- a/packages/richtext-slate/src/field/RichText.tsx +++ b/packages/richtext-slate/src/field/RichText.tsx @@ -5,22 +5,23 @@ import type { HistoryEditor } from 'slate-history' import type { ReactEditor } from 'slate-react' import { getTranslation } from '@payloadcms/translations' -import { - Error, - FieldDescription, - Label, - useEditDepth, - useField, - useTranslation, -} from '@payloadcms/ui' +import { useEditDepth, useField, useTranslation } from '@payloadcms/ui' import isHotkey from 'is-hotkey' import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { Node, Element as SlateElement, Text, Transforms, createEditor } from 'slate' import { withHistory } from 'slate-history' import { Editable, Slate, withReact } from 'slate-react' -import type { ElementNode, FieldProps, RichTextElement, RichTextLeaf, TextNode } from '../types' +import type { FormFieldBase } from '../../../ui/src/forms/fields/shared' +import type { + ElementNode, + RichTextCustomLeaf, + RichTextElement, + RichTextLeaf, + TextNode, +} from '../types' +import { withCondition } from '../../../ui/src/forms/withCondition' import { defaultRichTextValue } from '../data/defaultValue' import { richTextValidate } from '../data/validation' import elementTypes from './elements' @@ -33,6 +34,8 @@ import toggleLeaf from './leaves/toggle' import mergeCustomFunctions from './mergeCustomFunctions' import withEnterBreakOut from './plugins/withEnterBreakOut' import withHTML from './plugins/withHTML' +import { LeafButtonProvider } from './providers/LeafButtonProvider' +import { LeafProvider } from './providers/LeafProvider' const defaultElements: RichTextElement[] = [ 'h1', @@ -60,127 +63,163 @@ declare module 'slate' { } } -const RichText: React.FC = (props) => { +const RichText: React.FC< + FormFieldBase & { + name: string + richTextComponentMap: Map + } +> = (props) => { const { name, - admin: { - className, - condition, - description, - hideGutter, - placeholder, - readOnly, - style, - width, - } = { - className: undefined, - condition: undefined, - description: undefined, - hideGutter: undefined, - placeholder: undefined, - readOnly: undefined, - style: undefined, - width: undefined, - }, - admin, - defaultValue: defaultValueFromProps, - label, + Description, + Error, + Label, + className, path: pathFromProps, + placeholder, + readOnly, required, + richTextComponentMap, + style, validate = richTextValidate, + width, } = props - const elements: RichTextElement[] = admin?.elements || defaultElements - const leaves: RichTextLeaf[] = admin?.leaves || defaultLeaves + const [leaves] = useState(() => { + const enabledLeaves: Record< + string, + { + Button: React.ReactNode + Leaf: React.ReactNode + name: string + } + > = {} - const path = pathFromProps || name + for (const [key, value] of richTextComponentMap) { + if (key.startsWith('leaf.button') || key.startsWith('leaf.component')) { + const leafName = key.replace('leaf.button', '').replace('leaf.component', '') + if (!enabledLeaves[leafName]) { + enabledLeaves[leafName] = { + name: leafName, + Button: null, + Leaf: null, + } + } + + if (key.startsWith('leaf.button')) enabledLeaves[leafName].Button = value + if (key.startsWith('leaf.component')) enabledLeaves[leafName].Leaf = value + } + } + + return enabledLeaves + }) + + const elements: RichTextElement[] = defaultElements const { i18n } = useTranslation() - const [loaded, setLoaded] = useState(false) - const [enabledElements, setEnabledElements] = useState({}) - const [enabledLeaves, setEnabledLeaves] = useState({}) const editorRef = useRef(null) const toolbarRef = useRef(null) const drawerDepth = useEditDepth() const drawerIsOpen = drawerDepth > 1 - const renderElement = useCallback( - ({ attributes, children, element }) => { - const matchedElement = enabledElements[element.type] - const Element = matchedElement?.Element - - let attr = { ...attributes } - - // this converts text alignment to margin when dealing with void elements - if (element.textAlign) { - if (element.type === 'relationship' || element.type === 'upload') { - switch (element.textAlign) { - case 'left': - attr = { ...attr, style: { marginRight: 'auto' } } - break - case 'right': - attr = { ...attr, style: { marginLeft: 'auto' } } - break - case 'center': - attr = { ...attr, style: { marginLeft: 'auto', marginRight: 'auto' } } - break - default: - attr = { ...attr, style: { textAlign: element.textAlign } } - break - } - } else if (element.type === 'li') { - switch (element.textAlign) { - case 'right': - attr = { ...attr, style: { listStylePosition: 'inside', textAlign: 'right' } } - break - case 'center': - attr = { ...attr, style: { listStylePosition: 'inside', textAlign: 'center' } } - break - case 'left': - default: - attr = { ...attr, style: { listStylePosition: 'outside', textAlign: 'left' } } - break - } - } else { - attr = { ...attr, style: { textAlign: element.textAlign } } - } + const memoizedValidate = useCallback( + (value, validationOptions) => { + if (typeof validate === 'function') { + return validate(value, { ...validationOptions, required }) } - - if (Element) { - const el = ( - - {children} - - ) - - return el - } - - return
{children}
}, - [enabledElements, path, props], + [validate, required], ) + const { initialValue, path, schemaPath, setValue, showError, value } = useField({ + path: pathFromProps || name, + validate: memoizedValidate, + }) + + const renderElement = useCallback(({ attributes, children, element }) => { + // const matchedElement = enabledElements[element.type] + // const Element = matchedElement?.Element + + const attr = { ...attributes } + + // // this converts text alignment to margin when dealing with void elements + // if (element.textAlign) { + // if (element.type === 'relationship' || element.type === 'upload') { + // switch (element.textAlign) { + // case 'left': + // attr = { ...attr, style: { marginRight: 'auto' } } + // break + // case 'right': + // attr = { ...attr, style: { marginLeft: 'auto' } } + // break + // case 'center': + // attr = { ...attr, style: { marginLeft: 'auto', marginRight: 'auto' } } + // break + // default: + // attr = { ...attr, style: { textAlign: element.textAlign } } + // break + // } + // } else if (element.type === 'li') { + // switch (element.textAlign) { + // case 'right': + // attr = { ...attr, style: { listStylePosition: 'inside', textAlign: 'right' } } + // break + // case 'center': + // attr = { ...attr, style: { listStylePosition: 'inside', textAlign: 'center' } } + // break + // case 'left': + // default: + // attr = { ...attr, style: { listStylePosition: 'outside', textAlign: 'left' } } + // break + // } + // } else { + // attr = { ...attr, style: { textAlign: element.textAlign } } + // } + // } + + // if (Element) { + // const el = ( + // + // {children} + // + // ) + + // return el + // } + + return
{children}
+ }, []) + const renderLeaf = useCallback( ({ attributes, children, leaf }) => { - const matchedLeaves = Object.entries(enabledLeaves).filter(([leafName]) => leaf[leafName]) - + const matchedLeaves = Object.entries(leaves).filter(([leafName]) => leaf[leafName]) + console.log(matchedLeaves, leaf) if (matchedLeaves.length > 0) { return matchedLeaves.reduce( - (result, [leafName], i) => { - if (enabledLeaves[leafName]?.Leaf) { - const Leaf = enabledLeaves[leafName]?.Leaf + (result, [, leafConfig], i) => { + if (leafConfig?.Leaf) { + const Leaf = leafConfig.Leaf + return ( - - {result} - + + {Leaf} + ) } @@ -192,31 +231,15 @@ const RichText: React.FC = (props) => { return {children} }, - [enabledLeaves, path, props], + [path, props, schemaPath, richTextComponentMap], ) - const memoizedValidate = useCallback( - (value, validationOptions) => { - return validate(value, { ...validationOptions, required }) - }, - [validate, required], - ) - - const fieldType = useField({ - condition, - path, - validate: memoizedValidate, - }) - - const { errorMessage, initialValue, setValue, showError, value } = fieldType - const classes = [ baseClass, 'field-type', className, showError && 'error', readOnly && `${baseClass}--read-only`, - !hideGutter && `${baseClass}--gutter`, ] .filter(Boolean) .join(' ') @@ -225,12 +248,12 @@ const RichText: React.FC = (props) => { let CreatedEditor = withEnterBreakOut(withHistory(withReact(createEditor()))) CreatedEditor = withHTML(CreatedEditor) - CreatedEditor = enablePlugins(CreatedEditor, elements) - CreatedEditor = enablePlugins(CreatedEditor, leaves) + // CreatedEditor = enablePlugins(CreatedEditor, elements) + // CreatedEditor = enablePlugins(CreatedEditor, leaves) return CreatedEditor // eslint-disable-next-line react-hooks/exhaustive-deps - }, [elements, leaves, path]) + }, [path]) // All slate changes fire the onChange event // including selection changes @@ -254,18 +277,6 @@ const RichText: React.FC = (props) => { [editor.operations, readOnly, setValue, value], ) - useEffect(() => { - if (!loaded) { - const mergedElements = mergeCustomFunctions(elements, elementTypes) - const mergedLeaves = mergeCustomFunctions(leaves, leafTypes) - - setEnabledElements(mergedElements) - setEnabledLeaves(mergedLeaves) - - setLoaded(true) - } - }, [loaded, elements, leaves]) - useEffect(() => { function setClickableState(clickState: 'disabled' | 'enabled') { const selectors = 'button, a, [role="button"]' @@ -280,16 +291,16 @@ const RichText: React.FC = (props) => { }) } - if (loaded && readOnly) { + if (readOnly) { setClickableState('disabled') } return () => { - if (loaded && readOnly) { + if (readOnly) { setClickableState('enabled') } } - }, [loaded, readOnly]) + }, [readOnly]) // useEffect(() => { // // If there is a change to the initial value, we need to reset Slate history @@ -301,10 +312,6 @@ const RichText: React.FC = (props) => { // } // }, [path, editor]); - if (!loaded) { - return null - } - let valueToRender = value if (typeof valueToRender === 'string') { @@ -316,7 +323,7 @@ const RichText: React.FC = (props) => { } } - if (!valueToRender) valueToRender = defaultValueFromProps || defaultRichTextValue + if (!valueToRender) valueToRender = defaultRichTextValue return (
= (props) => { }} >
- -
) } + export default withCondition(RichText) diff --git a/packages/richtext-slate/src/field/leaves/bold/index.tsx b/packages/richtext-slate/src/field/leaves/bold/index.tsx index a991ae0e0f..d2fadaca9a 100644 --- a/packages/richtext-slate/src/field/leaves/bold/index.tsx +++ b/packages/richtext-slate/src/field/leaves/bold/index.tsx @@ -1,11 +1,18 @@ import React from 'react' +import type { RichTextCustomLeaf } from '../../..' + import BoldIcon from '../../icons/Bold' +import { useLeaf } from '../../providers/LeafProvider' import LeafButton from '../Button' -const Bold = ({ attributes, children }) => {children} +const Bold = () => { + const { attributes, children } = useLeaf() + return {children} +} -const bold = { +const bold: RichTextCustomLeaf = { + name: 'bold', Button: () => ( diff --git a/packages/richtext-slate/src/field/leaves/code/index.tsx b/packages/richtext-slate/src/field/leaves/code/index.tsx index f8db79da29..ee49f93cb4 100644 --- a/packages/richtext-slate/src/field/leaves/code/index.tsx +++ b/packages/richtext-slate/src/field/leaves/code/index.tsx @@ -1,11 +1,18 @@ import React from 'react' +import type { RichTextCustomLeaf } from '../../..' + import CodeIcon from '../../icons/Code' +import { useLeaf } from '../../providers/LeafProvider' import LeafButton from '../Button' -const Code = ({ attributes, children }) => {children} +const Code = () => { + const { attributes, children } = useLeaf() + return {children} +} -const code = { +const code: RichTextCustomLeaf = { + name: 'code', Button: () => ( diff --git a/packages/richtext-slate/src/field/leaves/index.tsx b/packages/richtext-slate/src/field/leaves/index.tsx index af49853baf..008ea92d8b 100644 --- a/packages/richtext-slate/src/field/leaves/index.tsx +++ b/packages/richtext-slate/src/field/leaves/index.tsx @@ -1,13 +1,17 @@ +import type { RichTextCustomLeaf } from '../..' + import bold from './bold' import code from './code' import italic from './italic' import strikethrough from './strikethrough' import underline from './underline' -export default { +const defaultLeaves: Record = { bold, code, italic, strikethrough, underline, } + +export default defaultLeaves diff --git a/packages/richtext-slate/src/field/leaves/italic/index.tsx b/packages/richtext-slate/src/field/leaves/italic/index.tsx index dd71f3ab1b..0b9fddcb42 100644 --- a/packages/richtext-slate/src/field/leaves/italic/index.tsx +++ b/packages/richtext-slate/src/field/leaves/italic/index.tsx @@ -1,11 +1,18 @@ import React from 'react' +import type { RichTextCustomLeaf } from '../../..' + import ItalicIcon from '../../icons/Italic' +import { useLeaf } from '../../providers/LeafProvider' import LeafButton from '../Button' -const Italic = ({ attributes, children }) => {children} +const Italic = () => { + const { attributes, children } = useLeaf() + return {children} +} -const italic = { +const italic: RichTextCustomLeaf = { + name: 'italic', Button: () => ( diff --git a/packages/richtext-slate/src/field/leaves/strikethrough/index.tsx b/packages/richtext-slate/src/field/leaves/strikethrough/index.tsx index 79c3fb0078..d00f2cd846 100644 --- a/packages/richtext-slate/src/field/leaves/strikethrough/index.tsx +++ b/packages/richtext-slate/src/field/leaves/strikethrough/index.tsx @@ -1,11 +1,19 @@ +'use client' import React from 'react' +import type { RichTextCustomLeaf } from '../../..' + import StrikethroughIcon from '../../icons/Strikethrough' +import { useLeaf } from '../../providers/LeafProvider' import LeafButton from '../Button' -const Strikethrough = ({ attributes, children }) => {children} +const Strikethrough = () => { + const { attributes, children } = useLeaf() + return {children} +} -const strikethrough = { +const strikethrough: RichTextCustomLeaf = { + name: 'strikethrough', Button: () => ( diff --git a/packages/richtext-slate/src/field/leaves/underline/index.tsx b/packages/richtext-slate/src/field/leaves/underline/index.tsx index 05906395b1..f36fec070c 100644 --- a/packages/richtext-slate/src/field/leaves/underline/index.tsx +++ b/packages/richtext-slate/src/field/leaves/underline/index.tsx @@ -1,11 +1,18 @@ import React from 'react' +import type { RichTextCustomLeaf } from '../../..' + import UnderlineIcon from '../../icons/Underline' +import { useLeaf } from '../../providers/LeafProvider' import LeafButton from '../Button' -const Underline = ({ attributes, children }) => {children} +const Underline = () => { + const { attributes, children } = useLeaf() + return {children} +} -const underline = { +const underline: RichTextCustomLeaf = { + name: 'underline', Button: () => ( diff --git a/packages/richtext-slate/src/field/providers/ElementProvider.tsx b/packages/richtext-slate/src/field/providers/ElementProvider.tsx new file mode 100644 index 0000000000..e29c2d7bda --- /dev/null +++ b/packages/richtext-slate/src/field/providers/ElementProvider.tsx @@ -0,0 +1,35 @@ +'use client' +import React from 'react' + +type ElementContextType = { + path: string + schemaPath: string +} + +const ElementContext = React.createContext({ + path: '', + schemaPath: '', +}) + +export const ElementProvider: React.FC<{ + children: React.ReactNode + path: string + schemaPath: string +}> = (props) => { + const { children, ...rest } = props + + return ( + + {children} + + ) +} + +export const useElement = () => { + const path = React.useContext(ElementContext) + return path +} diff --git a/packages/richtext-slate/src/field/providers/LeafButtonProvider.tsx b/packages/richtext-slate/src/field/providers/LeafButtonProvider.tsx new file mode 100644 index 0000000000..aec5aae8f6 --- /dev/null +++ b/packages/richtext-slate/src/field/providers/LeafButtonProvider.tsx @@ -0,0 +1,41 @@ +'use client' +import React from 'react' + +import type { FormFieldBase } from '../../../../ui/src/forms/fields/shared' + +type LeafButtonContextType = { + fieldProps: FormFieldBase & { + name: string + } + path: string + schemaPath: string +} + +const LeafButtonContext = React.createContext({ + fieldProps: {} as any, + path: '', + schemaPath: '', +}) + +export const LeafButtonProvider: React.FC< + LeafButtonContextType & { + children: React.ReactNode + } +> = (props) => { + const { children, ...rest } = props + + return ( + + {children} + + ) +} + +export const useLeafButton = () => { + const path = React.useContext(LeafButtonContext) + return path +} diff --git a/packages/richtext-slate/src/field/providers/LeafProvider.tsx b/packages/richtext-slate/src/field/providers/LeafProvider.tsx new file mode 100644 index 0000000000..5f765b1aeb --- /dev/null +++ b/packages/richtext-slate/src/field/providers/LeafProvider.tsx @@ -0,0 +1,50 @@ +'use client' +import React from 'react' + +import type { FormFieldBase } from '../../../../ui/src/forms/fields/shared' + +type LeafContextType = { + attributes: Record + children: React.ReactNode + editorRef: React.MutableRefObject + fieldProps: FormFieldBase & { + name: string + } + leaf: string + path: string + schemaPath: string +} + +const LeafContext = React.createContext({ + attributes: {}, + children: null, + editorRef: null, + fieldProps: {} as any, + leaf: '', + path: '', + schemaPath: '', +}) + +export const LeafProvider: React.FC< + LeafContextType & { + result: React.ReactNode + } +> = (props) => { + const { children, result, ...rest } = props + + return ( + + {children} + + ) +} + +export const useLeaf = () => { + const path = React.useContext(LeafContext) + return path +} diff --git a/packages/richtext-slate/src/index.ts b/packages/richtext-slate/src/index.tsx similarity index 71% rename from packages/richtext-slate/src/index.ts rename to packages/richtext-slate/src/index.tsx index 7e33db4ae5..7134fadc4c 100644 --- a/packages/richtext-slate/src/index.ts +++ b/packages/richtext-slate/src/index.tsx @@ -2,13 +2,15 @@ import type { RichTextAdapter } from 'payload/types' import { withMergedProps } from '@payloadcms/ui/utilities' import { withNullableJSONSchemaType } from 'payload/utilities' +import React from 'react' -import type { AdapterArguments } from './types' +import type { AdapterArguments, RichTextCustomLeaf } from './types' import RichTextCell from './cell' import { richTextRelationshipPromise } from './data/richTextRelationshipPromise' import { richTextValidate } from './data/validation' import RichTextField from './field' +import leafTypes from './field/leaves' export function slateEditor(args: AdapterArguments): RichTextAdapter { return { @@ -20,6 +22,29 @@ export function slateEditor(args: AdapterArguments): RichTextAdapter { + const componentMap = new Map() + + ;(args?.admin?.leaves || Object.values(leafTypes)).forEach((leaf) => { + let leafObject: RichTextCustomLeaf + + if (typeof leaf === 'object' && leaf !== null) { + leafObject = leaf + } else if (typeof leaf === 'string' && leafTypes[leaf]) { + leafObject = leafTypes[leaf] + } + + if (leafObject) { + const LeafButton = leafObject.Button + const LeafComponent = leafObject.Leaf + + componentMap.set(`leaf.button.${leafObject.name}`, ) + componentMap.set(`leaf.component.${leafObject.name}`, ) + } + }) + + return componentMap + }, outputSchema: ({ isRequired }) => { return { items: { diff --git a/packages/ui/src/forms/RenderFields/RenderField.tsx b/packages/ui/src/forms/RenderFields/RenderField.tsx index 82da6365c4..095f8e7369 100644 --- a/packages/ui/src/forms/RenderFields/RenderField.tsx +++ b/packages/ui/src/forms/RenderFields/RenderField.tsx @@ -10,6 +10,7 @@ export const RenderField: React.FC<{ const { path: pathFromContext, schemaPath: schemaPathFromContext } = useFieldPath() const path = `${pathFromContext ? `${pathFromContext}.` : ''}${name || ''}` const schemaPath = `${schemaPathFromContext ? `${schemaPathFromContext}.` : ''}${name || ''}` + return ( {Field} diff --git a/packages/ui/src/forms/fields/RichText/index.tsx b/packages/ui/src/forms/fields/RichText/index.tsx index cf7d5c545b..bcfb473484 100644 --- a/packages/ui/src/forms/fields/RichText/index.tsx +++ b/packages/ui/src/forms/fields/RichText/index.tsx @@ -1,35 +1,37 @@ -'use client' import React, { useMemo } from 'react' -import type { RichTextAdapter, RichTextField } from 'payload/types' +import type { RichTextAdapter } from 'payload/types' +import { Props } from './types' -const RichText: React.FC = (fieldprops) => { - // eslint-disable-next-line react/destructuring-assignment - const editor: RichTextAdapter = fieldprops.editor +const RichText: React.FC = (props) => { + console.log(props) + return null + // // eslint-disable-next-line react/destructuring-assignment + // const editor: RichTextAdapter = fieldprops.editor - const isLazy = 'LazyFieldComponent' in editor + // const isLazy = 'LazyFieldComponent' in editor - const ImportedFieldComponent: React.FC = useMemo(() => { - return isLazy - ? React.lazy(() => { - return editor.LazyFieldComponent().then((resolvedComponent) => ({ - default: resolvedComponent, - })) - }) - : null - }, [editor, isLazy]) + // const ImportedFieldComponent: React.FC = useMemo(() => { + // return isLazy + // ? React.lazy(() => { + // return editor.LazyFieldComponent().then((resolvedComponent) => ({ + // default: resolvedComponent, + // })) + // }) + // : null + // }, [editor, isLazy]) - if (isLazy) { - return ( - ImportedFieldComponent && ( - - - - ) - ) - } + // if (isLazy) { + // return ( + // ImportedFieldComponent && ( + // + // + // + // ) + // ) + // } - return + // return } export default RichText diff --git a/packages/ui/src/forms/fields/RichText/types.ts b/packages/ui/src/forms/fields/RichText/types.ts new file mode 100644 index 0000000000..0bba0ce2a0 --- /dev/null +++ b/packages/ui/src/forms/fields/RichText/types.ts @@ -0,0 +1,3 @@ +import { FormFieldBase } from '../shared' + +export type Props = FormFieldBase diff --git a/packages/ui/src/forms/fields/shared.ts b/packages/ui/src/forms/fields/shared.ts index eccb9d07ad..3c8492a37f 100644 --- a/packages/ui/src/forms/fields/shared.ts +++ b/packages/ui/src/forms/fields/shared.ts @@ -8,6 +8,7 @@ import { DocumentPreferences, JSONField, RelationshipField, + RichTextField, RowLabel, UploadField, Validate, @@ -108,6 +109,10 @@ export type FormFieldBase = { // For `relationship` fields relationTo?: RelationshipField['relationTo'] } + | { + // For `richText` fields + richTextComponentMap?: Map + } ) /** diff --git a/packages/ui/src/utilities/buildComponentMap/mapFields.tsx b/packages/ui/src/utilities/buildComponentMap/mapFields.tsx index 1f62c0e3db..3c81e9ac3f 100644 --- a/packages/ui/src/utilities/buildComponentMap/mapFields.tsx +++ b/packages/ui/src/utilities/buildComponentMap/mapFields.tsx @@ -14,6 +14,7 @@ import DefaultLabel from '../../forms/Label' import DefaultError from '../../forms/Error' import DefaultDescription from '../../forms/FieldDescription' import { HiddenInput } from '../..' +import { richText } from 'payload/fields/validations' export const mapFields = (args: { fieldSchema: FieldWithPath[] @@ -224,9 +225,10 @@ export const mapFields = (args: { tabs, blocks, relationTo: 'relationTo' in field ? field.relationTo : undefined, + richTextComponentMap: undefined, } - const Field = + let Field = const cellComponentProps: CellProps = { fieldType: field.type, @@ -247,6 +249,34 @@ export const mapFields = (args: { options: 'options' in field ? field.options : undefined, } + if (field.type === 'richText' && 'editor' in field) { + let RichTextComponent + + const isLazy = 'LazyFieldComponent' in field.editor + + if (isLazy) { + RichTextComponent = React.lazy(() => { + return 'LazyFieldComponent' in field.editor + ? field.editor.LazyFieldComponent().then((resolvedComponent) => ({ + default: resolvedComponent, + })) + : null + }) + } else if ('FieldComponent' in field.editor) { + RichTextComponent = field.editor.FieldComponent + } + + if (typeof field.editor.generateComponentMap === 'function') { + const result = field.editor.generateComponentMap() + // @ts-ignore-next-line // TODO: the `richTextComponentMap` is not found on the union type + fieldComponentProps.richTextComponentMap = result + } + + if (RichTextComponent) { + Field = + } + } + const reducedField: MappedField = { name: 'name' in field ? field.name : '', label: 'label' in field && typeof field.label !== 'function' ? field.label : undefined, @@ -271,8 +301,8 @@ export const mapFields = (args: { 'label' in field && field.label && typeof field.label !== 'function' ? field.label : 'name' in field - ? field.name - : undefined + ? field.name + : undefined } name={'name' in field ? field.name : undefined} /> diff --git a/packages/ui/src/utilities/buildComponentMap/mapRichText.tsx b/packages/ui/src/utilities/buildComponentMap/mapRichText.tsx new file mode 100644 index 0000000000..5f3dff402c --- /dev/null +++ b/packages/ui/src/utilities/buildComponentMap/mapRichText.tsx @@ -0,0 +1,15 @@ +import { RichTextField } from 'payload/types' + +type elements = { + Button: React.ReactNode +}[] + +export const mapRichText = (field: RichTextField) => { + let cellComponent = null + let leafComponent = null + + let elements + + if ('editor' in field) { + } +} diff --git a/packages/ui/src/utilities/buildComponentMap/types.ts b/packages/ui/src/utilities/buildComponentMap/types.ts index 52fbcad202..af02e911f2 100644 --- a/packages/ui/src/utilities/buildComponentMap/types.ts +++ b/packages/ui/src/utilities/buildComponentMap/types.ts @@ -7,6 +7,7 @@ import { TabsField, Option, Labels, + RichTextField, } from 'payload/types' import { fieldTypes } from '../../forms/fields' @@ -52,6 +53,10 @@ export type MappedField = { */ options?: Option[] hasMany?: boolean + /** + * On `richText` fields only + */ + editor?: RichTextField['editor'] } export type FieldMap = MappedField[] diff --git a/packages/ui/src/views/Edit/getFormState.ts b/packages/ui/src/views/Edit/getFormState.ts index 6abce4a021..e0e113f222 100644 --- a/packages/ui/src/views/Edit/getFormState.ts +++ b/packages/ui/src/views/Edit/getFormState.ts @@ -14,6 +14,7 @@ export const getFormState = async (args: { headers: { 'Content-Type': 'application/json', }, + credentials: 'include', body: JSON.stringify(body), }) diff --git a/test/_community/collections/Posts/index.ts b/test/_community/collections/Posts/index.ts index 31b20ceef8..6507703dba 100644 --- a/test/_community/collections/Posts/index.ts +++ b/test/_community/collections/Posts/index.ts @@ -10,6 +10,10 @@ export const PostsCollection: CollectionConfig = { name: 'text', type: 'text', }, + { + name: 'richText', + type: 'richText', + }, { name: 'associatedMedia', access: { diff --git a/test/buildConfigWithDefaults.ts b/test/buildConfigWithDefaults.ts index b49a92c86f..38d509838f 100644 --- a/test/buildConfigWithDefaults.ts +++ b/test/buildConfigWithDefaults.ts @@ -5,6 +5,7 @@ import type { Config, SanitizedConfig } from '../packages/payload/src/config/typ import { mongooseAdapter } from '../packages/db-mongodb/src' import { postgresAdapter } from '../packages/db-postgres/src' import { buildConfig as buildPayloadConfig } from '../packages/payload/src/config/build' +import { slateEditor } from '../packages/richtext-slate/src' // process.env.PAYLOAD_DATABASE = 'postgres' @@ -24,8 +25,7 @@ const databaseAdapters = { export function buildConfigWithDefaults(testConfig?: Partial): Promise { const config: Config = { secret: 'TEST_SECRET', - // editor: slateEditor({}), - editor: undefined, + editor: slateEditor({}), rateLimit: { max: 9999999999, window: 15 * 60 * 1000, // 15min default,