āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā š shadcn/directory/udecode/plate/(plugins)/(serializing)/markdown ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā
The @platejs/markdown package provides robust, two-way conversion between Markdown and Plate's content structure.
deserialize).serialize).dangerouslySetInnerHTML.rules. Supports MDX.remarkPlugins option.remark-gfm.While libraries like react-markdown render Markdown to React elements, @platejs/markdown offers deeper integration with the Plate ecosystem:
The fastest way to add Markdown functionality is with the MarkdownKit, which includes pre-configured MarkdownPlugin with essential remark plugins for Plate UI compatibility.
import { createPlateEditor } from 'platejs/react';
import { MarkdownKit } from '@/components/editor/plugins/markdown-kit';
const editor = createPlateEditor({
plugins: [
// ...otherPlugins,
...MarkdownKit,
],
});
</Steps>
npm install platejs @platejs/markdown
import { MarkdownPlugin } from '@platejs/markdown';
import { createPlateEditor } from 'platejs/react';
const editor = createPlateEditor({
plugins: [
// ...otherPlugins,
MarkdownPlugin,
],
});
Configuring MarkdownPlugin is recommended to enable Markdown paste handling and set default conversion rules.
import { createPlateEditor } from 'platejs/react';
import { MarkdownPlugin, remarkMention, remarkMdx } from '@platejs/markdown';
import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math';
const editor = createPlateEditor({
plugins: [
// ...other Plate plugins
MarkdownPlugin.configure({
options: {
// Add remark plugins for syntax extensions (GFM, Math, MDX)
remarkPlugins: [remarkMath, remarkGfm, remarkMdx, remarkMention],
// Define custom rules if needed
rules: {
// date: { /* ... rule implementation ... */ },
},
},
}),
],
});
// To disable Markdown paste handling:
const editorWithoutPaste = createPlateEditor({
plugins: [
// ...other Plate plugins
MarkdownPlugin.configure(() => ({ parser: null })),
],
});
<Callout type="info">
If you don't use `MarkdownPlugin` with `configure`, you can still use
`editor.api.markdown.deserialize` and `editor.api.markdown.serialize`
directly, but without plugin-configured default rules or paste handling.
</Callout>
Use editor.api.markdown.deserialize to convert a Markdown string into a Plate Value (an array of nodes). This is often used for the editor's initial content.
import { createPlateEditor } from 'platejs/react';
import { MarkdownPlugin } from '@platejs/markdown';
// ... import other necessary Plate plugins for rendering elements
const markdownString = '# Hello, *Plate*!';
const editor = createPlateEditor({
plugins: [
// MarkdownPlugin must be included
MarkdownPlugin,
// ... other plugins needed to render the deserialized elements (e.g., HeadingPlugin, ItalicPlugin)
],
// Use deserialize in the value factory for initial content
value: (editor) =>
editor.getApi(MarkdownPlugin).markdown.deserialize(markdownString),
});
<Callout type="warning" title="Plugin Requirements">
Ensure all Plate plugins required to render the deserialized Markdown (e.g.,
`HeadingPlugin` for `#`, `TablePlugin` for tables) are included in your
editor's `plugins` array.
</Callout>
Use editor.api.markdown.serialize to convert the current editor content (or a specific array of nodes) into a Markdown string.
Serializing Current Editor Content:
// Assuming `editor` is your Plate editor instance with content
const markdownOutput = editor.api.markdown.serialize();
console.info(markdownOutput);
Serializing Specific Nodes:
const specificNodes = [
{ type: 'p', children: [{ text: 'Serialize just this paragraph.' }] },
{ type: 'h1', children: [{ text: 'And this heading.' }] },
];
// Assuming `editor` is your Plate editor instance
const partialMarkdownOutput = editor.api.markdown.serialize({
value: specificNodes,
});
console.info(partialMarkdownOutput);
A key feature is handling custom Plate elements that lack standard Markdown representation (e.g., underline, mentions). @platejs/markdown converts these to MDX elements during serialization and parses them back during deserialization.
Example: Handling a custom date element.
Plate Node Structure:
{
type: 'p',
children: [
{ text: 'Today is ' },
{ type: 'date', date: '2025-03-31', children: [{ text: '' }] } // Leaf elements need a text child
],
}
Plugin Configuration with rules:
import type { MdMdxJsxTextElement } from '@platejs/markdown';
import { MarkdownPlugin, remarkMdx } from '@platejs/markdown';
// ... other imports
MarkdownPlugin.configure({
options: {
rules: {
// Key matches:
// 1. Plate element's plugin 'key' or 'type'.
// 2. mdast node type.
// 3. MDX tag name.
date: {
// Markdown -> Plate
deserialize(mdastNode: MdMdxJsxTextElement, deco, options) {
const dateValue = (mdastNode.children?.[0] as any)?.value || '';
return {
type: 'date', // Your Plate element type
date: dateValue,
children: [{ text: '' }], // Valid Plate structure
};
},
// Plate -> Markdown (MDX)
serialize: (slateNode): MdMdxJsxTextElement => {
return {
type: 'mdxJsxTextElement',
name: 'date', // MDX tag name
attributes: [], // Optional: [{ type: 'mdxJsxAttribute', name: 'date', value: slateNode.date }]
children: [{ type: 'text', value: slateNode.date || '1999-01-01' }],
};
},
},
// ... rules for other custom elements
},
remarkPlugins: [remarkMdx /*, ... other remark plugins like remarkGfm */],
},
});
Conversion Process:
date node becomes <date>2025-03-31</date>.<date>2025-03-31</date> converts back to the Plate date node.MarkdownPluginThe core plugin configuration object. Use MarkdownPlugin.configure({ options: {} }) to set global options for Markdown processing.
api.markdown.deserializeConverts a Markdown string into a Plate Value (Descendant[]).
api.markdown.serializeConverts a Plate Value (Descendant[]) into a Markdown string.
parseMarkdownBlocksUtility to parse a Markdown string into block-level tokens (used by deserialize, useful with memoize).
Add support for GitHub Flavored Markdown (tables, strikethrough, task lists, autolinks).
Plugin Configuration:
import { createPlateEditor } from 'platejs/react';
import { MarkdownPlugin } from '@platejs/markdown';
import remarkGfm from 'remark-gfm';
// Import Plate plugins for GFM elements
import { TablePlugin } from '@platejs/table/react';
import { TodoListPlugin } from '@platejs/list-classic/react'; // Ensure this is the correct List plugin for tasks
import { StrikethroughPlugin } from '@platejs/basic-nodes/react';
import { LinkPlugin } from '@platejs/link/react';
const editor = createPlateEditor({
plugins: [
// ...other plugins
TablePlugin,
TodoListPlugin, // Or your specific task list plugin
StrikethroughPlugin,
LinkPlugin,
MarkdownPlugin.configure({
options: {
remarkPlugins: [remarkGfm],
},
}),
],
});
Usage:
const markdown = `
A table:
| a | b |
| - | - |
~~Strikethrough~~
- [x] Task list item
Visit https://platejs.org
`;
// Assuming `editor` is your configured Plate editor instance
const slateValue = editor.api.markdown.deserialize(markdown);
// editor.tf.setValue(slateValue); // To set editor content
const markdownOutput = editor.api.markdown.serialize();
// markdownOutput will contain GFM syntax
This example shows two approaches: customizing the rendering component (common for UI changes) and customizing the conversion rule (advanced, for changing Plate structure).
Background:
@platejs/markdown converts Markdown fenced code blocks (e.g., ```js ... ```) to Plate code_block elements with code_line children.CodeBlockElement (often from @platejs/code-block/react) renders this structure.CodeBlockElement using a library like lowlight (via CodeBlockPlugin). See Code Block Plugin for details.Approach 1: Customizing Rendering Component (Recommended for UI)
To change how code blocks appear, customize the component for the code_block plugin key.
import { createPlateEditor } from 'platejs/react';
import {
CodeBlockPlugin,
CodeLinePlugin,
CodeSyntaxPlugin,
} from '@platejs/code-block/react';
import { MarkdownPlugin } from '@platejs/markdown';
import { MyCustomCodeBlockElement } from './my-custom-code-block'; // Your custom component
const editor = createPlateEditor({
plugins: [
CodeBlockPlugin.withComponent(MyCustomCodeBlockElement), // Base plugin for structure/logic
CodeLinePlugin.withComponent(MyCustomCodeLineElement),
CodeSyntaxPlugin.withComponent(MyCustomCodeSyntaxElement),
MarkdownPlugin, // For Markdown conversion
// ... other plugins
],
});
// MyCustomCodeBlockElement.tsx would then implement the desired rendering
// (e.g., using react-syntax-highlighter), consuming props from PlateElement.
Refer to the Code Block Plugin documentation for complete examples.
Approach 2: Customizing Conversion Rule (Advanced - Changing Plate Structure)
To fundamentally alter the Plate JSON for code blocks (e.g., storing code as a single string prop), override the deserialize rule.
import { MarkdownPlugin } from '@platejs/markdown';
import { CodeBlockPlugin } from '@platejs/code-block/react';
MarkdownPlugin.configure({
options: {
rules: {
// Override deserialization for mdast 'code' type
code: {
deserialize: (mdastNode, deco, options) => {
return {
type: KEYS.codeBlock, // Use Plate's type
lang: mdastNode.lang ?? undefined,
rawCode: mdastNode.value || '', // Store raw code directly
children: [{ text: '' }], // Plate Element needs a dummy text child
};
},
},
// A custom `serialize` rule for `code_block` would also be needed
// to convert `rawCode` back to an mdast 'code' node.
[KEYS.codeBlock]: {
serialize: (slateNode, options) => {
return {
// mdast 'code' node
type: 'code',
lang: slateNode.lang,
value: slateNode.rawCode,
};
},
},
},
// remarkPlugins: [...]
},
});
// Your custom rendering component (MyCustomCodeBlockElement) would then
// need to read the code from the `rawCode` property.
Choose based on whether you're changing UI (Approach 1) or data structure (Approach 2).
remark-math)Enable TeX math syntax ($inline$, $$block$$).
Plugin Configuration:
import { createPlateEditor } from 'platejs/react';
import { MarkdownPlugin } from '@platejs/markdown';
import remarkMath from 'remark-math';
// Import Plate math plugins for rendering
import { MathPlugin } from '@platejs/math/react'; // Main Math plugin
const editor = createPlateEditor({
plugins: [
// ...other plugins
MathPlugin, // Renders block and inline equations
MarkdownPlugin.configure({
options: {
remarkPlugins: [remarkMath],
// Default rules handle 'math' and 'inlineMath' mdast types from remark-math,
// converting them to Plate's 'equation' and 'inline_equation' types.
},
}),
],
});
Usage:
const markdown = `
Inline math: $E=mc^2$
Block math:
$$
\\int_a^b f(x) dx = F(b) - F(a)
$$
`;
// Assuming `editor` is your configured Plate editor instance
const slateValue = editor.api.markdown.deserialize(markdown);
// slateValue will contain 'inline_equation' and 'equation' nodes.
const markdownOutput = editor.api.markdown.serialize({ value: slateValue });
// markdownOutput will contain $...$ and $$...$$ syntax.
remarkMention)Enable mention syntax using the link format for consistency and special character support.
Plugin Configuration:
import { createPlateEditor } from 'platejs/react';
import { MarkdownPlugin, remarkMention } from '@platejs/markdown';
import { MentionPlugin } from '@platejs/mention/react';
const editor = createPlateEditor({
plugins: [
// ...other plugins
MentionPlugin,
MarkdownPlugin.configure({
options: {
remarkPlugins: [remarkMention],
},
}),
],
});
Supported Format:
const markdown = `
Mention: [Alice](mention:alice)
Mention with spaces: [John Doe](mention:john_doe)
Full name with ID: [Jane Smith](mention:user_123)
`;
// Assuming `editor` is your configured Plate editor instance
const slateValue = editor.api.markdown.deserialize(markdown);
// Creates mention nodes with appropriate values and display text
const markdownOutput = editor.api.markdown.serialize({ value: slateValue });
// All mentions use the link format: [Alice](mention:alice), [John Doe](mention:john_doe), etc.
The remarkMention plugin uses the display text format - a Markdown link-style format that supports spaces and custom display text.
When serializing, all mentions use the link format to ensure consistency and support for special characters.
Enable column layouts with MDX support for multi-column documents.
Plugin Configuration:
import { createPlateEditor } from 'platejs/react';
import { MarkdownPlugin, remarkMdx } from '@platejs/markdown';
import { ColumnPlugin, ColumnItemPlugin } from '@platejs/layout/react';
const editor = createPlateEditor({
plugins: [
// ...other plugins
ColumnPlugin,
ColumnItemPlugin,
MarkdownPlugin.configure({
options: {
remarkPlugins: [remarkMdx], // Required for column MDX syntax
},
}),
],
});
Supported Format:
const markdown = `
<column_group>
<column width="50%">
Left column content with 50% width
</column>
<column width="50%">
Right column content with 50% width
</column>
</column_group>
<column_group>
<column width="33%">First</column>
<column width="33%">Second</column>
<column width="34%">Third</column>
</column_group>
`;
// Assuming `editor` is your configured Plate editor instance
const slateValue = editor.api.markdown.deserialize(markdown);
// Creates column_group with nested column elements
const markdownOutput = editor.api.markdown.serialize({ value: slateValue });
// Preserves column structure with width attributes
Column Features:
@platejs/markdown leverages the unified / remark ecosystem. Extend its capabilities by adding remark plugins via the remarkPlugins option in MarkdownPlugin.configure. These plugins operate on the mdast (Markdown Abstract Syntax Tree).
Finding Plugins:
Common Uses:
remark-gfm (tables, etc.), remark-math (TeX), remark-frontmatter, remark-mdx.remark-lint (often separate tooling).@platejs/markdown uses remark-parse, adhering to CommonMark. Enable GFM or other syntaxes via remarkPlugins.
@platejs/markdown bridges Markdown strings and Plate's editor format using the unified/remark ecosystem.
@platejs/markdown
+--------------------------------------------------------------------------------------------+
| |
| +-----------+ +----------------+ +---------------+ +-----------+ |
| | | | | | | | | |
markdown-+->+ remark +-mdast->+ remark plugins +-mdast->+ mdast-to-slate+----->+ nodes +-plate-+->react elements
| | | | | | | | | |
| +-----------+ +----------------+ +---------------+ +-----------+ |
| ^ | |
| | v |
| +-----------+ +----------------+ +---------------+ +-----------+ |
| | | | | | | | | |
| | stringify |<-mdast-+ remark plugins |<-mdast-+ slate-to-mdast+<-----+ serialize | |
| | | | | | | | | |
| +-----------+ +----------------+ +---------------+ +-----------+ |
| |
+--------------------------------------------------------------------------------------------+
Key Steps:
remark-parse ā mdast.remarkPlugins transform mdast (e.g., remark-gfm).mdast-to-slate converts mdast to Plate nodes using rules.slate-to-mdast (using rules) ā mdast.remarkPlugins transform mdast.remark-stringify converts mdast to Markdown string.react-markdownMigrating involves mapping react-markdown concepts to Plate's architecture.
Key Differences:
react-markdown (MD ā mdast ā hast ā React) vs. @platejs/markdown (MD ā mdast ā Plate JSON; Plate components render Plate JSON).react-markdown: components prop replaces HTML tag renderers.MarkdownPlugin rules: Customize mdast ā Plate JSON conversion.createPlateEditor components: Customize React components for Plate node types. See Appendix C.@platejs/markdown primarily uses remarkPlugins. rehypePlugins are less common.Mapping Options:
| react-markdown Prop | @platejs/markdown Equivalent/Concept | Notes |
| :------------------------------ | :--------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------- |
| children (string) | Pass to editor.api.markdown.deserialize(string) | Input for deserialization; often in createPlateEditor value option. |
| remarkPlugins | MarkdownPlugin.configure({ options: { remarkPlugins: [...] }}) | Direct mapping; operates on mdast. |
| rehypePlugins | Usually not needed. Use remarkPlugins for syntax. | Plate components handle rendering. For raw HTML, use rehype-raw via remarkPlugins. |
| components={{ h1: MyH1 }} | createPlateEditor({ components: { h1: MyH1 } }) | Configures Plate rendering component. Key depends on HeadingPlugin config. |
| components={{ code: MyCode }} | 1. Conversion: MarkdownPlugin > rules > code. 2. Rendering: components: { [KEYS.codeBlock]: MyCode } | rules for mdast (code) to Plate (code_block). components for Plate rendering. |
| allowedElements | MarkdownPlugin.configure({ options: { allowedNodes: [...] }}) | Filters nodes during conversion (mdast/Plate types). |
| disallowedElements | MarkdownPlugin.configure({ options: { disallowedNodes: [...] }}) | Filters nodes during conversion. |
| unwrapDisallowed | No direct equivalent. Filtering removes nodes. | Custom rules could implement unwrapping. |
| skipHtml | Default behavior strips most HTML. | Use rehype-raw via remarkPlugins for HTML processing. |
| urlTransform | Customize via rules for link (deserialize) or plugin type (serialize). | Handle URL transformations in conversion rules. |
| allowElement | MarkdownPlugin.configure({ options: { allowNode: { ... } } }) | Function-based filtering during conversion. |
By default, @platejs/markdown does not process raw HTML tags for security. Standard Markdown generating HTML (e.g., *emphasis* ā <em>) is handled.
To process raw HTML in a trusted environment:
remark-mdx: Add to remarkPlugins.rehype-raw: Add rehype-raw to remarkPlugins.rules for parsed HTML hast nodes to Plate structures.import { MarkdownPlugin, remarkMdx } from '@platejs/markdown';
import rehypeRaw from 'rehype-raw'; // May require VFile, ensure compatibility
// import { VFile } from 'vfile'; // If needed by rehype-raw setup
MarkdownPlugin.configure({
options: {
remarkPlugins: [
remarkMdx,
// Using rehype plugins within remark pipeline can be complex.
[
rehypeRaw,
{
/* options, e.g., pass vfile */
},
],
],
rules: {
// Example: Rule for HTML tags parsed by rehype-raw.
// mdastNode structure depends on rehype-raw output.
element: {
// Generic rule for 'element' nodes from rehype-raw
deserialize: (mdastNode, deco, options) => {
// Simplified: Needs proper handling based on mdastNode.tagName and attributes.
// You'll likely need specific rules per HTML tag.
if (mdastNode.tagName === 'div') {
return {
type: 'html_div', // Example: Map to a custom 'html_div' Plate element
children: convertChildrenDeserialize(
mdastNode.children,
deco,
options
),
};
}
// Fallback or handle other tags
return convertChildrenDeserialize(mdastNode.children, deco, options);
},
},
// Add serialization rules if outputting raw HTML from Plate.
},
},
});
<Callout type="destructive" title="Security Warning">
Enabling raw HTML rendering increases XSS risk if the Markdown source isn't
trusted. Use [`rehype-sanitize`][github-rehype-sanitize] after `rehype-raw` to
whitelist HTML elements/attributes.
</Callout>
rules)The rules option in MarkdownPlugin.configure offers fine-grained control over mdast ā Plate JSON conversion. Keys in the rules object match node types.
mdast node types (e.g., paragraph, heading, strong, link, MDX types like mdxJsxTextElement). The deserialize function takes (mdastNode, deco, options) and returns a Plate Descendant or Descendant[].p, h1, a, code_block, bold). The serialize function takes (slateNode, options) and returns an mdast node.Example: Overriding Link Deserialization
MarkdownPlugin.configure({
options: {
rules: {
// Rule for mdast 'link' type
link: {
deserialize: (mdastNode, deco, options) => {
// Default creates { type: 'a', url: ..., children: [...] }
// Add a custom property:
return {
type: 'a', // Plate link element type
url: mdastNode.url,
title: mdastNode.title,
customProp: 'added-during-deserialize',
children: convertChildrenDeserialize(
mdastNode.children,
deco,
options
),
};
},
},
// Rule for Plate 'a' type (if serialization needs override for customProp)
a: {
// Assuming 'a' is the Plate type for links
serialize: (slateNode, options) => {
// Default creates mdast 'link'
// Handle customProp if needed in MDX attributes or similar
return {
type: 'link', // mdast type
url: slateNode.url,
title: slateNode.title,
// customProp: slateNode.customProp, // MDX attribute?
children: convertNodesSerialize(slateNode.children, options),
};
},
},
},
// ... remarkPlugins ...
},
});
Default Rules Summary:
Refer to defaultRules.ts for the complete list. Key conversions include:
| Markdown (mdast) | Plate Type | Notes |
| :------------------ | :--------------------- | :--------------------------------------------- |
| paragraph | p | |
| heading (depth) | h1 - h6 | Based on depth. |
| blockquote | blockquote | |
| list (ordered) | ol / p* | ol/li/lic or p with list indent props. |
| list (unordered) | ul / p* | ul/li/lic or p with list indent props. |
| code (fenced) | code_block | Contains code_line children. |
| inlineCode | code (mark) | Applied to text. |
| strong | bold (mark) | Applied to text. |
| emphasis | italic (mark) | Applied to text. |
| delete | strikethrough (mark) | Applied to text. |
| link | a | |
| image | img | Wraps in paragraph during serialization. |
| thematicBreak | hr | |
| table | table | Contains tr. |
| math (block) | equation | Requires remark-math. |
| inlineMath | inline_equation | Requires remark-math. |
| mdxJsxFlowElement | Custom | Requires remark-mdx and custom rules. |
| mdxJsxTextElement | Custom | Requires remark-mdx and custom rules. |
* List conversion depends on ListPlugin detection.
Default MDX Conversions (with remark-mdx):
| MDX (mdast) | Plate Type | Notes |
| :------------------------------------- | :----------------------- | :------------------------------------------ |
| <del>...</del> | strikethrough (mark) | Alt for ~~strikethrough~~ |
| <sub>...</sub> | subscript (mark) | H<sub>2</sub>O |
| <sup>...</sup> | superscript (mark) | E=mc<sup>2</sup> |
| <u>...</u> | underline (mark) | <u>Underlined</u> |
| <mark>...</mark> | highlight (mark) | <mark>Highlighted</mark> |
| <span style="font-family: ..."> | fontFamily (mark) | |
| <span style="font-size: ..."> | fontSize (mark) | |
| <span style="font-weight: ..."> | fontWeight (mark) | |
| <span style="color: ..."> | color (mark) | |
| <span style="background-color: ..."> | backgroundColor (mark) | |
| <date>...</date> | date | Custom Date element |
| [text](mention:id) | mention | Custom Mention element |
| <file name="..." /> | file | Custom File element |
| <audio src="..." /> | audio | Custom Audio element |
| <video src="..." /> | video | Custom Video element |
| <toc /> | toc | Table of Contents |
| <callout>...</callout> | callout | Callout block |
| <column_group>...</column_group> | column_group | Multi-column layout container |
| <column width="50%">...</column> | column | Single column with optional width attribute |
While rules handle MD ā Plate conversion, Plate uses React components to render Plate nodes. Configure these in createPlateEditor via the components option or plugin withComponent method.
Example:
import { createPlateEditor, ParagraphPlugin, PlateLeaf } from 'platejs/react';
import { BoldPlugin } from '@platejs/basic-nodes/react';
import { CodeBlockPlugin } from '@platejs/code-block/react';
import { ParagraphElement } from '@/components/ui/paragraph-node'; // Example UI component
import { CodeBlockElement } from '@/components/ui/code-block-node'; // Example UI component
const editor = createPlateEditor({
plugins: [
ParagraphPlugin.withComponent(ParagraphElement),
CodeBlockPlugin.withComponent(CodeBlockElement),
BoldPlugin,
/* ... */
],
});
Refer to Plugin Components for more on creating/registering components.
PlateMarkdown Component (Read-Only Display)For a react-markdown-like component for read-only display:
import React, { useEffect } from 'react';
import { Plate, PlateContent, usePlateEditor } from 'platejs/react';
import { MarkdownPlugin } from '@platejs/markdown';
// Import necessary Plate plugins for common Markdown features
import { HeadingPlugin } from '@platejs/basic-nodes/react';
// ... include other plugins like BlockquotePlugin, CodeBlockPlugin, ListPlugin, etc.
// ... and mark plugins like BoldPlugin, ItalicPlugin, etc.
export interface PlateMarkdownProps {
children: string; // Markdown content
remarkPlugins?: any[];
components?: Record<string, React.ComponentType<any>>; // Plate component overrides
className?: string;
}
export function PlateMarkdown({
children,
remarkPlugins = [],
components = {},
className,
}: PlateMarkdownProps) {
const editor = usePlateEditor({
plugins: [
// Include all plugins needed to render your Markdown
HeadingPlugin /* ... other plugins ... */,
MarkdownPlugin.configure({ options: { remarkPlugins } }),
],
components, // Pass through component overrides
});
useEffect(() => {
editor.tf.reset(); // Clear previous content
editor.tf.setValue(
editor.getApi(MarkdownPlugin).markdown.deserialize(children)
);
}, [children, editor, remarkPlugins]); // Re-deserialize if markdown or plugins change
return (
<Plate editor={editor}>
<PlateContent readOnly className={className} />
</Plate>
);
}
// Usage Example:
// const markdownString = "# Hello\nThis is *Markdown*.";
// <PlateMarkdown className="prose dark:prose-invert">
// {markdownString}
// </PlateMarkdown>
<Callout type="info" title="Initial Value">
This `PlateMarkdown` component provides a **read-only** view. For full
editing, see the [Installation guides](/docs/installation).
</Callout>
@platejs/markdown prioritizes safety by converting Markdown to a structured Plate format, avoiding direct HTML rendering. However, security depends on:
rules: Ensure deserialize rules don't introduce unsafe data.remarkPlugins: Vet third-party remark plugins for potential security risks.rehype-raw is used, always sanitize with rehype-sanitize if the source is untrusted.LinkPlugin (isUrl) or MediaEmbedPlugin (parseMediaUrl) is crucial.Recommendation: Treat untrusted Markdown input cautiously. Sanitize if allowing complex features or raw HTML.
ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā