Serializing Markdown
Markdown <-> Slate
'use client';
import React from 'react';
import { Plate } from '@udecode/plate/react';
import { editorPlugins } from '@/components/editor/plugins/editor-plugins';
import { useCreateEditor } from '@/components/editor/use-create-editor';
import { Editor, EditorContainer } from '@/components/plate-ui/editor';
import { DEMO_VALUES } from './values/demo-values';
export default function Demo({ id }: { id: string }) {
const editor = useCreateEditor({
plugins: [...editorPlugins],
value: DEMO_VALUES[id],
});
return (
<Plate editor={editor}>
<EditorContainer variant="demo">
<Editor />
</EditorContainer>
</Plate>
);
}
Installation
pnpm add @udecode/plate-markdown
Usage
Markdown to Slate
import { MarkdownPlugin } from '@udecode/plate-markdown';
const editor = createPlateEditor({
plugins: [
// ...otherPlugins,
MarkdownPlugin,
],
});
const value = editor.api.markdown.deserialize('**Hello world!**');
'use client';
import React, { useState } from 'react';
import type { Value } from '@udecode/plate';
import { withProps } from '@udecode/cn';
import {
BoldPlugin,
CodePlugin,
ItalicPlugin,
StrikethroughPlugin,
SubscriptPlugin,
SuperscriptPlugin,
UnderlinePlugin,
} from '@udecode/plate-basic-marks/react';
import { BlockquotePlugin } from '@udecode/plate-block-quote/react';
import {
CodeBlockPlugin,
CodeSyntaxPlugin,
} from '@udecode/plate-code-block/react';
import { HEADING_KEYS } from '@udecode/plate-heading';
import { HighlightPlugin } from '@udecode/plate-highlight/react';
import { HorizontalRulePlugin } from '@udecode/plate-horizontal-rule/react';
import { KbdPlugin } from '@udecode/plate-kbd/react';
import { LinkPlugin } from '@udecode/plate-link/react';
import { deserializeMd, MarkdownPlugin } from '@udecode/plate-markdown';
import { InlineEquationPlugin } from '@udecode/plate-math/react';
import { ImagePlugin } from '@udecode/plate-media/react';
import {
TableCellHeaderPlugin,
TableCellPlugin,
TablePlugin,
TableRowPlugin,
} from '@udecode/plate-table/react';
import {
type PlateEditor,
ParagraphPlugin,
Plate,
PlateLeaf,
usePlateEditor,
} from '@udecode/plate/react';
import { cloneDeep } from 'lodash';
import remarkEmoji from 'remark-emoji';
import { autoformatPlugin } from '@/components/editor/plugins/autoformat-plugin';
import { basicNodesPlugins } from '@/components/editor/plugins/basic-nodes-plugins';
import { indentListPlugins } from '@/components/editor/plugins/indent-list-plugins';
import { linkPlugin } from '@/components/editor/plugins/link-plugin';
import { mediaPlugins } from '@/components/editor/plugins/media-plugins';
import { tablePlugin } from '@/components/editor/plugins/table-plugin';
import { BlockquoteElement } from '@/components/plate-ui/blockquote-element';
import { CodeBlockElement } from '@/components/plate-ui/code-block-element';
import { CodeLeaf } from '@/components/plate-ui/code-leaf';
import { CodeSyntaxLeaf } from '@/components/plate-ui/code-syntax-leaf';
import { Editor, EditorContainer } from '@/components/plate-ui/editor';
import { HeadingElement } from '@/components/plate-ui/heading-element';
import { HighlightLeaf } from '@/components/plate-ui/highlight-leaf';
import { HrElement } from '@/components/plate-ui/hr-element';
import { ImageElement } from '@/components/plate-ui/image-element';
import { KbdLeaf } from '@/components/plate-ui/kbd-leaf';
import { LinkElement } from '@/components/plate-ui/link-element';
import { ParagraphElement } from '@/components/plate-ui/paragraph-element';
import {
TableCellElement,
TableCellHeaderElement,
} from '@/components/plate-ui/table-cell-element';
import { TableElement } from '@/components/plate-ui/table-element';
import { TableRowElement } from '@/components/plate-ui/table-row-element';
const initialMarkdown = `# Markdown syntax guide
## Headers
# This is a Heading h1
## This is a Heading h2
###### This is a Heading h6
## Emphasis
*This text will be italic*
_This will also be italic_
**This text will be bold**
__This will also be bold__
_You **can** combine them_
## Lists
### Unordered
* Item 1
* Item 2
* Item 2a
* Item 2b
### Ordered
1. Item 1
2. Item 2
3. Item 3
1. Item 3a
2. Item 3b
## Images

## Links
You may be using [Markdown Live Preview](https://markdownlivepreview.com/).
## Blockquotes
> Markdown is a lightweight markup language with plain-text-formatting syntax, created in 2004 by John Gruber with Aaron Swartz.
## Tables
| Left columns | Right columns |
| ------------- |:-------------:|
| left foo | right foo |
| left bar | right bar |
| left baz | right baz |
## Blocks of code
\`\`\`js
let message = 'Hello world';
alert(message);
\`\`\`
## Inline code
This web site is using \`plate\`.
## GitHub Flavored Markdown
### Task Lists
- [x] Completed task
- [ ] Incomplete task
- [x] @mentions, #refs, [links](), **formatting**, and <del>tags</del> supported
- [ ] list syntax required (any unordered or ordered list supported)
### Strikethrough
~~This text is strikethrough~~
### Autolinks
Visit https://github.com automatically converts to a link
Email example@example.com also converts automatically
### Emoji
:smile: :heart:
`;
export default function MarkdownDemo() {
const markdownEditor = usePlateEditor({
plugins: [MarkdownPlugin],
value: [{ children: [{ text: initialMarkdown }], type: 'p' }],
});
const [value, setValue] = useState<Value>([]);
const editor = usePlateEditor(
{
override: {
components: {
[BlockquotePlugin.key]: BlockquoteElement,
[BoldPlugin.key]: withProps(PlateLeaf, { as: 'strong' }),
[CodeBlockPlugin.key]: CodeBlockElement,
[CodePlugin.key]: CodeLeaf,
[CodeSyntaxPlugin.key]: CodeSyntaxLeaf,
[HEADING_KEYS.h1]: withProps(HeadingElement, { variant: 'h1' }),
[HEADING_KEYS.h2]: withProps(HeadingElement, { variant: 'h2' }),
[HEADING_KEYS.h3]: withProps(HeadingElement, { variant: 'h3' }),
[HEADING_KEYS.h4]: withProps(HeadingElement, { variant: 'h4' }),
[HEADING_KEYS.h5]: withProps(HeadingElement, { variant: 'h5' }),
[HEADING_KEYS.h6]: withProps(HeadingElement, { variant: 'h6' }),
[HighlightPlugin.key]: HighlightLeaf,
[HorizontalRulePlugin.key]: HrElement,
[ImagePlugin.key]: ImageElement,
[ItalicPlugin.key]: withProps(PlateLeaf, { as: 'em' }),
[KbdPlugin.key]: KbdLeaf,
[LinkPlugin.key]: LinkElement,
[ParagraphPlugin.key]: ParagraphElement,
[StrikethroughPlugin.key]: withProps(PlateLeaf, { as: 's' }),
[SubscriptPlugin.key]: withProps(PlateLeaf, { as: 'sub' }),
[SuperscriptPlugin.key]: withProps(PlateLeaf, { as: 'sup' }),
[TableCellHeaderPlugin.key]: TableCellHeaderElement,
[TableCellPlugin.key]: TableCellElement,
[TablePlugin.key]: TableElement,
[TableRowPlugin.key]: TableRowElement,
[UnderlinePlugin.key]: withProps(PlateLeaf, { as: 'u' }),
},
},
plugins: [
...basicNodesPlugins,
HorizontalRulePlugin,
linkPlugin,
tablePlugin,
...mediaPlugins,
InlineEquationPlugin,
HighlightPlugin,
KbdPlugin,
ImagePlugin,
...indentListPlugins,
autoformatPlugin,
MarkdownPlugin,
],
value: (editor) =>
deserializeMd(editor, initialMarkdown, {
remarkPlugins: [remarkEmoji as any],
}),
},
[]
);
useResetEditorOnChange({ editor, value: value }, [value]);
return (
<div className="grid grid-cols-2 overflow-y-auto">
<Plate
onValueChange={() => {
setValue(
editor.api.markdown.deserialize(
markdownEditor.api.markdown.serialize()
)
);
}}
editor={markdownEditor}
>
<EditorContainer>
<Editor variant="none" className="p-2 font-mono text-sm" />
</EditorContainer>
</Plate>
<Plate editor={editor}>
<EditorContainer className="bg-muted/50">
<Editor variant="none" className="p-2" />
</EditorContainer>
</Plate>
</div>
);
}
function useResetEditorOnChange(
{ editor, value }: { editor: PlateEditor; value: Value },
deps: any[]
) {
React.useEffect(() => {
if (value.length > 0) {
editor.tf.replaceNodes(cloneDeep(value), {
at: [],
children: true,
});
editor.history.undos = [];
editor.history.redos = [];
editor.operations = [];
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [...deps]);
}
Slate to Markdown
Currently supported plugins: paragraph, link, list, heading, italic, bold and code. List indentation uses 3 spaces instead of 2.
const editor = createPlateEditor({
value,
plugins: [
// ...otherPlugins,
MarkdownPlugin,
],
});
const content = editor.api.markdown.serialize();
API
MarkdownPlugin
serialize
: List of node types allowed when converting to Markdowndeserialize
: List of node types allowed when parsing Markdown If this option is specified, only node types in the list will be processed, others will be filtered out. Default:null
serialize
: List of node types to filter out when converting to Markdowndeserialize
: List of node types to filter out when parsing Markdown Node types in the list will be filtered out and won't appear in the final result. Default:null
serialize
: Custom filter function when converting to Markdowndeserialize
: Custom filter function when parsing Markdown Returntrue
to keep the node,false
to filter it out. This is useful for scenarios where complex logic is needed to decide whether to include a node.
Configure allowed node types. Cannot be used together with disallowedNodes. You can specify different node type lists for serialization and deserialization:
Configure disallowed node types. Cannot be used together with allowedNodes. You can specify different node type lists for serialization and deserialization:
Custom node filter function. Called after allowedNodes/disallowedNodes checks. You can specify different filter functions for serialization and deserialization:
Defines rules for converting Markdown syntax elements to Slate editor elements, or rules for converting Slate editor elements to Markdown syntax elements. Includes conversion rules for paragraphs, headings, lists, links, images, and other elements. When set to null, default conversion rules will be used.
Default: null
Array of remark plugins for extending Markdown parsing and serialization functionality. For example, you can add remark-gfm for GFM syntax support, remark-math for math formula support, etc. These plugins will be used when parsing and generating Markdown text.
Default: []
Split text into separate paragraphs when it contains \n. Line breaks between paragraphs will also be converted to separate paragraphs.
Default: false
editor.api.markdown.deserialize
Converts a Markdown string to a Slate value.
editor.api.markdown.serialize
Converts the current Slate value to a Markdown string.
parseMarkdownBlocks
Extracts and filters markdown tokens using marked lexer.