A set of plugins and utils for integrating remark, mdast, and ProseMirror. Can be used to convert ProseMirror documents to Markdown, and vice versa!
npm i @handlewithcare/remark-prosemirrorThis library exports tools for converting back and forth between Markdown and ProseMirror documents. It’s part of the unified ecosystem.
To parse a Markdown document into ProseMirror, first we parse the document into mdast (Markdown abstract syntax tree), and then use the remark plugin to compile that to ProseMirror.
import { unified } from "unified";
import { remarkParse } from "remark-parse";
import {
remarkProseMirror,
toPmNode,
toPmMark,
type RemarkProseMirrorOptions,
} from "@handlewithcare/remark-prosemirror";
import type { Node } from "prosemirror-model";
import { mySchema } from "./mySchema.js";
async function markdownToProseMirror(markdown: string): Node {
const doc = await unified()
// Use remarkParse to parse the markdown string
.use(remarkParse)
// Convert to ProseMirror with the remarkProseMirror plugin.
// It takes the schema and a set of handlers, each of which
// maps an mdast node type to a ProseMirror node (or nodes)
.use(remarkProseMirror, {
schema: mySchema,
handlers: {
// For simple nodes, you can use the built-in toPmNode
// util
paragraph: toPmNode(mySchema.nodes.paragraph),
listItem: toPmNode(mySchema.nodes.list_item),
// If you need to take over control, you can write your
// own handler, which gets passed the mdast node, its
// parent, and the plugin state, which has helper methods
// for converting nodes from mdast to ProseMirror.
list(node, _, state) {
const children = state.all(node);
const nodeType = node.ordered
? mySchema.nodes.ordered_list
: mySchema.nodes.bullet_list;
return nodeType.createAndFill({}, children);
},
// You can also treat mdast nodes as ProseMirror marks
emphasis: toPmMark(mySchema.marks.em),
strong: toPmMark(mySchema.marks.strong),
// And you can set attrs on nodes or marks based on
// the mdast data
link: toPmMark(mySchema.marks.link, (node) => ({
href: node.url,
title: node.title,
})),
},
} satisfies RemarkProseMirrorOptions)
.process(markdown);
return doc;
}To serialize a ProseMirror document to Markdown, first we convert the ProseMirror document to mdast. Then we can use remark to serialize the mdast to a string.
import { unified } from "unified";
import { remarkStringify } from "remark-stringify";
import {
fromProseMirror,
fromPmNode,
fromPmMark,
} from "@handlewithcare/remark-prosemirror";
import type { Node } from "prosemirror-model";
import { mySchema } from "./mySchema.js";
function proseMirrorToMarkdown(doc: Node) {
// Convert to mdast with the fromProseMirror util.
// It takes a schema, a set of node handlers, and a
// set of mark handlers, each of which converts a
// ProseMirror node or mark to an mdast node.
const mdast = fromProseMirror(doc, {
schema: mySchema,
nodeHandlers: {
// Simple nodes can be converted with the fromPmNode
// util.
paragraph: fromPmNode("paragraph"),
list_item: fromPmNode("listItem"),
// You can set mdast node properties from the
// ProseMirror node or its attrs
ordered_list: fromPmNode("list", () => ({
ordered: true,
})),
bullet_list: fromPmNode("list", () => ({
ordered: false,
})),
},
markHandlers: {
// Simple marks can be converted with the fromPmMark
// util.
em: fromPmMark("emphasis"),
strong: fromPmMark("strong"),
// Again, mdast node properties can be set from the
// ProseMirror mark attrs
link: fromPmMark("link", (mark) => ({
url: mark.attrs["href"],
title: mark.attrs["title"],
})),
},
});
return unified().use(remarkStringify).stringify(mdast);
}| Type Parameter |
|---|
PmNodes extends string |
PmMarks extends string |
markHandlers:
Partial<Record<PmMarks,PmMarkHandler>>
lib/mdast-util-from-prosemirror.ts:125
nodeHandlers:
Partial<Record<PmNodes,PmNodeHandler>>
lib/mdast-util-from-prosemirror.ts:124
schema:
Schema<PmNodes,PmMarks>
lib/mdast-util-from-prosemirror.ts:123
handlers:
MdastHandlers
lib/mdast-util-to-prosemirror.ts:367
optionalhtmlHandlers:HastHandlers
lib/mdast-util-to-prosemirror.ts:368
schema:
Schema<any,any>
lib/mdast-util-to-prosemirror.ts:366
fromPmMark<
Type>(type,getAttrs?):PmMarkHandler
| Type Parameter |
|---|
Type extends "blockquote" | "break" | "code" | "definition" | "delete" | "emphasis" | "footnoteDefinition" | "footnoteReference" | "heading" | "html" | "image" | "imageReference" | "inlineCode" | "link" | "linkReference" | "list" | "listItem" | "paragraph" | "strong" | "table" | "tableCell" | "tableRow" | "text" | "thematicBreak" | "yaml" | "root" |
| Parameter | Type |
|---|---|
type |
Type |
getAttrs? |
(pmNode) => Omit<Extract<Root, { type: Type; }> | Extract<Blockquote, { type: Type; }> | Extract<Break, { type: Type; }> | Extract<Code, { type: Type; }> | Extract<Definition, { type: Type; }> | Extract<Delete, { type: Type; }> | Extract<Emphasis, { type: Type; }> | Extract<FootnoteDefinition, { type: Type; }> | Extract<FootnoteReference, { type: Type; }> | Extract<Heading, { type: Type; }> | Extract<Html, { type: Type; }> | Extract<Image, { type: Type; }> | Extract<ImageReference, { type: Type; }> | Extract<InlineCode, { type: Type; }> | Extract<Link, { type: Type; }> | Extract<LinkReference, { type: Type; }> | Extract<List, { type: Type; }> | Extract<ListItem, { type: Type; }> | Extract<Paragraph, { type: Type; }> | Extract<Strong, { type: Type; }> | Extract<Table, { type: Type; }> | Extract<TableCell, { type: Type; }> | Extract<TableRow, { type: Type; }> | Extract<Text, { type: Type; }> | Extract<ThematicBreak, { type: Type; }> | Extract<Yaml, { type: Type; }>, "type" | "children"> |
PmMarkHandler
lib/mdast-util-from-prosemirror.ts:178
fromPmNode<
Type>(type,getAttrs?):PmNodeHandler
| Type Parameter |
|---|
Type extends "blockquote" | "break" | "code" | "definition" | "delete" | "emphasis" | "footnoteDefinition" | "footnoteReference" | "heading" | "html" | "image" | "imageReference" | "inlineCode" | "link" | "linkReference" | "list" | "listItem" | "paragraph" | "strong" | "table" | "tableCell" | "tableRow" | "text" | "thematicBreak" | "yaml" | "root" |
| Parameter | Type |
|---|---|
type |
Type |
getAttrs? |
(pmNode) => Omit<Extract<Root, { type: Type; }> | Extract<Blockquote, { type: Type; }> | Extract<Break, { type: Type; }> | Extract<Code, { type: Type; }> | Extract<Definition, { type: Type; }> | Extract<Delete, { type: Type; }> | Extract<Emphasis, { type: Type; }> | Extract<FootnoteDefinition, { type: Type; }> | Extract<FootnoteReference, { type: Type; }> | Extract<Heading, { type: Type; }> | Extract<Html, { type: Type; }> | Extract<Image, { type: Type; }> | Extract<ImageReference, { type: Type; }> | Extract<InlineCode, { type: Type; }> | Extract<Link, { type: Type; }> | Extract<LinkReference, { type: Type; }> | Extract<List, { type: Type; }> | Extract<ListItem, { type: Type; }> | Extract<Paragraph, { type: Type; }> | Extract<Strong, { type: Type; }> | Extract<Table, { type: Type; }> | Extract<TableCell, { type: Type; }> | Extract<TableRow, { type: Type; }> | Extract<Text, { type: Type; }> | Extract<ThematicBreak, { type: Type; }> | Extract<Yaml, { type: Type; }>, "type" | "children"> |
PmNodeHandler
lib/mdast-util-from-prosemirror.ts:157
fromProseMirror<
PmNodes,PmMarks>(pmNode,options):MdastRoot
| Type Parameter |
|---|
PmNodes extends string |
PmMarks extends string |
| Parameter | Type |
|---|---|
pmNode |
Node |
options |
FromProseMirrorOptions<PmNodes, PmMarks> |
MdastRoot
lib/mdast-util-from-prosemirror.ts:128
remarkProseMirror(
this, ...parameters):undefined|void
| Parameter | Type |
|---|---|
this |
Processor<undefined, undefined, undefined, undefined, undefined> |
...parameters |
[RemarkProseMirrorOptions] |
undefined | void
toPmMark<
MdastNode>(markType,getAttrs?): (node,_,state) =>Node[]
| Type Parameter |
|---|
MdastNode extends Nodes |
| Parameter | Type |
|---|---|
markType |
MarkType |
getAttrs? |
(mdastNode) => null | Record<string, unknown> |
Function
| Parameter | Type |
|---|---|
node |
MdastNode |
_ |
Parent |
state |
State |
Node[]
lib/mdast-util-to-prosemirror.ts:335
toPmNode<
MdastNode>(nodeType,getAttrs?): (node,_,state) =>null|Node
| Type Parameter |
|---|
MdastNode extends Nodes |
| Parameter | Type |
|---|---|
nodeType |
NodeType |
getAttrs? |
(mdastNode) => null | Record<string, unknown> |
Function
| Parameter | Type |
|---|---|
node |
MdastNode |
_ |
Parent |
state |
State |
null | Node
lib/mdast-util-to-prosemirror.ts:324
Reach out to Handle with Care! We're a product development collective with years of experience bringing excellent ideas to life. We love Markdown and ProseMirror, and we're always looking for new folks to work with!