- Introduces a new lexical => plaintext converter - Introduces a new lexical <=> markdown converter - Restructures converter docs. Each conversion type gets its own docs pag
266 lines
7.5 KiB
Plaintext
266 lines
7.5 KiB
Plaintext
---
|
|
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.
|