genesis-editor

Markdown

Load Markdown as HTML through the genesis engine: format="markdown", setMarkdown, and paste detection.

Markdown in, Markdown out#

Set format="markdown" and value, defaultValue, and onChange carry Markdown strings. Incoming Markdown is converted to HTML with marked (MIT) and parsed by the same engine as HTML & paste: sanitizing, tools, cleanup, and the import report still apply. Outgoing Markdown is BlockNote's lossy export, so echoing onChange back is stable even though it won't match the source character for character.

const [markdown, setMarkdown] = useState("# Hello\n\nWorld");

<BlockEditor format="markdown" value={markdown} onChange={(next) => setMarkdown(next)} />

Edit either side: the editor and the Markdown stay in sync.

A controlled value is parsed only when it differs from the Markdown the editor last emitted, so passing onChange's string straight back costs nothing and never moves the cursor. onChange runs at most every 150 ms while typing.

Pasting#

Plain-text Markdown from ChatGPT, GitHub, Slack, or a .md file is detected and imported through the HTML engine, in every format. Headings, lists, quotes, code fences, tables, rules, and Markdown links or images count; a lone URL or ordinary sentence stays with the default handler. Real HTML pastes still win: copying a rendered page keeps the HTML path. A trivial <pre> or <div> wrapper around the same Markdown source is treated as Markdown.

Inline links stay links. Scripts and javascript: URLs are stripped by the HTML engine after conversion. BlockNote's own clipboard and VS Code snippets are unchanged, and pastes into code blocks stay plain text.

Ref

setMarkdown replaces the document the same way setHTML does, and returns the import report. getMarkdown is the lossy export, in every format.

const editor = useRef<BlockEditorHandle>(null);

editor.current.getMarkdown();                 // lossy export
const report = editor.current.setMarkdown(md); // via the HTML engine
editor.current.lastReport;

On the server#

markdownToHTML, parseMarkdown, and parseMarkdownAsync live on genesis-editor/parser. They do not sanitize on their own: always run the HTML they produce through parseHTML (which parseMarkdown already does).

import { parseMarkdown, markdownToHTML } from "genesis-editor/parser";
import { linkedomDom } from "genesis-editor/parser/linkedom";

const { blocks, report } = parseMarkdown(md, { dom: linkedomDom, tools: ["heading", "bold", "link"] });
const html = markdownToHTML(md); // then parseHTML(html, { … }) if you already have HTML options

Every option and report field is listed in the API reference.