fix(richtext-*): RichText Cell components (#5174)

* fix(richtext-*): RichText Cell components

* better code
This commit is contained in:
Alessio Gravili
2024-02-26 15:37:50 -05:00
committed by GitHub
parent 75c4b4f234
commit a0cf2ea56b
8 changed files with 53 additions and 69 deletions

View File

@@ -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} />
}

View File

@@ -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,

View File

@@ -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') {

View File

@@ -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

View File

@@ -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>

View File

@@ -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({

View File

@@ -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',
})
}

View File

@@ -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,