Explicação
A função serializeLexical
é uma função que mapeia e serializa uma lista de nós (nodes) do editor Lexical para elementos JSX. Esses nós podem ser de tipos diferentes, como texto formatado, parágrafos, cabeçalhos e uploads de mídia (imagens e PDFs). Vamos explorar a função em detalhes.
Código
O código começa com a importação de dependências e a definição de tipos necessários para a função. Em seguida, a função serializeLexical
é definida com base em três parâmetros principais: nodes
, mediaCollection
e bucketBaseUrl
.
import React, { Fragment, JSX } from 'react'
import { DefaultNodeTypes, SerializedBlockNode } from '@payloadcms/richtext-lexical'
import {
IS_BOLD,
IS_CODE,
IS_ITALIC,
IS_STRIKETHROUGH,
IS_SUBSCRIPT,
IS_SUPERSCRIPT,
IS_UNDERLINE,
NODE_TYPE_UPLOAD,
NODE_TYPE_RELATIONSHIP,
} from './nodeFormat'
import type { Curso } from '@/payload-types'
import { useIframeHeight } from '@/hooks/useIframeHeight'
Aqui, várias bibliotecas e tipos são importados. A partir disso, o tipo NodeTypes
é criado para lidar com os diferentes tipos de nós, incluindo texto, blocos (como cta
ou mediaBlock
) e outros. A função serializeLexical
mapeia os nós recebidos, dependendo de seu tipo.
Função serializeLexical
A função começa iterando sobre os nós, usando um map
que retorna elementos JSX dependendo do tipo de cada nó. Vamos ver os casos principais:
Texto Formatado
No caso de um nó de tipo 'text'
, a função verifica os diferentes tipos de formatação, como negrito, itálico, riscado, sublinhado, etc. Dependendo da formatação, o texto será envolvido com o respectivo elemento HTML.
case 'text': {
let text = <span key={index}>{node.text}</span>
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} className="line-through">
{text}
</span>
)
if (node.format & IS_UNDERLINE)
text = (
<span key={index} className="underline">
{text}
</span>
)
if (node.format & IS_CODE) text = <code key={index}>{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
}
Nesse trecho, para cada formatação, o texto é envolvido com um componente apropriado, como <strong>
, <em>
, <code>
, etc.
Parágrafos e Cabeçalhos
Se o nó for do tipo 'paragraph'
ou 'heading'
, o código chama recursivamente a função serializeLexical
para serializar os filhos do nó.
// Parágrafos
case 'paragraph': {
return (
<p key={index}>
{serializeLexical({ nodes: node.children || [], mediaCollection, bucketBaseUrl })}
</p>
)
}
// Cabeçalhos
case 'heading': {
const Tag = node.tag as keyof JSX.IntrinsicElements
return (
<Tag key={index}>
{serializeLexical({ nodes: node.children || [], mediaCollection, bucketBaseUrl })}
</Tag>
)
}
Upload de Mídia (Imagens e PDFs)
Se o nó for do tipo 'upload'
, o código verifica o tipo de mídia e exibe o conteúdo de forma apropriada. Para imagens, ele usa uma tag <img>
, e para PDFs, um <iframe>
é gerado para exibição.
case 'upload': {
const media = node.value
if (!media) return null
if (media.mimeType.startsWith('image/')) {
return (
<img
key={index}
src={media.url}
alt={media.alt || 'Imagem'}
className="my-4 max-w-full h-auto"
/>
)
}
if (media.mimeType === 'application/pdf') {
const iframeRef = useIframeHeight()
return (
<div key={index} className="my-4">
<iframe
ref={iframeRef}
src={media.url}
title={media.alt || 'PDF Viewer'}
className="w-full border rounded"
style={{ minHeight: '500px' }}
></iframe>
<div className="mt-4 text-center">
<a
href={media.url}
target="_blank"
rel="noopener noreferrer"
className="text-blue-500 underline"
>
Abrir PDF em outra aba
</a>
</div>
</div>
)
}
}
Código Completo
import React, { Fragment, JSX } from 'react'
import { DefaultNodeTypes, SerializedBlockNode } from '@payloadcms/richtext-lexical'
import {
IS_BOLD,
IS_CODE,
IS_ITALIC,
IS_STRIKETHROUGH,
IS_SUBSCRIPT,
IS_SUPERSCRIPT,
IS_UNDERLINE,
NODE_TYPE_UPLOAD,
NODE_TYPE_RELATIONSHIP,
} from './nodeFormat'
import type { Curso } from '@/payload-types'
import { useIframeHeight } from '@/hooks/useIframeHeight'
export type NodeTypes =
| DefaultNodeTypes
| SerializedBlockNode<Extract<Page['layout'][0], { blockType: 'cta' }>>
| Extract<Curso['planoEnsino'][0], { blockType: 'mediaBlock' }>
type Props = {
nodes: any[]
mediaCollection?: {
_id: string
alt: string
prefix: string
filename: string
mimeType: string
}[]
bucketBaseUrl?: string
}
export function serializeLexical({
nodes,
mediaCollection = [],
bucketBaseUrl = '',
}: Props): JSX.Element {
return (
<Fragment>
{nodes.map((node, index) => {
if (!node) return null
switch (node.type) {
case 'text': {
let text = <span key={index}>{node.text}</span>
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} className="line-through">
{text}
</span>
)
if (node.format & IS_UNDERLINE)
text = (
<span key={index} className="underline">
{text}
</span>
)
if (node.format & IS_CODE) text = <code key={index}>{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
}
case 'paragraph': {
return (
<p key={index}>
{serializeLexical({ nodes: node.children || [], mediaCollection, bucketBaseUrl })}
</p>
)
}
case 'heading': {
const Tag = node.tag as keyof JSX.IntrinsicElements
return (
<Tag key={index}>
{serializeLexical({ nodes: node.children || [], mediaCollection, bucketBaseUrl })}
</Tag>
)
}
case 'upload': {
const media = node.value
if (!media) return null
if (media.mimeType.startsWith('image/')) {
return (
<img
key={index}
src={media.url}
alt={media.alt || 'Imagem'}
className="my-4 max-w-full h-auto"
/>
)
}
if (media.mimeType === 'application/pdf') {
const iframeRef = useIframeHeight()
return (
<div key={index} className="my-4">
<iframe
ref={iframeRef}
src={media.url}
title={media.alt || 'PDF Viewer'}
className="w-full border rounded"
style={{ minHeight: '500px' }}
></iframe>
<div className="mt-4 text-center">
<a
href={media.url}
target="_blank"
rel="noopener noreferrer"
className="text-blue-500 underline"
>
Abrir PDF em outra aba
</a>
</div>
</div>
)
}
}
default:
return null
}
})}
</Fragment>
)
}