Plate
PlateEditorsTemplates
GitHub16kGitHub
DiscordDiscord
  • HTML Export
  • Export
  • Server-Side
  • Version History
  • Editable Voids
  • Huge Document
  • Hundreds Editors
  • Markdown Streaming
  • Preview Markdown
  • Collaboration Demo
  • Table Nomerge Demo
  • Excalidraw Demo
  • Code Drawing Demo
  • Single Block Demo
  • Find Demo
  • AI Demo
  • Align Demo
  • Autoformat Demo
  • Basic Nodes Demo
  • Block Menu Demo
  • Node Selection Demo
  • Column Demo
  • Code Block Demo
  • Callout Demo
  • Discussion Demo
  • Date Demo
  • Footnote Demo
  • Drag & Drop Demo
  • Emoji Demo
  • Equation Demo
  • Exit Break Demo
  • Floating Toolbar Demo
  • Font Demo
  • Indent Demo
  • List Demo
  • Line Height Demo
  • Link Demo
  • Media Demo
  • Mention Demo
  • Block Placeholder Demo
  • Serializing CSV Demo
  • Serializing Docx Demo
  • Serializing HTML Demo
  • Serializing Markdown Demo
  • Slash Command Demo
  • Plugin Rules Demo
  • Table Demo
  • Table of Contents Demo
  • Details Demo

Preview Markdown

PreviousNext

Decorate text ranges so Markdown syntax previews inline.

This example previews Markdown syntax with Plate decorations. It keeps the Markdown characters in text nodes and applies CSS classes to matching ranges.

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 { TextApi } from 'platejs';
import { definePlugin, EditorRoot, useCreateEditor } from 'platejs/react';
import Prism, { type TokenStream } from 'prismjs';
import * as React from 'react';
 
import { cn } from '@/lib/utils';
import { BasicNodesKit } from '@/components/editor/basic-nodes';
import { Editor, EditorContainer } from '@/components/editor/editor';
import { previewMdValue } from '@/registry/examples/values/preview-md-value';
 
import 'prismjs/components/prism-markdown.js';
 







































































Initial value

The value is plain Plate content whose paragraph text includes Markdown characters.

/** @jsxRuntime classic */
/** @jsx jsx */
import { jsx } from '@platejs/test';
import type { Value } from 'platejs';
 
jsx;
 
export const previewMdValue: Value = (
  <fragment>
    <hheading level={2}>👀 Preview Markdown</hheading>
    <hp>
      Plate 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>








Runtime shape

SurfaceOwnerNotes
PreviewMarkdownPluginRegistry exampleTokenizes each text node and returns keyed ranges with token-specific className and data-preview-markdown attributes.
BasicNodesKitRegistry kitSupplies the editor's normal paragraph and heading plugins.
prismjsDependencySupplies 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.
  • Decorations, annotations, and widgets covers transient range paint.
Markdown StreamingCollaboration Demo

On This Page

DemoSourceInitial valueRuntime shapeRelated
Build your editor
Production-ready AI template and reusable components.
Get all-access
const
PreviewMarkdownPlugin
=
definePlugin
(
'previewMarkdown'
, {
decorate: {
read: ({ entry: [node, path] }) => {
if (!TextApi.isText(node)) return [];
const getLength = (token: TokenStream): number => {
if (typeof token === 'string') return token.length;
if (Array.isArray(token)) {
return token.reduce((length, child) => length + getLength(child), 0);
}
if (typeof token.content === 'string') return token.content.length;
return getLength(token.content);
};
const decorations = [];
const tokens = Prism.tokenize(node.text, Prism.languages.markdown);
let start = 0;
for (const [index, token] of tokens.entries()) {
const length = getLength(token);
const end = start + length;
if (typeof token !== 'string') {
decorations.push({
attributes: {
className: cn(
token.type === 'bold' && 'font-bold',
token.type === 'italic' && 'italic',
token.type === 'title' &&
'mx-0 mt-5 mb-2.5 inline-block font-bold text-[20px]',
token.type === 'list' && 'pl-2.5 text-[20px] leading-[10px]',
token.type === 'hr' &&
'block border-[#ddd] border-b-2 text-center',
token.type === 'blockquote' &&
'inline-block border-[#ddd] border-l-2 pl-2.5 text-[#aaa] italic',
token.type === 'code' && 'bg-[#eee] p-[3px] font-mono'
),
'data-preview-markdown': token.type,
},
key: `${path.join('.')}:${index}:${start}:${end}:${token.type}`,
range: {
anchor: { offset: start, path },
focus: { offset: end, path },
},
});
}
start = end;
}
return decorations;
},
},
});
export default function PreviewMdDemo() {
const editor = useCreateEditor(
{
plugins: [...BasicNodesKit, PreviewMarkdownPlugin],
initialValue: previewMdValue,
},
[]
);
return (
<EditorRoot editor={editor}>
<EditorContainer>
<Editor />
</EditorContainer>
</EditorRoot>
);
}
'use client';
 
import { TextApi } from 'platejs';
import { definePlugin, EditorRoot, useCreateEditor } from 'platejs/react';
import Prism, { type TokenStream } from 'prismjs';
import * as React from 'react';
 
import { cn } from '@/lib/utils';
import { BasicNodesKit } from '@/components/editor/basic-nodes';
import { Editor, EditorContainer } from '@/components/editor/editor';
import { previewMdValue } from '@/registry/examples/values/preview-md-value';
 
import 'prismjs/components/prism-markdown.js';
 
const PreviewMarkdownPlugin = definePlugin('previewMarkdown', {
  decorate: {
    read: ({ entry: [node, path] }) => {
      if (!TextApi.isText(node)) return [];
 
      const getLength = (token: TokenStream): number => {
        if (typeof token === 'string') return token.length;
        if (Array.isArray(token)) {
          return token.reduce((length, child) => length + getLength(child), 0);
        }
        if (typeof token.content === 'string') return token.content.length;
 
        return getLength(token.content);
      };
      const decorations = [];
      const tokens = Prism.tokenize(node.text, Prism.languages.markdown);
      let start = 0;
 
      for (const [index, token] of tokens.entries()) {
        const length = getLength(token);
        const end = start + length;
 
        if (typeof token !== 'string') {
          decorations.push({
            attributes: {
              className: cn(
                token.type === 'bold' && 'font-bold',
                token.type === 'italic' && 'italic',
                token.type === 'title' &&
                  'mx-0 mt-5 mb-2.5 inline-block font-bold text-[20px]',
                token.type === 'list' && 'pl-2.5 text-[20px] leading-[10px]',
                token.type === 'hr' &&
                  'block border-[#ddd] border-b-2 text-center',
                token.type === 'blockquote' &&
                  'inline-block border-[#ddd] border-l-2 pl-2.5 text-[#aaa] italic',
                token.type === 'code' && 'bg-[#eee] p-[3px] font-mono'
              ),
              'data-preview-markdown': token.type,
            },
            key: `${path.join('.')}:${index}:${start}:${end}:${token.type}`,
            range: {
              anchor: { offset: start, path },
              focus: { offset: end, path },
            },
          });
        }
 
        start = end;
      }
 
      return decorations;
    },
  },
});
 
export default function PreviewMdDemo() {
  const editor = useCreateEditor(
    {
      plugins: [...BasicNodesKit, PreviewMarkdownPlugin],
      initialValue: previewMdValue,
    },
    []
  );
 
  return (
    <EditorRoot editor={editor}>
      <EditorContainer>
        <Editor />
      </EditorContainer>
    </EditorRoot>
  );
}
<hp>- List item.</hp>
<hp>&gt; Blockquote paragraph.</hp>
<hp>&gt; &gt; Nested blockquote.</hp>
<hp>&gt; - 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';
import type { Value } from 'platejs';
 
jsx;
 
export const previewMdValue: Value = (
  <fragment>
    <hheading level={2}>👀 Preview Markdown</hheading>
    <hp>
      Plate 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>&gt; Blockquote paragraph.</hp>
    <hp>&gt; &gt; Nested blockquote.</hp>
    <hp>&gt; - Quoted list item.</hp>
    <hp>---</hp>
    <hp>## Try it out!</hp>
    <hp>Try it out for yourself!</hp>
  </fragment>
);