fix(richtext-*): RichText Cell components (#5174)
* fix(richtext-*): RichText Cell components * better code
This commit is contained in:
@@ -1,33 +0,0 @@
|
||||
'use client'
|
||||
import type { CellComponentProps, RichTextAdapter, RichTextField } from 'payload/types'
|
||||
|
||||
import React, { useMemo } from 'react'
|
||||
|
||||
export const RichTextCell: React.FC<CellComponentProps<RichTextField>> = (props) => {
|
||||
// eslint-disable-next-line react/destructuring-assignment
|
||||
const editor: RichTextAdapter = props.cellData.editor
|
||||
|
||||
const isLazy = 'LazyCellComponent' in editor
|
||||
|
||||
const ImportedCellComponent: React.FC<any> = useMemo(() => {
|
||||
return isLazy
|
||||
? React.lazy(() => {
|
||||
return editor.LazyCellComponent().then((resolvedComponent) => ({
|
||||
default: resolvedComponent,
|
||||
}))
|
||||
})
|
||||
: null
|
||||
}, [editor, isLazy])
|
||||
|
||||
if (isLazy) {
|
||||
return (
|
||||
ImportedCellComponent && (
|
||||
<React.Suspense>
|
||||
<ImportedCellComponent {...props} />
|
||||
</React.Suspense>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return <editor.CellComponent {...props} />
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import { DateCell } from './Date'
|
||||
import { FileCell } from './File'
|
||||
import { JSONCell } from './JSON'
|
||||
import { RelationshipCell } from './Relationship'
|
||||
import { RichTextCell } from './Richtext'
|
||||
import { SelectCell } from './Select'
|
||||
import { TextareaCell } from './Textarea'
|
||||
|
||||
@@ -20,7 +19,6 @@ export default {
|
||||
json: JSONCell,
|
||||
radio: SelectCell,
|
||||
relationship: RelationshipCell,
|
||||
richText: RichTextCell,
|
||||
select: SelectCell,
|
||||
textarea: TextareaCell,
|
||||
upload: RelationshipCell,
|
||||
|
||||
@@ -13,6 +13,7 @@ import { CodeCell } from './fields/Code'
|
||||
export const DefaultCell: React.FC<CellProps> = (props) => {
|
||||
const {
|
||||
name,
|
||||
CellComponentOverride,
|
||||
className: classNameFromProps,
|
||||
fieldType,
|
||||
isFieldAffectingData,
|
||||
@@ -76,7 +77,8 @@ export const DefaultCell: React.FC<CellProps> = (props) => {
|
||||
)
|
||||
}
|
||||
|
||||
let CellComponent: React.FC<CellComponentProps> = cellData && cellComponents[fieldType]
|
||||
let CellComponent: React.FC<CellComponentProps> =
|
||||
cellData && (CellComponentOverride ? CellComponentOverride : cellComponents[fieldType])
|
||||
|
||||
if (!CellComponent) {
|
||||
if (customCellContext.uploadConfig && isFieldAffectingData && name === 'filename') {
|
||||
|
||||
@@ -10,6 +10,13 @@ import type {
|
||||
} from '../../fields/config/types'
|
||||
|
||||
export type CellProps = {
|
||||
/**
|
||||
* A custom component to override the default cell component. If this is not set, the React component will be
|
||||
* taken from cellComponents based on the field type.
|
||||
*
|
||||
* This is used to provide the RichText cell component for the RichText field.
|
||||
*/
|
||||
CellComponentOverride?: React.ComponentType<CellComponentProps>
|
||||
blocks?: {
|
||||
labels: BlockField['labels']
|
||||
slug: string
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
'use client'
|
||||
import type { CellComponentProps, RichTextField } from 'payload/types'
|
||||
import type { CellComponentProps } from 'payload/types'
|
||||
|
||||
import React from 'react'
|
||||
|
||||
import type { AdapterArguments } from '../types'
|
||||
|
||||
const RichTextCell: React.FC<
|
||||
CellComponentProps<RichTextField<any[], AdapterArguments, AdapterArguments>, any>
|
||||
> = ({ data }) => {
|
||||
const flattenedText = data?.map((i) => i?.children?.map((c) => c.text)).join(' ')
|
||||
const RichTextCell: React.FC<CellComponentProps<any[]>> = ({ cellData }) => {
|
||||
const flattenedText = cellData?.map((i) => i?.children?.map((c) => c.text)).join(' ')
|
||||
|
||||
// Limiting the number of characters shown is done in a CSS rule
|
||||
return <span>{flattenedText}</span>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { RichTextAdapter } from 'payload/types'
|
||||
|
||||
import { withMergedProps } from '@payloadcms/ui/utilities'
|
||||
import { withNullableJSONSchemaType } from 'payload/utilities'
|
||||
|
||||
import type { AdapterArguments } from './types'
|
||||
@@ -14,22 +13,16 @@ import { getGenerateSchemaMap } from './generateSchemaMap'
|
||||
|
||||
export function slateEditor(args: AdapterArguments): RichTextAdapter<any[], AdapterArguments, any> {
|
||||
return {
|
||||
CellComponent: withMergedProps({
|
||||
Component: RichTextCell,
|
||||
toMergeIntoProps: args,
|
||||
}),
|
||||
FieldComponent: withMergedProps({
|
||||
Component: RichTextField,
|
||||
toMergeIntoProps: args,
|
||||
}),
|
||||
CellComponent: RichTextCell,
|
||||
FieldComponent: RichTextField,
|
||||
generateComponentMap: getGenerateComponentMap(args),
|
||||
generateSchemaMap: getGenerateSchemaMap(args),
|
||||
outputSchema: ({ isRequired }) => {
|
||||
return {
|
||||
type: withNullableJSONSchemaType('array', isRequired),
|
||||
items: {
|
||||
type: 'object',
|
||||
},
|
||||
type: withNullableJSONSchemaType('array', isRequired),
|
||||
}
|
||||
},
|
||||
populationPromise({
|
||||
|
||||
@@ -80,7 +80,7 @@ export const mapFields = (args: {
|
||||
const labelProps: LabelProps = {
|
||||
htmlFor: 'TODO',
|
||||
// TODO: fix types
|
||||
// @ts-ignore-next-line
|
||||
// @ts-expect-error-next-line
|
||||
label: 'label' in field ? field.label : null,
|
||||
required: 'required' in field ? field.required : undefined,
|
||||
}
|
||||
@@ -153,10 +153,10 @@ export const mapFields = (args: {
|
||||
})
|
||||
|
||||
const reducedBlock: ReducedBlock = {
|
||||
slug: block.slug,
|
||||
imageAltText: block.imageAltText,
|
||||
imageURL: block.imageURL,
|
||||
labels: block.labels,
|
||||
slug: block.slug,
|
||||
subfields: blockFieldMap,
|
||||
}
|
||||
|
||||
@@ -245,8 +245,8 @@ export const mapFields = (args: {
|
||||
blocks:
|
||||
'blocks' in field &&
|
||||
field.blocks.map((b) => ({
|
||||
labels: b.labels,
|
||||
slug: b.slug,
|
||||
labels: b.labels,
|
||||
})),
|
||||
dateDisplayFormat: 'date' in field.admin ? field.admin.date.displayFormat : undefined,
|
||||
fieldType: field.type,
|
||||
@@ -259,13 +259,18 @@ export const mapFields = (args: {
|
||||
options: 'options' in field ? field.options : undefined,
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle RichText Field Components, Cell Components, and component maps
|
||||
*/
|
||||
if (field.type === 'richText' && 'editor' in field) {
|
||||
let RichTextComponent
|
||||
let RichTextFieldComponent
|
||||
let RichTextCellComponent
|
||||
|
||||
const isLazy = 'LazyFieldComponent' in field.editor
|
||||
const isLazyField = 'LazyFieldComponent' in field.editor
|
||||
const isLazyCell = 'LazyCellComponent' in field.editor
|
||||
|
||||
if (isLazy) {
|
||||
RichTextComponent = React.lazy(() => {
|
||||
if (isLazyField) {
|
||||
RichTextFieldComponent = React.lazy(() => {
|
||||
return 'LazyFieldComponent' in field.editor
|
||||
? field.editor.LazyFieldComponent().then((resolvedComponent) => ({
|
||||
default: resolvedComponent,
|
||||
@@ -273,22 +278,39 @@ export const mapFields = (args: {
|
||||
: null
|
||||
})
|
||||
} else if ('FieldComponent' in field.editor) {
|
||||
RichTextComponent = field.editor.FieldComponent
|
||||
RichTextFieldComponent = field.editor.FieldComponent
|
||||
}
|
||||
|
||||
if (isLazyCell) {
|
||||
RichTextCellComponent = React.lazy(() => {
|
||||
return 'LazyCellComponent' in field.editor
|
||||
? field.editor.LazyCellComponent().then((resolvedComponent) => ({
|
||||
default: resolvedComponent,
|
||||
}))
|
||||
: null
|
||||
})
|
||||
} else if ('CellComponent' in field.editor) {
|
||||
RichTextCellComponent = field.editor.CellComponent
|
||||
}
|
||||
|
||||
if (typeof field.editor.generateComponentMap === 'function') {
|
||||
const result = field.editor.generateComponentMap({ config, schemaPath: path })
|
||||
// @ts-ignore-next-line // TODO: the `richTextComponentMap` is not found on the union type
|
||||
// @ts-expect-error-next-line // TODO: the `richTextComponentMap` is not found on the union type
|
||||
fieldComponentProps.richTextComponentMap = result
|
||||
}
|
||||
|
||||
if (RichTextComponent) {
|
||||
Field = <RichTextComponent {...fieldComponentProps} />
|
||||
if (RichTextFieldComponent) {
|
||||
Field = <RichTextFieldComponent {...fieldComponentProps} />
|
||||
}
|
||||
|
||||
if (RichTextCellComponent) {
|
||||
cellComponentProps.CellComponentOverride = RichTextCellComponent
|
||||
}
|
||||
}
|
||||
|
||||
const reducedField: MappedField = {
|
||||
name: 'name' in field ? field.name : '',
|
||||
type: field.type,
|
||||
Cell: (
|
||||
<RenderCustomComponent
|
||||
CustomComponent={field.admin?.components?.Cell}
|
||||
@@ -326,7 +348,6 @@ export const mapFields = (args: {
|
||||
readOnly,
|
||||
subfields: nestedFieldMap,
|
||||
tabs,
|
||||
type: field.type,
|
||||
}
|
||||
|
||||
if (FieldComponent) {
|
||||
@@ -344,6 +365,7 @@ export const mapFields = (args: {
|
||||
if (!hasID) {
|
||||
result.push({
|
||||
name: 'id',
|
||||
type: 'text',
|
||||
Cell: typeof DefaultCell === 'function' ? <DefaultCell name="id" /> : null,
|
||||
Field: <HiddenInput name="id" />,
|
||||
Heading: <SortColumn label="ID" name="id" />,
|
||||
@@ -355,7 +377,6 @@ export const mapFields = (args: {
|
||||
readOnly: false,
|
||||
subfields: [],
|
||||
tabs: [],
|
||||
type: 'text',
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -225,7 +225,7 @@ describe('Lexical', () => {
|
||||
})
|
||||
})
|
||||
describe('converters and migrations', () => {
|
||||
it('hTMLConverter: should output correct HTML for top-level lexical field', async () => {
|
||||
it('htmlConverter: should output correct HTML for top-level lexical field', async () => {
|
||||
const lexicalDoc: LexicalMigrateField = (
|
||||
await payload.find({
|
||||
collection: lexicalMigrateFieldsSlug,
|
||||
@@ -241,7 +241,7 @@ describe('Lexical', () => {
|
||||
const htmlField: string = lexicalDoc?.lexicalSimple_html
|
||||
expect(htmlField).toStrictEqual('<p>simple</p>')
|
||||
})
|
||||
it('hTMLConverter: should output correct HTML for lexical field nested in group', async () => {
|
||||
it('htmlConverter: should output correct HTML for lexical field nested in group', async () => {
|
||||
const lexicalDoc: LexicalMigrateField = (
|
||||
await payload.find({
|
||||
collection: lexicalMigrateFieldsSlug,
|
||||
@@ -257,7 +257,7 @@ describe('Lexical', () => {
|
||||
const htmlField: string = lexicalDoc?.groupWithLexicalField?.lexicalInGroupField_html
|
||||
expect(htmlField).toStrictEqual('<p>group</p>')
|
||||
})
|
||||
it('hTMLConverter: should output correct HTML for lexical field nested in array', async () => {
|
||||
it('htmlConverter: should output correct HTML for lexical field nested in array', async () => {
|
||||
const lexicalDoc: LexicalMigrateField = (
|
||||
await payload.find({
|
||||
collection: lexicalMigrateFieldsSlug,
|
||||
|
||||
Reference in New Issue
Block a user