Desenvolvedores
src_
utils_
serialize.tsx

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>
  )
}