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!
The following companies, organizations, and individuals support Pitter Patter's ongoing development.
Sponsors receive regular updates about development progress, including previews of upcoming features, priority getting issues addressed as we release features, and a direct line of communication to us for support. Funders above a certain threshold join our steering committee — a lightweight governance model where significant contributors help shape our roadmap priorities.
Moment |
Lingco |
dskrpt |
Fastrepl |