This example previews Markdown syntax with Slate decorations. It does not deserialize Markdown into Plate nodes; it keeps the text as text and styles matching ranges with a custom leaf renderer.
Demo
Loading…
Source
The demo tokenizes each text node with Prism's Markdown grammar and returns decoration ranges for token types such as title, bold, italic, blockquote, list, horizontal rule, and code.
'use client';
import * as React from 'react';
import {
type Decorate,
type RenderLeafProps,
type TText,
createSlatePlugin,
TextApi,
} from 'platejs';
import { Plate, usePlateEditor } from 'platejs/react';
import Prism from 'prismjs';
import { cn } from '@/lib/utils';
import { BasicNodesKit } from '@/components/editor/plugins/basic-nodes-kit';
import { previewMdValue } from '@/registry/examples/values/preview-md-value';
import { Editor, EditorContainer } from '@/components/ui/editor';
import 'prismjs/components/prism-markdown.js';
/** Decorate texts with markdown preview. */
const decoratePreview: Decorate = ({ entry: [node, path] }) => {
const ranges: any[] = [];
if (!TextApi.isText(node)) {
return ranges;
}
const getLength = (token: any) => {
if (typeof token === 'string') {
return token.length;
}
if (typeof token.content === 'string') {
return token.content.length;
}
return token.content.reduce((l: any, t: any) => l + getLength(t), 0);
};
const tokens = Prism.tokenize(node.text, Prism.languages.markdown);
let start = 0;
for (const token of tokens) {
const length = getLength(token);
const end = start + length;
if (typeof token !== 'string') {
ranges.push({
anchor: { offset: start, path },
focus: { offset: end, path },
[token.type]: true,
});
}
start = end;
}
return ranges;
};
function PreviewLeaf({
attributes,
children,
leaf,
}: RenderLeafProps<
{
blockquote?: boolean;
bold?: boolean;
code?: boolean;
hr?: boolean;
italic?: boolean;
list?: boolean;
title?: boolean;
} & TText
>) {
const { blockquote, bold, code, hr, italic, list, title } = leaf;
return (
<span
{...attributes}
className={cn(
bold && 'font-bold',
italic && 'italic',
title && 'mx-0 mt-5 mb-2.5 inline-block font-bold text-[20px]',
list && 'pl-2.5 text-[20px] leading-[10px]',
hr && 'block border-[#ddd] border-b-2 text-center',
blockquote &&
'inline-block border-[#ddd] border-l-2 pl-2.5 text-[#aaa] italic',
code && 'bg-[#eee] p-[3px] font-mono'
)}
>
{children}
</span>
);
}
export default function PreviewMdDemo() {
const editor = usePlateEditor(
{
plugins: [
...BasicNodesKit,
createSlatePlugin({
key: 'preview-markdown',
decorate: decoratePreview,
}),
],
value: previewMdValue,
},
[]
);
return (
<Plate editor={editor}>
<EditorContainer>
<Editor renderLeaf={PreviewLeaf} />
</EditorContainer>
</Plate>
);
}'use client';
import * as React from 'react';
import {
type Decorate,
type RenderLeafProps,
type TText,
createSlatePlugin,
TextApi,
} from 'platejs';
import { Plate, usePlateEditor } from 'platejs/react';
import Prism from 'prismjs';
import { cn } from '@/lib/utils';
import { BasicNodesKit } from '@/components/editor/plugins/basic-nodes-kit';
import { previewMdValue } from '@/registry/examples/values/preview-md-value';
import { Editor, EditorContainer } from '@/components/ui/editor';
import 'prismjs/components/prism-markdown.js';
/** Decorate texts with markdown preview. */
const decoratePreview: Decorate = ({ entry: [node, path] }) => {
const ranges: any[] = [];
if (!TextApi.isText(node)) {
return ranges;
}
const getLength = (token: any) => {
if (typeof token === 'string') {
return token.length;
}
if (typeof token.content === 'string') {
return token.content.length;
}
return token.content.reduce((l: any, t: any) => l + getLength(t), 0);
};
const tokens = Prism.tokenize(node.text, Prism.languages.markdown);
let start = 0;
for (const token of tokens) {
const length = getLength(token);
const end = start + length;
if (typeof token !== 'string') {
ranges.push({
anchor: { offset: start, path },
focus: { offset: end, path },
[token.type]: true,
});
}
start = end;
}
return ranges;
};
function PreviewLeaf({
attributes,
children,
leaf,
}: RenderLeafProps<
{
blockquote?: boolean;
bold?: boolean;
code?: boolean;
hr?: boolean;
italic?: boolean;
list?: boolean;
title?: boolean;
} & TText
>) {
const { blockquote, bold, code, hr, italic, list, title } = leaf;
return (
<span
{...attributes}
className={cn(
bold && 'font-bold',
italic && 'italic',
title && 'mx-0 mt-5 mb-2.5 inline-block font-bold text-[20px]',
list && 'pl-2.5 text-[20px] leading-[10px]',
hr && 'block border-[#ddd] border-b-2 text-center',
blockquote &&
'inline-block border-[#ddd] border-l-2 pl-2.5 text-[#aaa] italic',
code && 'bg-[#eee] p-[3px] font-mono'
)}
>
{children}
</span>
);
}
export default function PreviewMdDemo() {
const editor = usePlateEditor(
{
plugins: [
...BasicNodesKit,
createSlatePlugin({
key: 'preview-markdown',
decorate: decoratePreview,
}),
],
value: previewMdValue,
},
[]
);
return (
<Plate editor={editor}>
<EditorContainer>
<Editor renderLeaf={PreviewLeaf} />
</EditorContainer>
</Plate>
);
}Initial Value
The value is plain Plate content whose paragraph text includes Markdown characters.
/** @jsxRuntime classic */
/** @jsx jsx */
import { jsx } from '@platejs/test-utils';
jsx;
export const previewMdValue: any = (
<fragment>
<hh2>👀 Preview Markdown</hh2>
<hp>
Slate is flexible enough to add **decorations** that can format text based
on its content. For example, this editor has **Markdown** preview
decorations on it, to make it _dead_ simple to make an editor with
built-in `Markdown` previewing.
</hp>
<hp>- List item.</hp>
<hp>> Blockquote paragraph.</hp>
<hp>> > Nested blockquote.</hp>
<hp>> - Quoted list item.</hp>
<hp>---</hp>
<hp>## Try it out!</hp>
<hp>Try it out for yourself!</hp>
</fragment>
);/** @jsxRuntime classic */
/** @jsx jsx */
import { jsx } from '@platejs/test-utils';
jsx;
export const previewMdValue: any = (
<fragment>
<hh2>👀 Preview Markdown</hh2>
<hp>
Slate is flexible enough to add **decorations** that can format text based
on its content. For example, this editor has **Markdown** preview
decorations on it, to make it _dead_ simple to make an editor with
built-in `Markdown` previewing.
</hp>
<hp>- List item.</hp>
<hp>> Blockquote paragraph.</hp>
<hp>> > Nested blockquote.</hp>
<hp>> - Quoted list item.</hp>
<hp>---</hp>
<hp>## Try it out!</hp>
<hp>Try it out for yourself!</hp>
</fragment>
);Runtime Shape
| Surface | Owner | Notes |
|---|---|---|
decoratePreview | Registry example | Reads text nodes, tokenizes node.text, and returns Slate ranges with token-type flags. |
PreviewLeaf | Registry example | Applies CSS classes when a decorated leaf has bold, italic, title, list, hr, blockquote, or code. |
preview-markdown plugin | Registry example | Local createSlatePlugin({ decorate }) plugin. |
BasicNodesKit | Registry kit | Supplies the editor's normal paragraph and heading plugins. |
prismjs | Dependency | Supplies the Markdown tokenizer used by the decoration function. |
Use this pattern when the editor should keep raw Markdown characters visible. Use Markdown when the editor should convert Markdown text into Plate nodes.
Related
- Markdown covers Markdown deserialization and serialization.
- Plate Plugin covers
decorate. - Text covers text decorations and decorated leaves.