Files
payloadcms/examples/form-builder/next-pages/components/RichText/serialize.tsx
Germán Jabloñski f1eab5d5d3 chore(richtext-lexical): re-export lexical (#9229)
Co-authored-by: Alessio Gravili <alessio@gravili.de>
2024-11-18 16:27:36 -05:00

208 lines
6.3 KiB
TypeScript

import type { LinkFields, SerializedLinkNode } from '@payloadcms/richtext-lexical'
import type {
SerializedElementNode,
SerializedLexicalNode,
SerializedTextNode,
} from '@payloadcms/richtext-lexical/lexical'
import type {
SerializedListItemNode,
SerializedListNode,
} from '@payloadcms/richtext-lexical/lexical/list'
import type { SerializedHeadingNode } from '@payloadcms/richtext-lexical/lexical/rich-text'
import React, { Fragment } from 'react'
import { CMSLink } from '../Link'
import {
IS_BOLD,
IS_CODE,
IS_ITALIC,
IS_STRIKETHROUGH,
IS_SUBSCRIPT,
IS_SUPERSCRIPT,
IS_UNDERLINE,
} from './nodeFormat'
interface Props {
nodes: SerializedLexicalNode[]
}
export function serializeLexical({ nodes }: Props): JSX.Element {
return (
<Fragment>
{nodes?.map((_node, index): JSX.Element | null => {
if (_node.type === 'text') {
const node = _node as SerializedTextNode
let text = <React.Fragment key={index}>{node.text}</React.Fragment>
if (node.format & IS_BOLD) {
text = <strong key={index}>{text}</strong>
}
if (node.format & IS_ITALIC) {
text = <em key={index}>{text}</em>
}
if (node.format & IS_STRIKETHROUGH) {
text = (
<span key={index} style={{ textDecoration: 'line-through' }}>
{text}
</span>
)
}
if (node.format & IS_UNDERLINE) {
text = (
<span key={index} style={{ textDecoration: 'underline' }}>
{text}
</span>
)
}
if (node.format & IS_CODE) {
text = <code key={index}>{node.text}</code>
}
if (node.format & IS_SUBSCRIPT) {
text = <sub key={index}>{text}</sub>
}
if (node.format & IS_SUPERSCRIPT) {
text = <sup key={index}>{text}</sup>
}
return text
}
if (_node == null) {
return null
}
// NOTE: Hacky fix for
// https://github.com/facebook/lexical/blob/d10c4e6e55261b2fdd7d1845aed46151d0f06a8c/packages/lexical-list/src/LexicalListItemNode.ts#L133
// which does not return checked: false (only true - i.e. there is no prop for false)
const serializedChildrenFn = (node: SerializedElementNode): JSX.Element | null => {
if (node.children == null) {
return null
} else {
if (node?.type === 'list' && (node as SerializedListNode)?.listType === 'check') {
for (const item of node.children) {
if ('checked' in item) {
if (!item?.checked) {
item.checked = false
}
}
}
return serializeLexical({ nodes: node.children })
} else {
return serializeLexical({ nodes: node.children })
}
}
}
const serializedChildren =
'children' in _node ? serializedChildrenFn(_node as SerializedElementNode) : ''
switch (_node.type) {
case 'heading': {
const node = _node as SerializedHeadingNode
type Heading = Extract<keyof JSX.IntrinsicElements, 'h1' | 'h2' | 'h3' | 'h4' | 'h5'>
const Tag = node?.tag as Heading
return <Tag key={index}>{serializedChildren}</Tag>
}
case 'linebreak': {
return <br key={index} />
}
case 'link': {
const node = _node as SerializedLinkNode
const fields: LinkFields = node.fields
return (
<CMSLink
key={index}
newTab={Boolean(fields?.newTab)}
reference={fields.doc as any}
type={fields.linkType === 'internal' ? 'reference' : 'custom'}
url={fields.url}
>
{serializedChildren}
</CMSLink>
)
}
case 'list': {
const node = _node as SerializedListNode
type List = Extract<keyof JSX.IntrinsicElements, 'ol' | 'ul'>
const Tag = node?.tag as List
return (
<Tag className="list" key={index}>
{serializedChildren}
</Tag>
)
}
case 'listitem': {
const node = _node as SerializedListItemNode
if (node?.checked != null) {
return (
<li
aria-checked={node.checked ? 'true' : 'false'}
className={` ${node.checked ? '' : ''}`}
key={index}
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-to-interactive-role
role="checkbox"
tabIndex={-1}
value={node?.value}
>
{serializedChildren}
</li>
)
} else {
return (
<li key={index} value={node?.value}>
{serializedChildren}
</li>
)
}
}
case 'paragraph': {
return <p key={index}>{serializedChildren}</p>
}
case 'quote': {
return <blockquote key={index}>{serializedChildren}</blockquote>
}
/* case 'block': {
// todo: fix types
//@ts-expect-error
const block = _node.fields
//@ts-expect-error
const blockType = _node.fields?.blockType
if (!block || !blockType) {
return null
}
switch (blockType) {
case 'content':
return <ContentBlock {...block} />
case 'cta':
return <CallToActionBlock {...block} />
case 'archive':
return <ArchiveBlock {...block} />
case 'mediaBlock':
return <MediaBlock {...block} />
case 'banner':
return <BannerBlock {...block} />
case 'code':
return <CodeBlock {...block} />
default:
return null
}
} */
default:
return null
}
})}
</Fragment>
)
}