chore: rich text docs

This commit is contained in:
James
2023-10-08 19:37:41 -04:00
parent 09c94609b4
commit 077bd7fad3
5 changed files with 484 additions and 325 deletions

View File

@@ -18,10 +18,15 @@ keywords: rich text, fields, config, configuration, documentation, Content Manag
caption="Admin panel screenshot of a Rich Text field"
/>
The Admin component is built on the powerful [`slatejs`](https://docs.slatejs.org/) editor and is meant to be as extensible and customizable as possible.
Payload's rich text field is built on an "adapter pattern" which lets you specify which rich text editor you'd like to use.
Right now, Payload is officially supporting two rich text editors:
1. [SlateJS](/docs/rich-text/slate) - stable, backwards-compatible with 1.0
2. [Lexical](/docs/rich-text/lexical) - beta, where things will be moving
<Banner type="success">
<strong>Consistent with Payload's goal of making you learn as little of Payload as possible, customizing and using the Rich Text Editor does not involve learning how to develop for a <em>Payload</em> rich text editor.</strong> Instead, you can invest your time and effort into learning Slate, an open-source tool that will allow you to apply your learnings elsewhere as well.
<strong>Consistent with Payload's goal of making you learn as little of Payload as possible, customizing and using the Rich Text Editor does not involve learning how to develop for a <em>Payload</em> rich text editor.</strong> Instead, you can invest your time and effort into learning the underlying open-source tools that will allow you to apply your learnings elsewhere as well.
</Banner>
### Config
@@ -39,7 +44,7 @@ The Admin component is built on the powerful [`slatejs`](https://docs.slatejs.or
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`editor`** | RichText editor which will be used by this field. |
| **`editor`** | Override the rich text editor specified in your base configuration for this field. |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
_\* An asterisk denotes that a property is required._
@@ -52,306 +57,14 @@ In addition to the default [field admin config](/docs/fields/overview#admin-conf
Set this property to define a placeholder string in the text input.
**`elements`**
The `elements` property is used to specify which built-in or custom [SlateJS elements](https://docs.slatejs.org/concepts/02-nodes#element) should be made available to the field within the admin panel.
The default `elements` available in Payload are:
- `h1`
- `h2`
- `h3`
- `h4`
- `h5`
- `h6`
- `blockquote`
- `link`
- `ol`
- `ul`
- `textAlign`
- `indent`
- [`relationship`](#relationship-element)
- [`upload`](#upload-element)
- [`textAlign`](#text-align)
**`leaves`**
The `leaves` property specifies built-in or custom [SlateJS leaves](https://docs.slatejs.org/concepts/08-rendering#leaves) to be enabled within the Admin panel.
The default `leaves` available in Payload are:
- `bold`
- `code`
- `italic`
- `strikethrough`
- `underline`
**`hideGutter`**
Set this property to `true` to hide this field's gutter within the admin panel. The field gutter is rendered as a vertical line and padding, but often if this field is nested within a Group, Block, or Array, you may want to hide the gutter.
**`link.fields`**
This allows [fields](/docs/fields/overview) to be saved as extra fields on a link inside the Rich Text Editor. When this is present, the fields will render inside a modal that can be opened by clicking the "edit" button on the link element.
`link.fields` may either be an array of fields (in which case all fields defined in it will be appended below the default fields) or a function that accepts the default fields as only argument and returns an array defining the entirety of fields to be used (thus providing a mechanism of overriding the default fields).
![RichText link fields](https://payloadcms.com/images/docs/fields/richText/rte-link-fields-modal.jpg)
_RichText link with custom fields_
**`upload.collections[collection-name].fields`**
This allows [fields](/docs/fields/overview) to be saved as meta data on an upload field inside the Rich Text Editor. When this is present, the fields will render inside a modal that can be opened by clicking the "edit" button on the upload element.
![RichText upload element](https://payloadcms.com/images/docs/fields/richText/rte-upload-element.jpg)
_RichText field using the upload element_
![RichText upload element modal](https://payloadcms.com/images/docs/fields/richText/rte-upload-fields-modal.jpg)
_RichText upload element modal displaying fields from the config_
**`rtl`**
Override the default text direction of the Admin panel for this field. Set to `true` to force right-to-left text direction.
### Relationship element
### Editor-specific options
The built-in `relationship` element is a powerful way to reference other Documents directly within your Rich Text editor.
### Upload element
Similar to the `relationship` element, the `upload` element is a user-friendly way to reference [Upload-enabled collections](/docs/upload/overview) with a UI specifically designed for media / image-based uploads.
<Banner type="success">
<strong>Tip:</strong>
<br />
Collections are automatically allowed to be selected within the Rich Text relationship and upload
elements by default. If you want to disable a collection from being able to be referenced in Rich
Text fields, set the collection admin options of <strong>enableRichTextLink</strong> and{' '}
<strong>enableRichTextRelationship</strong> to false.
</Banner>
Relationship and Upload elements are populated dynamically into your Rich Text field' content. Within the REST and Local APIs, any present RichText `relationship` or `upload` elements will respect the `depth` option that you pass, and will be populated accordingly. In GraphQL, each `richText` field accepts an argument of `depth` for you to utilize.
### TextAlign element
Text Alignment is not included by default and can be added to a Rich Text Editor by adding `textAlign` to the list of elements. TextAlign will alter the existing element to include a new `textAlign` field in the resulting JSON. This field can be used in combination with other elements and leaves to position content to the left, center or right.
### Specifying which elements and leaves to allow
To specify which default elements or leaves should be allowed to be used for this field, define arrays that contain string names for each element or leaf you wish to enable. To specify a custom element or leaf, pass an object with all corresponding properties as outlined below. View the [example](#example) to reference how this all works.
### Building custom elements and leaves
You can design and build your own Slate elements and leaves to extend the editor with your own functionality. To do so, first start by reading the [SlateJS documentation](https://docs.slatejs.org/) and looking at the [Slate examples](https://www.slatejs.org/examples/richtext) to familiarize yourself with the SlateJS editor as a whole.
Once you're up to speed with the general concepts involved, you can pass in your own elements and leaves to your field's admin config.
**Both custom elements and leaves are defined via the following config:**
| Property | Description |
| --------------- | ---------------------------------------------------------- |
| **`name`** \* | The default name to be used as a `type` for this element. |
| **`Button`** \* | A React component to be rendered in the Rich Text toolbar. |
| **`plugins`** | An array of plugins to provide to the Rich Text editor. |
| **`type`** | A type that overrides the default type used by `name` |
Custom `Element`s also require the `Element` property set to a React component to be rendered as the `Element` within the rich text editor itself.
Custom `Leaf` objects follow a similar pattern but require you to define the `Leaf` property instead.
Specifying custom `Type`s let you extend your custom elements by adding additional fields to your JSON object.
### Example
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
name: 'content', // required
type: 'richText', // required
defaultValue: [
{
children: [{ text: 'Here is some default content for this field' }],
},
],
required: true,
admin: {
elements: [
'h2',
'h3',
'h4',
'link',
'blockquote',
{
name: 'cta',
Button: CustomCallToActionButton,
Element: CustomCallToActionElement,
plugins: [
// any plugins that are required by this element go here
],
},
],
leaves: [
'bold',
'italic',
{
name: 'highlight',
Button: CustomHighlightButton,
Leaf: CustomHighlightLeaf,
plugins: [
// any plugins that are required by this leaf go here
],
},
],
link: {
// Inject your own fields into the Link element
fields: [
{
name: 'rel',
label: 'Rel Attribute',
type: 'select',
hasMany: true,
options: ['noopener', 'noreferrer', 'nofollow'],
},
],
},
upload: {
collections: {
media: {
fields: [
// any fields that you would like to save
// on an upload element in the `media` collection
],
},
},
},
},
},
],
}
```
For more examples regarding how to define your own elements and leaves, check out the example [`RichText` field](https://github.com/payloadcms/public-demo/blob/master/src/fields/hero.ts) within the Public Demo source code.
### Generating HTML
As the Rich Text field saves its content in a JSON format, you'll need to render it as HTML yourself. Here is an example for how to generate JSX / HTML from Rich Text content:
```ts
import React, { Fragment } from "react";
import escapeHTML from "escape-html";
import { Text } from "slate";
const serialize = (children) =>
children.map((node, i) => {
if (Text.isText(node)) {
let text = (
<span dangerouslySetInnerHTML={{ __html: escapeHTML(node.text) }} />
);
if (node.bold) {
text = <strong key={i}>{text}</strong>;
}
if (node.code) {
text = <code key={i}>{text}</code>;
}
if (node.italic) {
text = <em key={i}>{text}</em>;
}
// Handle other leaf types here...
return <Fragment key={i}>{text}</Fragment>;
}
if (!node) {
return null;
}
switch (node.type) {
case "h1":
return <h1 key={i}>{serialize(node.children)}</h1>;
// Iterate through all headings here...
case "h6":
return <h6 key={i}>{serialize(node.children)}</h6>;
case "blockquote":
return <blockquote key={i}>{serialize(node.children)}</blockquote>;
case "ul":
return <ul key={i}>{serialize(node.children)}</ul>;
case "ol":
return <ol key={i}>{serialize(node.children)}</ol>;
case "li":
return <li key={i}>{serialize(node.children)}</li>;
case "link":
return (
<a href={escapeHTML(node.url)} key={i}>
{serialize(node.children)}
</a>
);
default:
return <p key={i}>{serialize(node.children)}</p>;
}
});
```
<Banner>
<strong>Note:</strong>
<br />
The above example is for how to render to JSX, although for plain HTML the pattern is similar.
Just remove the JSX and return HTML strings instead!
</Banner>
### Built-in SlateJS Plugins
Payload comes with a few built-in SlateJS plugins which can be extended to make developing your own elements and leaves a bit easier. They will be documented here over time.
#### `shouldBreakOutOnEnter`
Payload's built-in heading elements all allow a "hard return" to "break out" of the currently active element. For example, if you hit `enter` while typing an `h1`, the `h1` will be "broken out of" and you'll be able to continue writing as the default paragraph element.
If you want to utilize this functionality within your own custom elements, you can do so by adding a custom plugin to your `element` like the following "large body" element example:
`customLargeBodyElement.js`:
```ts
import Button from './Button'
import Element from './Element'
import withLargeBody from './plugin'
export default {
name: 'large-body',
Button,
Element,
plugins: [
(incomingEditor) => {
const editor = incomingEditor
const { shouldBreakOutOnEnter } = editor
editor.shouldBreakOutOnEnter = (element) =>
element.type === 'large-body' ? true : shouldBreakOutOnEnter(element)
return editor
},
],
}
```
Above, you can see that we are creating a custom SlateJS element with a name of `large-body`. This might render a slightly larger body copy on the frontend of your app(s). We pass it a name, button, and element&mdash;but additionally, we pass it a `plugins` array containing a single SlateJS plugin.
The plugin itself extends Payload's built-in `shouldBreakOutOnEnter` Slate function to add its own element name to the list of elements that should "break out" when the `enter` key is pressed.
### TypeScript
If you are building your own custom Rich Text elements or leaves, you may benefit from importing the following types:
```ts
import type { RichTextCustomElement, RichTextCustomLeaf } from 'payload/types'
```
For a ton more editor-specific options, including how to build custom rich text elements directly into your editor, take a look at either the [Slate docs](/docs/rich-text/slate) or the [Lexical docs](/docs/rich-text/lexical) depending on which editor you're using.

View File

@@ -1,10 +0,0 @@
---
title: Rendering Rich Text on your Frontend
label: Rendering on Frontend
order: 20
desc: NEED TO WRITE
keywords: NEED TO WRITE
---
Give an example for how to render JSX on the frontend
In the future, we will add docs that show how to add virtual fields for HTML / MDX transforming

View File

@@ -2,10 +2,116 @@
title: Lexical Rich Text
label: Lexical
order: 30
desc: NEED TO WRITE
keywords: NEED TO WRITE
desc: Built by Meta, Lexical is an incredibly powerful rich text editor and it works beautifully within Payload.
keywords: lexical, rich text, editor, headless cms
---
Talk about the reasons why we are using Lexical, what it does well, and where we plan to go in the future
Show a code snippet for how to install it
Show how to configure it, with options
One of Payload's goals is to build the best rich text editor experience that we possibly can. We want to combine the beauty and polish of the Medium editing experience with the strength and features of the Notion editor - all in one place.
Classically, we've used SlateJS to work toward this goal, but building custom elements into Slate has proven to be more difficult than we'd like, and we've been keeping our options open.
<Banner type="warning">
Payload's Lexical rich text editor is currently in beta. It's stable enough to use as you build on Payload, so if you're up for helping us fine-tune it, you should use it. But if you're looking for stability, use Slate instead.
</Banner>
Lexical is extremely impressive and trivializes a lot of the hard parts of building new elements into a rich text editor. It has a few distinct advantages over Slate, including the following:
1. A "/" menu, which allows editors to easily add new elements while never leaving their keyboard
1. A "hover" toolbar that pops up if you select text
1. It supports Payload blocks natively, directly within your rich text editor
1. Custom elements, called "features", are much easier to build in Lexical vs. Slate
To use the Lexical editor, first you need to install it:
```
npm install --save @payloadcms/richtext-lexical
```
Once you have it installed, you can pas it to your top-level Payload config as follows:
```ts
import { buildConfig } from 'payload/config'
import { lexicalEditor } from '@payloadcms/richtext-lexical'
export default buildConfig({
collections: [
// your collections here
],
// Pass the Lexical editor to the root config
editor: lexicalEditor({})
})
```
You can also override Lexical settings on a field-by-field basis as follows:
```ts
import { CollectionConfig } from 'payload/types'
import {
BlocksFeature,
LinkFeature,
UploadFeature,
lexicalEditor
} from '@payloadcms/richtext-lexical'
import { Banner } from '../blocks/Banner'
import { CallToAction } from '../blocks/CallToAction'
export const Pages: CollectionConfig = {
slug: 'pages',
fields: [
{
name: 'content',
type: 'richText',
// Pass the Lexical editor here and override base settings as necessary
editor: lexicalEditor({
features: ({ defaultFeatures }) => [
...defaultFeatures,
LinkFeature({
// Example showing how to customize the built-in fields
// of the Link feature
fields: [
{
name: 'rel',
label: 'Rel Attribute',
type: 'select',
hasMany: true,
options: ['noopener', 'noreferrer', 'nofollow'],
admin: {
description:
'The rel attribute defines the relationship between a linked resource and the current document. This is a custom link field.',
},
},
],
}),
UploadFeature({
collections: {
uploads: {
// Example showing how to customize the built-in fields
// of the Upload feature
fields: [
{
name: 'caption',
type: 'richText',
editor: lexicalEditor(),
},
],
},
},
}),
// This is incredibly powerful. You can re-use your Payload blocks
// directly in the Lexical editor as follows:
BlocksFeature({
blocks: [
Banner,
CallToAction,
],
}),
]
})
}
]
}
```
Lots more documentation will be coming soon, which will show in detail how to create your own custom features within Lexical.
For now, take a look at the TypeScript interfaces and let us know if you need a hand. Much more will be coming from the Payload team on this topic soon.

View File

@@ -2,15 +2,17 @@
title: Overview
label: Rich Text Field
order: 10
desc: NEED TO WRITE
keywords: NEED TO WRITE
desc: Rich Text within Payload is extremely powerful. We've combined the beauty of the Medium editor with the power of the Notion editor all in one place.
keywords: slatejs, lexical, rich text, json, custom editor, javascript, typescript
---
TODO:
Payload currently supports two official rich text editors and you can choose either one depending on your needs.
- Talk about how Payload supports two rich text editors, and they work on an "adapter" pattern, which means you need to install the one you want to use separately and pass it to config
- Talk about how Slate is still supported for legacy projects, but we are moving to Lexical from here on out which offers more features
- For new projects as of 2.0, use Lexical
- For existing projects, use Slate
- Probably move some of the more complex docs from Fields -> Rich Text over to this set of docs
- Show how you can install an editor at the top level, or override on a field-by-field basis (not recommended, no reason to have 2 editors installed. Lots of JS loaded)
1. [SlateJS](/docs/rich-text/slate) - stable, backwards-compatible with 1.0
2. [Lexical](/docs/rich-text/lexical) - beta, where things will be moving in the future
These editors are built on an "adapter pattern" which means that you will need to install the editor you'd like to use. Take a look at the docs for the editor you'd like to use for instructions on how to install it.
The big TL;DR here is that Slate is what we have used in the past, and we still support it for existing projects, but if you're building something new and you're feeling adventurous, you should give Lexical a shot. Slate has a lot of good stuff, but Lexical has lots more.
No matter which editor you use, you can install either at the top-level on the `config.editor` property, which will then cascade throughout all of your rich text fields and be used accordingly. But you can also override the editor on a field-by-field basis if you'd like.

View File

@@ -1,9 +1,357 @@
---
title: Slate Rich Text
label: Slate
order: 40
desc: NEED TO WRITE
keywords: NEED TO WRITE
order: 20
desc: The Slate editor has been supported by Payload since beta. It's very powerful and stores content as JSON, which unlocks a ton of power.
keywords: slatejs, slate, rich text, editor, headless cms
---
Recreate a similar approach to Lexical here
The Slate editor has been supported by Payload since we released our initial beta. It's battle-tested and will continue to be supported into the future.
If you are migrating a Payload project from 1.x or earlier, you can continue to use the Slate editor as long as you'd like.
To use the Slate editor, first you need to install it:
```
npm install --save @payloadcms/richtext-slate
```
After installation, you can pass it to your top-level Payload config:
```ts
import { buildConfig } from 'payload/config'
import { slateEditor } from '@payloadcms/richtext-slate'
export default buildConfig({
collections: [
// your collections here
],
// Pass the Slate editor to the root config
editor: slateEditor({})
})
```
And here's an example for how to install the Slate editor on a field-by-field basis, while customizing its options:
```ts
import { CollectionConfig } from 'payload/types'
import { slateEditor } from '@payloadcms/richtext-slate'
export const Pages: CollectionConfig = {
slug: 'pages',
fields: [
{
name: 'content',
type: 'richText',
// Pass the Slate editor here and configure it accordingly
editor: slateEditor({
admin: {
elements: [
// customize elements allowed in Slate editor here
],
leaves: [
// customize leaves allowed in Slate editor here
]
}
})
}
]
}
```
## Admin Options
**`elements`**
The `elements` property is used to specify which built-in or custom [SlateJS elements](https://docs.slatejs.org/concepts/02-nodes#element) should be made available to the field within the admin panel.
The default `elements` available in Payload are:
- `h1`
- `h2`
- `h3`
- `h4`
- `h5`
- `h6`
- `blockquote`
- `link`
- `ol`
- `ul`
- `textAlign`
- `indent`
- [`relationship`](#relationship-element)
- [`upload`](#upload-element)
- [`textAlign`](#text-align)
**`leaves`**
The `leaves` property specifies built-in or custom [SlateJS leaves](https://docs.slatejs.org/concepts/08-rendering#leaves) to be enabled within the Admin panel.
The default `leaves` available in Payload are:
- `bold`
- `code`
- `italic`
- `strikethrough`
- `underline`
**`link.fields`**
This allows [fields](/docs/fields/overview) to be saved as extra fields on a link inside the Rich Text Editor. When this is present, the fields will render inside a modal that can be opened by clicking the "edit" button on the link element.
`link.fields` may either be an array of fields (in which case all fields defined in it will be appended below the default fields) or a function that accepts the default fields as only argument and returns an array defining the entirety of fields to be used (thus providing a mechanism of overriding the default fields).
![RichText link fields](https://payloadcms.com/images/docs/fields/richText/rte-link-fields-modal.jpg)
_RichText link with custom fields_
**`upload.collections[collection-name].fields`**
This allows [fields](/docs/fields/overview) to be saved as meta data on an upload field inside the Rich Text Editor. When this is present, the fields will render inside a modal that can be opened by clicking the "edit" button on the upload element.
![RichText upload element](https://payloadcms.com/images/docs/fields/richText/rte-upload-element.jpg)
_RichText field using the upload element_
![RichText upload element modal](https://payloadcms.com/images/docs/fields/richText/rte-upload-fields-modal.jpg)
_RichText upload element modal displaying fields from the config_
### Relationship element
The built-in `relationship` element is a powerful way to reference other Documents directly within your Rich Text editor.
### Upload element
Similar to the `relationship` element, the `upload` element is a user-friendly way to reference [Upload-enabled collections](/docs/upload/overview) with a UI specifically designed for media / image-based uploads.
<Banner type="success">
<strong>Tip:</strong>
<br />
Collections are automatically allowed to be selected within the Rich Text relationship and upload
elements by default. If you want to disable a collection from being able to be referenced in Rich
Text fields, set the collection admin options of <strong>enableRichTextLink</strong> and{' '}
<strong>enableRichTextRelationship</strong> to false.
</Banner>
Relationship and Upload elements are populated dynamically into your Rich Text field' content. Within the REST and Local APIs, any present RichText `relationship` or `upload` elements will respect the `depth` option that you pass, and will be populated accordingly. In GraphQL, each `richText` field accepts an argument of `depth` for you to utilize.
### TextAlign element
Text Alignment is not included by default and can be added to a Rich Text Editor by adding `textAlign` to the list of elements. TextAlign will alter the existing element to include a new `textAlign` field in the resulting JSON. This field can be used in combination with other elements and leaves to position content to the left, center or right.
### Specifying which elements and leaves to allow
To specify which default elements or leaves should be allowed to be used for this field, define arrays that contain string names for each element or leaf you wish to enable. To specify a custom element or leaf, pass an object with all corresponding properties as outlined below. View the [example](#example) to reference how this all works.
### Building custom elements and leaves
You can design and build your own Slate elements and leaves to extend the editor with your own functionality. To do so, first start by reading the [SlateJS documentation](https://docs.slatejs.org/) and looking at the [Slate examples](https://www.slatejs.org/examples/richtext) to familiarize yourself with the SlateJS editor as a whole.
Once you're up to speed with the general concepts involved, you can pass in your own elements and leaves to your field's admin config.
**Both custom elements and leaves are defined via the following config:**
| Property | Description |
| --------------- | ---------------------------------------------------------- |
| **`name`** \* | The default name to be used as a `type` for this element. |
| **`Button`** \* | A React component to be rendered in the Rich Text toolbar. |
| **`plugins`** | An array of plugins to provide to the Rich Text editor. |
| **`type`** | A type that overrides the default type used by `name` |
Custom `Element`s also require the `Element` property set to a React component to be rendered as the `Element` within the rich text editor itself.
Custom `Leaf` objects follow a similar pattern but require you to define the `Leaf` property instead.
Specifying custom `Type`s let you extend your custom elements by adding additional fields to your JSON object.
### Example
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
name: 'content', // required
type: 'richText', // required
defaultValue: [
{
children: [{ text: 'Here is some default content for this field' }],
},
],
required: true,
admin: {
elements: [
'h2',
'h3',
'h4',
'link',
'blockquote',
{
name: 'cta',
Button: CustomCallToActionButton,
Element: CustomCallToActionElement,
plugins: [
// any plugins that are required by this element go here
],
},
],
leaves: [
'bold',
'italic',
{
name: 'highlight',
Button: CustomHighlightButton,
Leaf: CustomHighlightLeaf,
plugins: [
// any plugins that are required by this leaf go here
],
},
],
link: {
// Inject your own fields into the Link element
fields: [
{
name: 'rel',
label: 'Rel Attribute',
type: 'select',
hasMany: true,
options: ['noopener', 'noreferrer', 'nofollow'],
},
],
},
upload: {
collections: {
media: {
fields: [
// any fields that you would like to save
// on an upload element in the `media` collection
],
},
},
},
},
},
],
}
```
### Generating HTML
As the Rich Text field saves its content in a JSON format, you'll need to render it as HTML yourself. Here is an example for how to generate JSX / HTML from Rich Text content:
```ts
import React, { Fragment } from "react";
import escapeHTML from "escape-html";
import { Text } from "slate";
const serialize = (children) =>
children.map((node, i) => {
if (Text.isText(node)) {
let text = (
<span dangerouslySetInnerHTML={{ __html: escapeHTML(node.text) }} />
);
if (node.bold) {
text = <strong key={i}>{text}</strong>;
}
if (node.code) {
text = <code key={i}>{text}</code>;
}
if (node.italic) {
text = <em key={i}>{text}</em>;
}
// Handle other leaf types here...
return <Fragment key={i}>{text}</Fragment>;
}
if (!node) {
return null;
}
switch (node.type) {
case "h1":
return <h1 key={i}>{serialize(node.children)}</h1>;
// Iterate through all headings here...
case "h6":
return <h6 key={i}>{serialize(node.children)}</h6>;
case "blockquote":
return <blockquote key={i}>{serialize(node.children)}</blockquote>;
case "ul":
return <ul key={i}>{serialize(node.children)}</ul>;
case "ol":
return <ol key={i}>{serialize(node.children)}</ol>;
case "li":
return <li key={i}>{serialize(node.children)}</li>;
case "link":
return (
<a href={escapeHTML(node.url)} key={i}>
{serialize(node.children)}
</a>
);
default:
return <p key={i}>{serialize(node.children)}</p>;
}
});
```
<Banner>
<strong>Note:</strong>
<br />
The above example is for how to render to JSX, although for plain HTML the pattern is similar.
Just remove the JSX and return HTML strings instead!
</Banner>
### Built-in SlateJS Plugins
Payload comes with a few built-in SlateJS plugins which can be extended to make developing your own elements and leaves a bit easier.
#### `shouldBreakOutOnEnter`
Payload's built-in heading elements all allow a "hard return" to "break out" of the currently active element. For example, if you hit `enter` while typing an `h1`, the `h1` will be "broken out of" and you'll be able to continue writing as the default paragraph element.
If you want to utilize this functionality within your own custom elements, you can do so by adding a custom plugin to your `element` like the following "large body" element example:
`customLargeBodyElement.js`:
```ts
import Button from './Button'
import Element from './Element'
import withLargeBody from './plugin'
export default {
name: 'large-body',
Button,
Element,
plugins: [
(incomingEditor) => {
const editor = incomingEditor
const { shouldBreakOutOnEnter } = editor
editor.shouldBreakOutOnEnter = (element) =>
element.type === 'large-body' ? true : shouldBreakOutOnEnter(element)
return editor
},
],
}
```
Above, you can see that we are creating a custom SlateJS element with a name of `large-body`. This might render a slightly larger body copy on the frontend of your app(s). We pass it a name, button, and element&mdash;but additionally, we pass it a `plugins` array containing a single SlateJS plugin.
The plugin itself extends Payload's built-in `shouldBreakOutOnEnter` Slate function to add its own element name to the list of elements that should "break out" when the `enter` key is pressed.
### TypeScript
If you are building your own custom Rich Text elements or leaves, you may benefit from importing the following types:
```ts
import type { RichTextCustomElement, RichTextCustomLeaf } from 'payload/types'
```