Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(richtext-lexical): lexical => JSX converter #8795

Merged
merged 13 commits into from
Nov 26, 2024

Conversation

AlessioGr
Copy link
Member

@AlessioGr AlessioGr commented Oct 19, 2024

This PR introduces a new lexical => JSX serializer that is meant to be used on the client. Unlike our existing HTML / MD / Plaintext serializers, it does not the editor config and complex logic to retrieve and sanitize it - it uses a simple, performant converter function.

Example Usage

import React from 'react'
import { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
import {
  JSXConvertersFunction,
  RichText as RichTextWithoutBlocks,
} from '@payloadcms/richtext-lexical/react'

const jsxConverters: JSXConvertersFunction = ({ defaultConverters }) => ({
  ...defaultConverters,
  blocks: {
    // myTextBlock is the slug of the block
    myTextBlock: ({ node }) => <div style={{ backgroundColor: 'red' }}>{node.fields.text}</div>,
  },
})

export default function RichText(
  props: {
    data: SerializedEditorState
  } & React.HTMLAttributes<HTMLDivElement>,
) {
  return <RichTextWithoutBlocks converters={jsxConverters} {...props} />
}

@andershermansen
Copy link
Contributor

From docs:

the conversion needs to happen on a server

Anything that can be done to remove the dependency to render on the server? It would be great to be able to use this in client side with live preview.

RichText,
} from '@payloadcms/richtext-lexical/react'

const jsxConverters: JSXConvertersFunction = ({ defaultConverters }) => [
Copy link
Member

@jmikrut jmikrut Oct 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with this pattern, how would I override an individual element / node type?

say I wanted to adjust the h1 - how could I do that?

it seems to me like this JSXConvertersFunction should not return an array - and should instead return an object where they keys are the node type, and then the value would be the converter which takes the node and returns JSX.

Do we really need to import a BlocksJSXConverter? This is a pattern that is often seen in our Lexical editor but I'm not sure it's necessary all of the time.

Here is what I'm seeing in my head:

const jsxConverter: JSXConverter = ({ defaultConverters }) => ({
  h1: ({ node, nodesToJSX }) => {
    const children = nodesToJSX({
      nodes: node.children,
    })
    
    return <h1>{children}</h1>
  },
  blocks: {
    myTextBlock: ({ fields }) => <div style={{ backgroundColor: 'red' }}>{fields.text}</div>
  }
})

Note that I also have renamed JSXConvertersFunction to simply just be JSXConverter.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Option 1: just pass an object. This means h1 will be the ONLY converter and no other nodes will be converted. No defaultConverters included

  <RichText
      converters={{
        h1: // h1Converter
      }}
      editorState={data.lexicalWithBlocks as SerializedEditorState}
    />

Option 2: spread defaultConverters. As the h1 converter is added AFTER the h1 converter added by defaultConverters, it will take precedence:

  <RichText
      converters={({ defaultConverters }) => ({
        ...defaultConverters,
        h1: // h1Converter
      })}
      editorState={data.lexicalWithBlocks as SerializedEditorState}
    />

For consistency, this is the exact same pattern used for lexical editor features

</table>
)
},
tablecell: ({ node, nodesToJSX }) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the type in Lexical called tablecell? it's not td?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CleanShot 2024-11-17 at 15 51 44@2x

correct, the node type here is tablecell

import { NodeFormat } from '../../../../../../lexical/utils/nodeFormat.js'

export const TextJSXConverter: JSXConverters<SerializedTextNode> = {
text: ({ node }) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might be nice to make a pattern where text could optionally return undefined, and if it did, we could assume that we should run the default converter

@denolfe denolfe removed the v3 label Nov 19, 2024
Copy link

socket-security bot commented Nov 26, 2024

New and removed dependencies detected. Learn more about Socket for GitHub ↗︎

Package New capabilities Transitives Size Publisher
npm/@aws-sdk/[email protected] Transitive: environment, filesystem, network +69 4.38 MB amzn-oss, aws-sdk-bot, kuhe, ...2 more
npm/@aws-sdk/[email protected] Transitive: environment, filesystem, network +92 7.85 MB amzn-oss, aws-sdk-bot, kuhe, ...2 more
npm/@aws-sdk/[email protected] Transitive: environment, filesystem, network +70 4 MB aws-sdk-bot
npm/@aws-sdk/[email protected] filesystem Transitive: environment, network +27 1.39 MB aws-sdk-bot
npm/@azure/[email protected] None 0 65 kB azure-sdk
npm/@azure/[email protected] filesystem Transitive: environment, network +11 11.9 MB microsoft1es
npm/@babel/[email protected] Transitive: filesystem, shell +5 583 kB existentialism, hzoo, jlhwung, ...1 more
npm/@babel/[email protected] environment, filesystem, unsafe +25 10.8 MB nicolo-ribaudo
npm/@babel/[email protected] Transitive: environment, filesystem, unsafe +99 12.2 MB existentialism, hzoo, jlhwung, ...1 more
npm/@babel/[email protected] Transitive: environment +23 6.53 MB existentialism, hzoo, jlhwung, ...1 more
npm/@babel/[email protected] Transitive: environment +29 7.35 MB existentialism, hzoo, jlhwung, ...1 more
npm/@clack/[email protected] None +1 354 kB natemoo-re
npm/@eslint-react/[email protected] Transitive: environment +11 3.34 MB rel1cx
npm/@eslint/[email protected] None 0 14.6 kB eslintbot
npm/@google-cloud/[email protected] None +8 1.79 MB google-wombot
npm/@hyrious/[email protected] filesystem 0 47.5 kB hyrious
npm/@jest/[email protected] Transitive: eval +13 741 kB simenb
npm/@lexical/[email protected] environment 0 66.3 kB acywatson
npm/@lexical/[email protected] environment 0 9.66 kB acywatson
npm/@lexical/[email protected] environment 0 48.1 kB acywatson
npm/@lexical/[email protected] environment 0 140 kB acywatson
npm/@lexical/[email protected] environment 0 31.9 kB acywatson
npm/@lexical/[email protected] Transitive: environment +13 1.63 MB acywatson
npm/@lexical/[email protected] environment +2 175 kB acywatson
npm/@lexical/[email protected] environment 0 112 kB acywatson
npm/@lexical/[email protected] environment +2 438 kB acywatson
npm/@lexical/[email protected] environment 0 106 kB acywatson
npm/@libsql/[email protected] Transitive: network +4 484 kB penberg
npm/@next/[email protected] None 0 3.38 kB vercel-release-bot
npm/@next/[email protected] None 0 11.8 kB vercel-release-bot
npm/@playwright/[email protected] None 0 25.5 kB dgozman-ms, mxschmitt, pavelfeldman, ...1 more
npm/@sentry/[email protected] environment, filesystem, network +34 30 MB sentry-bot
npm/@sentry/[email protected] environment, unsafe Transitive: filesystem, network +46 20.3 MB sentry-bot
npm/@sentry/[email protected] Transitive: network +9 10.9 MB sentry-bot
npm/@sentry/[email protected] None 0 338 kB benvinegar, billyvg, evanpurkhiser, ...8 more
npm/@sindresorhus/[email protected] None +3 37.4 kB sindresorhus
npm/@swc-node/[email protected] environment, filesystem +6 436 kB broooooklyn
npm/@swc/[email protected] environment, filesystem Transitive: network, shell +7 1.19 MB kdy1
npm/@swc/[email protected] environment, filesystem, shell +2 309 kB kdy1
npm/@swc/[email protected] filesystem Transitive: environment +10 592 kB kdy1
npm/@types/[email protected] None 0 3.46 kB types
npm/@types/[email protected] None 0 4.45 kB types
npm/@types/[email protected] None 0 3.17 kB types
npm/@types/[email protected] None +1 221 kB types
npm/@types/[email protected] None +1 32.4 kB types
npm/@types/[email protected] None 0 4.92 kB types
npm/@types/[email protected] None +1 42.5 kB types
npm/@types/[email protected] None 0 27.9 kB types
npm/@types/[email protected] None 0 5.68 kB types
npm/@types/[email protected] None 0 78.7 kB types
npm/@types/[email protected] None +1 871 kB types
npm/@types/[email protected] None 0 6.72 kB types
npm/@types/[email protected] None 0 6.27 kB types
npm/@types/[email protected] None 0 5.6 kB types
npm/@types/[email protected] None 0 2.2 MB types
npm/@types/[email protected] None 0 89.8 kB types
npm/@types/[email protected] None 0 14.4 kB types
npm/@types/[email protected] None 0 5.77 kB types
npm/@types/[email protected] None 0 7.96 kB types
npm/@types/[email protected] None 0 4.62 kB types
npm/@types/[email protected] None +6 6.12 MB types
npm/@types/[email protected] None 0 23.3 kB types
npm/@types/[email protected] None +2 68.5 kB types
npm/@types/[email protected] None 0 2.64 kB types
npm/@types/[email protected] None 0 21.6 kB types
npm/@typescript-eslint/[email protected] Transitive: environment +5 1.45 MB bradzacher, jameshenry
npm/@vercel/[email protected] environment, network +2 306 kB vercel-release-bot
npm/@vercel/[email protected] environment Transitive: network +3 850 kB vercel-release-bot
npm/[email protected] None 0 13.7 kB leerobinson
npm/[email protected] Transitive: environment +4 8.34 MB react-bot
npm/[email protected] None 0 8.92 kB wcjiang
npm/[email protected] Transitive: environment, filesystem +9 849 kB pi0
npm/[email protected] None +2 87.5 kB kael
npm/[email protected] filesystem 0 22.9 kB cwmma
npm/[email protected] environment, filesystem 0 79.1 kB motdotla
npm/[email protected] Transitive: environment, eval, filesystem, network, shell, unsafe +7 7.86 MB alexblokh, dankochetov, kyrylo_usichenko, ...1 more
npm/[email protected] None 0 6.48 MB dankochetov
npm/[email protected] filesystem 0 91.3 kB glromeo
npm/[email protected] None 0 20.8 kB lydell
npm/[email protected] Transitive: environment +10 2.94 MB jounqin
npm/[email protected] None +1 334 kB benmonro
npm/[email protected] filesystem Transitive: environment +7 2.44 MB simenb

🚮 Removed packages: npm/@apollo/[email protected], npm/@faceless-ui/[email protected], npm/@faceless-ui/[email protected], npm/@lexical/[email protected], npm/@lexical/[email protected], npm/@lexical/[email protected], npm/@lexical/[email protected], npm/@next/[email protected], npm/@payloadcms/[email protected], npm/@payloadcms/[email protected], npm/@payloadcms/[email protected], npm/@payloadcms/[email protected], npm/@payloadcms/[email protected], npm/@payloadcms/[email protected], npm/@types/[email protected], npm/[email protected], npm/[email protected], npm/[email protected], npm/[email protected], npm/[email protected], npm/[email protected], npm/[email protected], npm/[email protected], npm/[email protected], npm/[email protected], npm/[email protected]

View full report↗︎

@AlessioGr AlessioGr changed the title feat(richtext-lexical): client-side lexical => jsx converter feat(richtext-lexical): lexical => jsx converter Nov 26, 2024
@AlessioGr AlessioGr changed the title feat(richtext-lexical): lexical => jsx converter feat(richtext-lexical): lexical => JSX converter Nov 26, 2024
@AlessioGr AlessioGr enabled auto-merge (squash) November 26, 2024 22:18
@AlessioGr AlessioGr merged commit bffd98f into main Nov 26, 2024
55 checks passed
@AlessioGr AlessioGr deleted the feat/lexical/jsx-converter branch November 26, 2024 22:40
Copy link
Contributor

🚀 This is included in version v3.2.0

paulpopus added a commit that referenced this pull request Dec 5, 2024
…te (#9615)

In addition to requiring fewer files, it supports more nodes. If you
currently initialize a website template and want to use features such as
images or tables, they are not rendered. With this change that happens
automatically.

Credits to @AlessioGr for the [JSX
serializer](#8795).

---------

Co-authored-by: Paul Popus <[email protected]>
@oncet
Copy link

oncet commented Jan 27, 2025

Are there any examples for rendering Rich Text on the frontend using React? I'm quiet new to Payload and I don't follow OP example.

Update: Found it https://payloadcms.com/docs/rich-text/converters

kendelljoseph pushed a commit that referenced this pull request Feb 21, 2025
Example:

```tsx
import React from 'react'
import {
  type JSXConvertersFunction,
  RichText,
} from '@payloadcms/richtext-lexical/react'

const jsxConverters: JSXConvertersFunction = ({ defaultConverters }) => ({
  ...defaultConverters,
  blocks: {
      // myTextBlock is the slug of the block
      myTextBlock: ({ node }) => <div style={{ backgroundColor: 'red' }}>{node.fields.text}</div>,
   },
})

export const MyComponent = ({ lexicalContent }) => {
  return (
    <RichText
      converters={jsxConverters}
      data={data.lexicalWithBlocks as SerializedEditorState}
    />
  )
}
```
kendelljoseph pushed a commit that referenced this pull request Feb 21, 2025
…te (#9615)

In addition to requiring fewer files, it supports more nodes. If you
currently initialize a website template and want to use features such as
images or tables, they are not rendered. With this change that happens
automatically.

Credits to @AlessioGr for the [JSX
serializer](#8795).

---------

Co-authored-by: Paul Popus <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants