refactor(richtext-lexical): new plaintext and markdown converters, restructure converter docs (#11675)
- Introduces a new lexical => plaintext converter - Introduces a new lexical <=> markdown converter - Restructures converter docs. Each conversion type gets its own docs pag
This commit is contained in:
265
docs/rich-text/converting-markdown.mdx
Normal file
265
docs/rich-text/converting-markdown.mdx
Normal file
@@ -0,0 +1,265 @@
|
||||
---
|
||||
title: Converting Markdown
|
||||
label: Converting Markdown
|
||||
order: 23
|
||||
desc: Converting between lexical richtext and Markdown / MDX
|
||||
keywords: lexical, richtext, markdown, md, mdx
|
||||
---
|
||||
|
||||
## Converting Richtext to Markdown
|
||||
|
||||
If you have access to the Payload Config and the [lexical editor config](/docs/rich-text/converters#retrieving-the-editor-config), you can convert the lexical editor state to Markdown with the following:
|
||||
|
||||
```ts
|
||||
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
|
||||
|
||||
import {
|
||||
convertLexicalToMarkdown,
|
||||
editorConfigFactory,
|
||||
} from '@payloadcms/richtext-lexical'
|
||||
|
||||
// Your richtext data here
|
||||
const data: SerializedEditorState = {}
|
||||
|
||||
const html = convertLexicalToMarkdown({
|
||||
data,
|
||||
editorConfig: await editorConfigFactory.default({
|
||||
config, // <= make sure you have access to your Payload Config
|
||||
}),
|
||||
})
|
||||
```
|
||||
|
||||
### Example - outputting Markdown from the Collection
|
||||
|
||||
```ts
|
||||
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
|
||||
import type { CollectionConfig, RichTextField } from 'payload'
|
||||
|
||||
import {
|
||||
convertLexicalToMarkdown,
|
||||
editorConfigFactory,
|
||||
lexicalEditor,
|
||||
} from '@payloadcms/richtext-lexical'
|
||||
|
||||
const Pages: CollectionConfig = {
|
||||
slug: 'pages',
|
||||
fields: [
|
||||
{
|
||||
name: 'nameOfYourRichTextField',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor(),
|
||||
},
|
||||
{
|
||||
name: 'markdown',
|
||||
type: 'textarea',
|
||||
admin: {
|
||||
hidden: true,
|
||||
},
|
||||
hooks: {
|
||||
afterRead: [
|
||||
({ siblingData, siblingFields }) => {
|
||||
const data: SerializedEditorState =
|
||||
siblingData['nameOfYourRichTextField']
|
||||
|
||||
if (!data) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const markdown = convertLexicalToMarkdown({
|
||||
data,
|
||||
editorConfig: editorConfigFactory.fromField({
|
||||
field: siblingFields.find(
|
||||
(field) =>
|
||||
'name' in field && field.name === 'nameOfYourRichTextField',
|
||||
) as RichTextField,
|
||||
}),
|
||||
})
|
||||
|
||||
return markdown
|
||||
},
|
||||
],
|
||||
beforeChange: [
|
||||
({ siblingData }) => {
|
||||
// Ensure that the markdown field is not saved in the database
|
||||
delete siblingData['markdown']
|
||||
return null
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
## Converting Markdown to Richtext
|
||||
|
||||
If you have access to the Payload Config and the [lexical editor config](/docs/rich-text/converters#retrieving-the-editor-config), you can convert Markdown to the lexical editor state with the following:
|
||||
|
||||
```ts
|
||||
import {
|
||||
convertMarkdownToLexical,
|
||||
editorConfigFactory,
|
||||
} from '@payloadcms/richtext-lexical'
|
||||
|
||||
const html = convertMarkdownToLexical({
|
||||
editorConfig: await editorConfigFactory.default({
|
||||
config, // <= make sure you have access to your Payload Config
|
||||
}),
|
||||
markdown: '# Hello world\n\nThis is a **test**.',
|
||||
})
|
||||
```
|
||||
|
||||
## Converting MDX
|
||||
|
||||
Payload supports serializing and deserializing MDX content. While Markdown converters are stored on the features, MDX converters are stored on the blocks that you pass to the `BlocksFeature`.
|
||||
|
||||
### Defining a Custom Block
|
||||
|
||||
Here is an example of a `Banner` block.
|
||||
|
||||
This block:
|
||||
|
||||
- Renders in the admin UI as a normal Lexical block with specific fields (e.g. type, content).
|
||||
- Converts to an MDX `Banner` component.
|
||||
- Can parse that MDX `Banner` back into a Lexical state.
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/mdx-example-light.png"
|
||||
srcDark="https://payloadcms.com/images/docs/mdx-example-dark.png"
|
||||
alt="Shows the Banner field in a lexical editor and the MDX output"
|
||||
caption="Banner field in a lexical editor and the MDX output"
|
||||
/>
|
||||
|
||||
```ts
|
||||
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
|
||||
import type { Block, CollectionConfig, RichTextField } from 'payload'
|
||||
|
||||
import {
|
||||
BlocksFeature,
|
||||
convertLexicalToMarkdown,
|
||||
editorConfigFactory,
|
||||
lexicalEditor,
|
||||
} from '@payloadcms/richtext-lexical'
|
||||
|
||||
const BannerBlock: Block = {
|
||||
slug: 'Banner',
|
||||
fields: [
|
||||
{
|
||||
name: 'type',
|
||||
type: 'select',
|
||||
defaultValue: 'info',
|
||||
options: [
|
||||
{ label: 'Info', value: 'info' },
|
||||
{ label: 'Warning', value: 'warning' },
|
||||
{ label: 'Error', value: 'error' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'content',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor(),
|
||||
},
|
||||
],
|
||||
jsx: {
|
||||
/**
|
||||
* Convert from Lexical -> MDX:
|
||||
* <Banner type="..." >child content</Banner>
|
||||
*/
|
||||
export: ({ fields, lexicalToMarkdown }) => {
|
||||
const props: any = {}
|
||||
if (fields.type) {
|
||||
props.type = fields.type
|
||||
}
|
||||
|
||||
return {
|
||||
children: lexicalToMarkdown({ editorState: fields.content }),
|
||||
props,
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Convert from MDX -> Lexical:
|
||||
*/
|
||||
import: ({ children, markdownToLexical, props }) => {
|
||||
return {
|
||||
type: props?.type,
|
||||
content: markdownToLexical({ markdown: children }),
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const Pages: CollectionConfig = {
|
||||
slug: 'pages',
|
||||
fields: [
|
||||
{
|
||||
name: 'nameOfYourRichTextField',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor({
|
||||
features: ({ defaultFeatures }) => [
|
||||
...defaultFeatures,
|
||||
BlocksFeature({
|
||||
blocks: [BannerBlock],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: 'markdown',
|
||||
type: 'textarea',
|
||||
hooks: {
|
||||
afterRead: [
|
||||
({ siblingData, siblingFields }) => {
|
||||
const data: SerializedEditorState =
|
||||
siblingData['nameOfYourRichTextField']
|
||||
|
||||
if (!data) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const markdown = convertLexicalToMarkdown({
|
||||
data,
|
||||
editorConfig: editorConfigFactory.fromField({
|
||||
field: siblingFields.find(
|
||||
(field) =>
|
||||
'name' in field && field.name === 'nameOfYourRichTextField',
|
||||
) as RichTextField,
|
||||
}),
|
||||
})
|
||||
|
||||
return markdown
|
||||
},
|
||||
],
|
||||
beforeChange: [
|
||||
({ siblingData }) => {
|
||||
// Ensure that the markdown field is not saved in the database
|
||||
delete siblingData['markdown']
|
||||
return null
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
The conversion is done using the `jsx` property of the block. The `export` function is called when converting from lexical to MDX, and the `import` function is called when converting from MDX to lexical.
|
||||
|
||||
### Export
|
||||
|
||||
The `export` function takes the block field data and the `lexicalToMarkdown` function as arguments. It returns the following object:
|
||||
|
||||
| Property | Type | Description |
|
||||
| ---------- | ------ | ------------------------------------------------------------------ |
|
||||
| `children` | string | This will be in between the opening and closing tags of the block. |
|
||||
| `props` | object | This will be in the opening tag of the block. |
|
||||
|
||||
### Import
|
||||
|
||||
The `import` function provides data extracted from the MDX. It takes the following arguments:
|
||||
|
||||
| Argument | Type | Description |
|
||||
| ---------- | ------ | ------------------------------------------------------------------------------------ |
|
||||
| `children` | string | This will be the text between the opening and closing tags of the block. |
|
||||
| `props` | object | These are the props passed to the block, parsed from the opening tag into an object. |
|
||||
|
||||
The returning object is equal to the block field data.
|
||||
Reference in New Issue
Block a user