From zbeyens. The source code is available on GitHub.

Plate
PlatePliteEditorsTemplates
GitHub16kGitHub
DiscordDiscord
  • Examples
    • Plate to HTML
    • 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
    • List Classic Demo
    • Find Replace 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
    • Cursor Overlay 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
    • Toggle Demo

Preview Markdown

PreviousNext

Decorate text ranges so Markdown syntax previews inline.

This example previews Markdown syntax with Plite 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 type { DecoratedRange } from '@platejs/plite';
import { property, TextApi } from 'platejs';
import {
  type PlateLeafProps,
  definePlatePlugin,
  Plate,
  PlateLeaf,
  usePlateEditor,
} 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





































































































Initial Value

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

/** @jsxRuntime classic */
/** @jsx jsx */
import { jsx } from '@platejs/test-utils';
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
decoratePreviewRegistry exampleReads text nodes, tokenizes node.text, and returns Plite ranges with token-type flags.
PreviewLeafRegistry exampleApplies CSS classes when a decorated leaf has bold, italic, title, list, hr, blockquote, or code.
preview-markdown pluginRegistry example

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.
Markdown StreamingCollaboration Demo

On This Page

DemoSourceInitial ValueRuntime ShapeRelated
Build your editor
Production-ready AI template and reusable components.
Get all-access
{ Editor, EditorContainer }
from
'@/components/editor/editor'
;
import { previewMdValue } from '@/registry/examples/values/preview-md-value';
import 'prismjs/components/prism-markdown.js';
const PreviewMarkdownPlugin = definePlatePlugin('previewMarkdown', {
schema: {
mark: property.boolean({ default: false, omitDefault: true }),
},
decorate: ({ 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 ranges: Array<
DecoratedRange & {
blockquote?: boolean;
bold?: boolean;
code?: boolean;
hr?: boolean;
italic?: boolean;
list?: boolean;
previewMarkdown: boolean;
title?: boolean;
}
> = [];
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 },
blockquote: token.type === 'blockquote' || undefined,
bold: token.type === 'bold' || undefined,
code: token.type === 'code' || undefined,
focus: { offset: end, path },
hr: token.type === 'hr' || undefined,
italic: token.type === 'italic' || undefined,
list: token.type === 'list' || undefined,
previewMarkdown: true,
title: token.type === 'title' || undefined,
});
}
start = end;
}
return ranges;
},
});
function PreviewLeaf(props: PlateLeafProps<typeof PreviewMarkdownPlugin>) {
const { blockquote, bold, code, hr, italic, list, title } = props.leaf;
return (
<PlateLeaf
{...props}
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'
)}
/>
);
}
const PreviewMarkdownKit = PreviewMarkdownPlugin.configure({
component: PreviewLeaf,
});
export default function PreviewMdDemo() {
const editor = usePlateEditor(
{
plugins: [...BasicNodesKit, PreviewMarkdownKit],
initialValue: previewMdValue,
},
[]
);
return (
<Plate editor={editor}>
<EditorContainer>
<Editor />
</EditorContainer>
</Plate>
);
}
'use client';
 
import type { DecoratedRange } from '@platejs/plite';
import { property, TextApi } from 'platejs';
import {
  type PlateLeafProps,
  definePlatePlugin,
  Plate,
  PlateLeaf,
  usePlateEditor,
} 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 = definePlatePlugin('previewMarkdown', {
  schema: {
    mark: property.boolean({ default: false, omitDefault: true }),
  },
  decorate: ({ 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 ranges: Array<
      DecoratedRange & {
        blockquote?: boolean;
        bold?: boolean;
        code?: boolean;
        hr?: boolean;
        italic?: boolean;
        list?: boolean;
        previewMarkdown: boolean;
        title?: boolean;
      }
    > = [];
    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 },
          blockquote: token.type === 'blockquote' || undefined,
          bold: token.type === 'bold' || undefined,
          code: token.type === 'code' || undefined,
          focus: { offset: end, path },
          hr: token.type === 'hr' || undefined,
          italic: token.type === 'italic' || undefined,
          list: token.type === 'list' || undefined,
          previewMarkdown: true,
          title: token.type === 'title' || undefined,
        });
      }
 
      start = end;
    }
 
    return ranges;
  },
});
 
function PreviewLeaf(props: PlateLeafProps<typeof PreviewMarkdownPlugin>) {
  const { blockquote, bold, code, hr, italic, list, title } = props.leaf;
 
  return (
    <PlateLeaf
      {...props}
      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'
      )}
    />
  );
}
 
const PreviewMarkdownKit = PreviewMarkdownPlugin.configure({
  component: PreviewLeaf,
});
 
export default function PreviewMdDemo() {
  const editor = usePlateEditor(
    {
      plugins: [...BasicNodesKit, PreviewMarkdownKit],
      initialValue: previewMdValue,
    },
    []
  );
 
  return (
    <Plate editor={editor}>
      <EditorContainer>
        <Editor />
      </EditorContainer>
    </Plate>
  );
}
<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-utils';
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>
);
Local defineBasePlugin(name, { decorate }) plugin.
BasicNodesKitRegistry kitSupplies the editor's normal paragraph and heading plugins.
prismjsDependencySupplies the Markdown tokenizer used by the decoration function.