Convert markdown-prosemirror parser/compiler to Remark

This commit is contained in:
Kyle Mathews 2017-03-02 12:02:55 -08:00 committed by Shawn Erquhart
parent 24c0a1bdb4
commit 0eb109cb73
6 changed files with 603 additions and 313 deletions

View File

@ -167,6 +167,7 @@
"slate-drop-or-paste-images": "^0.2.0",
"slug": "^0.9.1",
"textarea-caret-position": "^0.1.1",
"unist-util-visit": "^1.1.1",
"uuid": "^2.0.3",
"whatwg-fetch": "^1.0.0"
},

View File

@ -23,8 +23,8 @@ export default class MarkupItReactRenderer extends React.Component {
render() {
const { value } = this.props;
const mast = remark.parse(value);
const hast = toHAST(mast, { allowDangerousHTML: true });
const mdast = remark.parse(value);
const hast = toHAST(mdast, { allowDangerousHTML: true });
const html = hastToHTML(hast, { allowDangerousHTML: true });
return <div dangerouslySetInnerHTML={{ __html: html }} />; // eslint-disable-line react/no-danger
}

View File

@ -0,0 +1,324 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Compile markdown to Prosemirror document structure should compile a markdown ordered list 1`] = `
Object {
"content": Array [
Object {
"attrs": Object {
"level": 1,
},
"content": Array [
Object {
"text": "H1",
"type": "text",
},
],
"type": "heading",
},
Object {
"attrs": Object {
"order": 1,
"tight": true,
},
"content": Array [
Object {
"content": Array [
Object {
"content": Array [
Object {
"text": "yo",
"type": "text",
},
],
"type": "paragraph",
},
],
"type": "list_item",
},
Object {
"content": Array [
Object {
"content": Array [
Object {
"text": "bro",
"type": "text",
},
],
"type": "paragraph",
},
],
"type": "list_item",
},
Object {
"content": Array [
Object {
"content": Array [
Object {
"text": "fro",
"type": "text",
},
],
"type": "paragraph",
},
],
"type": "list_item",
},
],
"type": "ordered_list",
},
],
"type": "doc",
}
`;
exports[`Compile markdown to Prosemirror document structure should compile bulleted lists 1`] = `
Object {
"content": Array [
Object {
"attrs": Object {
"level": 1,
},
"content": Array [
Object {
"text": "H1",
"type": "text",
},
],
"type": "heading",
},
Object {
"attrs": Object {
"tight": false,
},
"content": Array [
Object {
"content": Array [
Object {
"content": Array [
Object {
"text": "yo",
"type": "text",
},
],
"type": "paragraph",
},
],
"type": "list_item",
},
Object {
"content": Array [
Object {
"content": Array [
Object {
"text": "bro",
"type": "text",
},
],
"type": "paragraph",
},
],
"type": "list_item",
},
Object {
"content": Array [
Object {
"content": Array [
Object {
"text": "fro",
"type": "text",
},
],
"type": "paragraph",
},
],
"type": "list_item",
},
],
"type": "bullet_list",
},
],
"type": "doc",
}
`;
exports[`Compile markdown to Prosemirror document structure should compile hard breaks (double space) 1`] = `
Object {
"content": Array [
Object {
"content": Array [
Object {
"text": "blue moon",
"type": "text",
},
Object {
"type": "hard_break",
},
Object {
"text": "footballs",
"type": "text",
},
],
"type": "paragraph",
},
],
"type": "doc",
}
`;
exports[`Compile markdown to Prosemirror document structure should compile horizontal rules 1`] = `
Object {
"content": Array [
Object {
"attrs": Object {
"level": 1,
},
"content": Array [
Object {
"text": "H1",
"type": "text",
},
],
"type": "heading",
},
Object {
"type": "horizontal_rule",
},
Object {
"content": Array [
Object {
"text": "blue moon",
"type": "text",
},
],
"type": "paragraph",
},
],
"type": "doc",
}
`;
exports[`Compile markdown to Prosemirror document structure should compile horizontal rules 2`] = `
Object {
"content": Array [
Object {
"attrs": Object {
"level": 1,
},
"content": Array [
Object {
"text": "H1",
"type": "text",
},
],
"type": "heading",
},
Object {
"type": "horizontal_rule",
},
Object {
"content": Array [
Object {
"text": "blue moon",
"type": "text",
},
],
"type": "paragraph",
},
],
"type": "doc",
}
`;
exports[`Compile markdown to Prosemirror document structure should compile images 1`] = `
Object {
"content": Array [
Object {
"content": Array [
Object {
"attrs": Object {
"alt": "super",
"src": "duper.jpg",
"title": null,
},
"type": "image",
},
],
"type": "paragraph",
},
],
"type": "doc",
}
`;
exports[`Compile markdown to Prosemirror document structure should compile multiple header levels 1`] = `
Object {
"content": Array [
Object {
"attrs": Object {
"level": 1,
},
"content": Array [
Object {
"text": "H1",
"type": "text",
},
],
"type": "heading",
},
Object {
"attrs": Object {
"level": 2,
},
"content": Array [
Object {
"text": "H2",
"type": "text",
},
],
"type": "heading",
},
Object {
"attrs": Object {
"level": 3,
},
"content": Array [
Object {
"text": "H3",
"type": "text",
},
],
"type": "heading",
},
],
"type": "doc",
}
`;
exports[`Compile markdown to Prosemirror document structure should compile simple markdown 1`] = `
Object {
"content": Array [
Object {
"attrs": Object {
"level": 1,
},
"content": Array [
Object {
"text": "H1",
"type": "text",
},
],
"type": "heading",
},
Object {
"content": Array [
Object {
"text": "sweet body",
"type": "text",
},
],
"type": "paragraph",
},
],
"type": "doc",
}
`;

View File

@ -0,0 +1,91 @@
import { Schema } from "prosemirror-model";
import { schema } from "prosemirror-markdown";
const makeParser = require("../parser");
const testSchema = new Schema({
nodes: schema.nodeSpec,
marks: schema.markSpec,
});
const parser = makeParser(testSchema);
describe("Compile markdown to Prosemirror document structure", () => {
it("should compile simple markdown", () => {
const value = `
# H1
sweet body
`;
expect(parser(value)).toMatchSnapshot();
});
it("should compile a markdown ordered list", () => {
const value = `
# H1
1. yo
2. bro
3. fro
`;
expect(parser(value)).toMatchSnapshot();
});
it("should compile bulleted lists", () => {
const value = `
# H1
* yo
* bro
* fro
`;
expect(parser(value)).toMatchSnapshot();
});
it("should compile multiple header levels", () => {
const value = `
# H1
## H2
### H3
`;
expect(parser(value)).toMatchSnapshot();
});
it("should compile horizontal rules", () => {
const value = `
# H1
---
blue moon
`;
expect(parser(value)).toMatchSnapshot();
});
it("should compile horizontal rules", () => {
const value = `
# H1
---
blue moon
`;
expect(parser(value)).toMatchSnapshot();
});
it("should compile hard breaks (double space)", () => {
const value = `
blue moon
footballs
`;
expect(parser(value)).toMatchSnapshot();
});
it("should compile images", () => {
const value = `
![super](duper.jpg)
`;
expect(parser(value)).toMatchSnapshot();
});
});

View File

@ -5,8 +5,13 @@ import { EditorState } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
import history from 'prosemirror-history';
import {
blockQuoteRule, orderedListRule, bulletListRule, codeBlockRule, headingRule,
inputRules, allInputRules,
blockQuoteRule,
orderedListRule,
bulletListRule,
codeBlockRule,
headingRule,
inputRules,
allInputRules,
} from 'prosemirror-inputrules';
import { keymap } from 'prosemirror-keymap';
import { schema as markdownSchema, defaultMarkdownSerializer } from 'prosemirror-markdown';
@ -56,20 +61,26 @@ function schemaWithPlugins(schema, plugins) {
let nodeSpec = schema.nodeSpec;
plugins.forEach((plugin) => {
const attrs = {};
plugin.get('fields').forEach((field) => {
attrs[field.get('name')] = { default: null };
plugin.get("fields").forEach((field) => {
attrs[field.get("name")] = { default: null };
});
nodeSpec = nodeSpec.addToEnd(`plugin_${ plugin.get('id') }`, {
nodeSpec = nodeSpec.addToEnd(`plugin_${ plugin.get("id") }`, {
attrs,
group: 'block',
parseDOM: [{
tag: 'div[data-plugin]',
getAttrs(dom) {
return JSON.parse(dom.getAttribute('data-plugin'));
group: "block",
parseDOM: [
{
tag: "div[data-plugin]",
getAttrs(dom) {
return JSON.parse(dom.getAttribute("data-plugin"));
},
},
}],
],
toDOM(node) {
return ['div', { 'data-plugin': JSON.stringify(node.attrs) }, plugin.get('label')];
return [
"div",
{ "data-plugin": JSON.stringify(node.attrs) },
plugin.get("label"),
];
},
});
});
@ -83,8 +94,8 @@ function schemaWithPlugins(schema, plugins) {
function createSerializer(schema, plugins) {
const serializer = Object.create(defaultMarkdownSerializer);
plugins.forEach((plugin) => {
serializer.nodes[`plugin_${ plugin.get('id') }`] = (state, node) => {
const toBlock = plugin.get('toBlock');
serializer.nodes[`plugin_${ plugin.get("id") }`] = (state, node) => {
const toBlock = plugin.get("toBlock");
state.write(`${ toBlock.call(plugin, node.attrs) }\n\n`);
};
});
@ -159,17 +170,31 @@ export default class Editor extends Component {
const { schema, selection } = state;
if (selection.from === selection.to) {
const { $from } = selection;
if ($from.parent && $from.parent.type === schema.nodes.paragraph && $from.parent.textContent === '') {
if (
$from.parent &&
$from.parent.type === schema.nodes.paragraph &&
$from.parent.textContent === ""
) {
const pos = this.view.coordsAtPos(selection.from);
const editorPos = this.view.content.getBoundingClientRect();
const selectionPosition = { top: pos.top - editorPos.top, left: pos.left - editorPos.left };
const selectionPosition = {
top: pos.top - editorPos.top,
left: pos.left - editorPos.left,
};
this.setState({ selectionPosition });
} else {
this.setState({ showToolbar: false, showBlockMenu: false });
}
} else {
const pos = this.view.coordsAtPos(selection.from);
const editorPos = this.view.content.getBoundingClientRect();
const selectionPosition = { top: pos.top - editorPos.top, left: pos.left - editorPos.left };
this.setState({ selectionPosition });
const selectionPosition = {
top: pos.top - editorPos.top,
left: pos.left - editorPos.left,
};
this.setState({ selectionPosition });
}
};
@ -177,26 +202,24 @@ export default class Editor extends Component {
this.ref = ref;
};
handleHeader = level => (
() => {
const { schema } = this.state;
const state = this.view.state;
const { $from, to, node } = state.selection;
let nodeType = schema.nodes.heading;
let attrs = { level };
let inHeader = node && node.hasMarkup(nodeType, attrs);
if (!inHeader) {
inHeader = to <= $from.end() && $from.parent.hasMarkup(nodeType, attrs);
}
if (inHeader) {
nodeType = schema.nodes.paragraph;
attrs = {};
}
const command = setBlockType(nodeType, { level });
command(state, this.handleAction);
handleHeader = level => () => {
const { schema } = this.state;
const state = this.view.state;
const { $from, to, node } = state.selection;
let nodeType = schema.nodes.heading;
let attrs = { level };
let inHeader = node && node.hasMarkup(nodeType, attrs);
if (!inHeader) {
inHeader = to <= $from.end() && $from.parent.hasMarkup(nodeType, attrs);
}
);
if (inHeader) {
nodeType = schema.nodes.paragraph;
attrs = {};
}
const command = setBlockType(nodeType, { level });
command(state, this.handleAction);
};
handleBold = () => {
const command = toggleMark(this.state.schema.marks.strong);
@ -213,14 +236,20 @@ export default class Editor extends Component {
if (!markActive(this.view.state, this.state.schema.marks.link)) {
url = prompt('Link URL:'); // eslint-disable-line no-alert
}
const command = toggleMark(this.state.schema.marks.link, { href: url ? processUrl(url) : null });
const command = toggleMark(this.state.schema.marks.link, {
href: url ? processUrl(url) : null,
});
command(this.view.state, this.handleAction);
};
handlePluginSubmit = (plugin, data) => {
const { schema } = this.state;
const nodeType = schema.nodes[`plugin_${ plugin.get('id') }`];
this.view.props.onAction(this.view.state.tr.replaceSelectionWith(nodeType.create(data.toJS())).action());
const nodeType = schema.nodes[`plugin_${ plugin.get("id") }`];
this.view.props.onAction(
this.view.state.tr
.replaceSelectionWith(nodeType.create(data.toJS()))
.action()
);
};
handleDragEnter = (e) => {
@ -248,31 +277,40 @@ export default class Editor extends Component {
if (e.dataTransfer.files && e.dataTransfer.files.length) {
Array.from(e.dataTransfer.files).forEach((file) => {
createAssetProxy(file.name, file)
.then((assetProxy) => {
createAssetProxy(file.name, file).then((assetProxy) => {
this.props.onAddAsset(assetProxy);
if (file.type.split('/')[0] === 'image') {
if (file.type.split("/")[0] === "image") {
nodes.push(
schema.nodes.image.create({ src: assetProxy.public_path, alt: file.name })
schema.nodes.image.create({
src: assetProxy.public_path,
alt: file.name,
})
);
} else {
nodes.push(
schema.marks.link.create({ href: assetProxy.public_path, title: file.name })
schema.marks.link.create({
href: assetProxy.public_path,
title: file.name,
})
);
}
});
});
} else {
nodes.push(schema.nodes.paragraph.create({}, e.dataTransfer.getData('text/plain')));
nodes.push(
schema.nodes.paragraph.create({}, e.dataTransfer.getData("text/plain"))
);
}
nodes.forEach((node) => {
this.view.props.onAction(this.view.state.tr.replaceSelectionWith(node).action());
this.view.props.onAction(
this.view.state.tr.replaceSelectionWith(node).action()
);
});
};
handleToggle = () => {
this.props.onMode('raw');
this.props.onMode("raw");
};
render() {
@ -283,36 +321,38 @@ export default class Editor extends Component {
classNames.push(styles.dragging);
}
return (<div
className={classNames.join(' ')}
onDragEnter={this.handleDragEnter}
onDragLeave={this.handleDragLeave}
onDragOver={this.handleDragOver}
onDrop={this.handleDrop}
>
<Sticky
className={styles.editorControlBar}
classNameActive={styles.editorControlBarSticky}
fillContainerWidth
return (
<div
className={classNames.join(' ')}
onDragEnter={this.handleDragEnter}
onDragLeave={this.handleDragLeave}
onDragOver={this.handleDragOver}
onDrop={this.handleDrop}
>
<Toolbar
selectionPosition={selectionPosition}
onH1={this.handleHeader(1)}
onH2={this.handleHeader(2)}
onBold={this.handleBold}
onItalic={this.handleItalic}
onLink={this.handleLink}
onToggleMode={this.handleToggle}
plugins={plugins}
onSubmit={this.handlePluginSubmit}
onAddAsset={onAddAsset}
onRemoveAsset={onRemoveAsset}
getAsset={getAsset}
/>
</Sticky>
<div ref={this.handleRef} />
<div className={styles.shim} />
</div>);
<Sticky
className={styles.editorControlBar}
classNameActive={styles.editorControlBarSticky}
fillContainerWidth
>
<Toolbar
selectionPosition={selectionPosition}
onH1={this.handleHeader(1)}
onH2={this.handleHeader(2)}
onBold={this.handleBold}
onItalic={this.handleItalic}
onLink={this.handleLink}
onToggleMode={this.handleToggle}
plugins={plugins}
onSubmit={this.handlePluginSubmit}
onAddAsset={onAddAsset}
onRemoveAsset={onRemoveAsset}
getAsset={getAsset}
/>
</Sticky>
<div ref={this.handleRef} />
<div className={styles.shim} />
</div>
);
}
}

View File

@ -2,256 +2,90 @@
/*
Based closely on
https://github.com/ProseMirror/prosemirror-markdown/blob/master/src/from_markdown.js
Adds a bit of logic allowing editor plugins to hook into the parsing.
*/
const markdownit = require("markdown-it")
import Remark from "remark";
const visit = require('unist-util-visit')
const {Mark} = require("prosemirror-model")
function maybeMerge(a, b) {
if (a.isText && b.isText && Mark.sameSet(a.marks, b.marks))
return a.copy(a.text + b.text)
}
let schema
function pluginHandler(schema, plugins) {
return (type, attrs, content) => {
if (type.name === 'paragraph' && content.length === 1 && content[0].type.name === 'text') {
const text = content[0].text;
const plugin = plugins.find(plugin => plugin.get('pattern').test(text));
if (plugin) {
const nodeType = schema.nodes[`plugin_${ plugin.get('id') }`];
const data = plugin.get('fromBlock').call(plugin, text.match(plugin.get('pattern')));
return nodeType.create(data);
}
}
return null;
};
}
// Setup Remark.
const remark = new Remark({
commonmark: true,
footnotes: true,
pedantic: true,
});
// Object used to track the context of a running parse.
class MarkdownParseState {
constructor(schema, plugins, tokenHandlers) {
this.schema = schema
this.stack = [{type: schema.nodes.doc, content: []}]
this.marks = Mark.none
this.tokenHandlers = tokenHandlers
this.pluginHandler = pluginHandler(schema, plugins);
const processMdastNode = (node) => {
console.log('processMdastNode', node)
if (node.type === 'root') {
const content = node.children.map((childNode) => (
processMdastNode(childNode)
))
return schema.node('doc', {}, content)
}
top() {
return this.stack[this.stack.length - 1]
}
push(elt) {
if (this.stack.length) this.top().content.push(elt)
}
// : (string)
// Adds the given text to the current position in the document,
// using the current marks as styling.
addText(text) {
if (!text) return
let nodes = this.top().content, last = nodes[nodes.length - 1]
let node = this.schema.text(text, this.marks), merged
if (last && (merged = maybeMerge(last, node))) nodes[nodes.length - 1] = merged
else nodes.push(node)
}
// : (Mark)
// Adds the given mark to the set of active marks.
openMark(mark) {
this.marks = mark.addToSet(this.marks)
}
// : (Mark)
// Removes the given mark from the set of active marks.
closeMark(mark) {
this.marks = mark.removeFromSet(this.marks)
}
parseTokens(toks) {
for (let i = 0; i < toks.length; i++) {
let tok = toks[i]
let handler = this.tokenHandlers[tok.type]
if (!handler)
throw new Error("Token type `" + tok.type + "` not supported by Markdown parser")
handler(this, tok)
}
}
// : (NodeType, ?Object, ?[Node]) → ?Node
// Add a node at the current position.
addNode(type, attrs, content) {
const node = this.pluginHandler(type, attrs, content) || type.createAndFill(attrs, content, this.marks);
if (!node) return null
this.push(node)
return node
}
// : (NodeType, ?Object)
// Wrap subsequent content in a node of the given type.
openNode(type, attrs) {
this.stack.push({type: type, attrs: attrs, content: []})
}
// : () → ?Node
// Close and return the node that is currently on top of the stack.
closeNode() {
if (this.marks.length) this.marks = Mark.none
let info = this.stack.pop()
return this.addNode(info.type, info.attrs, info.content)
}
}
function attrs(given, token) {
return given instanceof Function ? given(token) : given
}
// Code content is represented as a single token with a `content`
// property in Markdown-it.
function noOpenClose(type) {
return type == "code_inline" || type == "code_block" || type == "fence"
}
function withoutTrailingNewline(str) {
return str[str.length - 1] == "\n" ? str.slice(0, str.length - 1) : str
}
function tokenHandlers(schema, tokens) {
let handlers = Object.create(null)
for (let type in tokens) {
let spec = tokens[type]
if (spec.block) {
let nodeType =schema.nodeType(spec.block);
if (noOpenClose(type)) {
handlers[type] = (state, tok) => {
state.openNode(nodeType, attrs(spec.attrs, tok))
state.addText(withoutTrailingNewline(tok.content))
state.closeNode()
}
} else {
handlers[type + "_open"] = (state, tok) => state.openNode(nodeType, attrs(spec.attrs, tok))
handlers[type + "_close"] = state => state.closeNode()
}
} else if (spec.node) {
let nodeType = schema.nodeType(spec.node)
handlers[type] = (state, tok) => state.addNode(nodeType, attrs(spec.attrs, tok))
} else if (spec.mark) {
let markType = schema.marks[spec.mark]
if (noOpenClose(type)) {
handlers[type] = (state, tok) => {
state.openMark(markType.create(attrs(spec.attrs, tok)))
state.addText(withoutTrailingNewline(tok.content))
state.closeMark(markType)
}
} else {
handlers[type + "_open"] = (state, tok) => state.openMark(markType.create(attrs(spec.attrs, tok)))
handlers[type + "_close"] = state => state.closeMark(markType)
}
/***
* Block nodes
***/
if (node.type === 'heading') {
const content = node.children.map((childNode) => (
processMdastNode(childNode)
))
console.log(content)
return schema.node('heading', { level: node.depth }, content)
} else if (node.type === 'paragraph') {
const content = node.children.map((childNode) => (
processMdastNode(childNode)
))
return schema.node('paragraph', {}, content)
} else if (node.type === 'list') {
const content = node.children.map((childNode) => (
processMdastNode(childNode)
))
if (node.ordered) {
return schema.node('ordered_list', { tight: true, order: 1 }, content)
} else {
throw new RangeError("Unrecognized parsing spec " + JSON.stringify(spec))
return schema.node('bullet_list', {}, content)
}
} else if (node.type === 'listItem') {
const content = node.children.map((childNode) => (
processMdastNode(childNode)
))
return schema.node('list_item', {}, content)
} else if (node.type === 'thematicBreak') {
return schema.node('horizontal_rule')
} else if (node.type === 'break') {
return schema.node('hard_break')
} else if (node.type === 'image') {
return schema.node('image', { src: node.url, alt: node.alt })
}
/***
* end block items
***/
// Inline
if (node.type === 'text') {
console.log('text value', node.value)
return schema.text(node.value)
}
handlers.text = (state, tok) => state.addText(tok.content)
handlers.inline = (state, tok) => state.parseTokens(tok.children)
handlers.softbreak = state => state.addText("\n")
return handlers
return doc
}
// ;; A configuration of a Markdown parser. Such a parser uses
// [markdown-it](https://github.com/markdown-it/markdown-it) to
// tokenize a file, and then runs the custom rules it is given over
// the tokens to create a ProseMirror document tree.
class MarkdownParser {
// :: (Schema, MarkdownIt, Object)
// Create a parser with the given configuration. You can configure
// the markdown-it parser to parse the dialect you want, and provide
// a description of the ProseMirror entities those tokens map to in
// the `tokens` object, which maps token names to descriptions of
// what to do with them. Such a description is an object, and may
// have the following properties:
//
// **`node`**`: ?string`
// : This token maps to a single node, whose type can be looked up
// in the schema under the given name. Exactly one of `node`,
// `block`, or `mark` must be set.
//
// **`block`**`: ?string`
// : This token comes in `_open` and `_close` variants (which are
// appended to the base token name provides a the object
// property), and wraps a block of content. The block should be
// wrapped in a node of the type named to by the property's
// value.
//
// **`mark`**`: ?string`
// : This token also comes in `_open` and `_close` variants, but
// should add a mark (named by the value) to its content, rather
// than wrapping it in a node.
//
// **`attrs`**`: ?union<Object, (MarkdownToken) → Object>`
// : If the mark or node to be created needs attributes, they can
// be either given directly, or as a function that takes a
// [markdown-it
// token](https://markdown-it.github.io/markdown-it/#Token) and
// returns an attribute object.
constructor(schema, plugins, tokenizer, tokens) {
// :: Object The value of the `tokens` object used to construct
// this parser. Can be useful to copy and modify to base other
// parsers on.
this.tokens = tokens
this.schema = schema
this.tokenizer = tokenizer
this.plugins = plugins
this.tokenHandlers = tokenHandlers(schema, tokens)
}
// :: (string) → Node
// Parse a string as [CommonMark](http://commonmark.org/) markup,
// and create a ProseMirror document as prescribed by this parser's
// rules.
parse(text) {
let state = new MarkdownParseState(this.schema, this.plugins, this.tokenHandlers), doc
state.parseTokens(this.tokenizer.parse(text, {}))
do { doc = state.closeNode() } while (state.stack.length)
return doc
}
const compileMarkdownToProseMirror = (src) => {
console.log(src)
const mdast = remark.parse(src)
console.log(mdast)
const doc = processMdastNode(mdast)
console.log(doc.content)
return doc
}
// :: MarkdownParser
// A parser parsing unextended [CommonMark](http://commonmark.org/),
// without inline HTML, and producing a document in the basic schema.
export default function createMarkdownParser(schema, plugins) {
const tokens = {
blockquote: {block: "blockquote"},
paragraph: {block: "paragraph"},
list_item: {block: "list_item"},
// Note - we force lists to be tight here, while that's not ProseMirror's default
// The default behavior means list elements always have a `p` inside, and we want
// to avoid tha.
bullet_list: {block: "bullet_list", attrs: tok => ({tight: true})},
ordered_list: {block: "ordered_list", attrs: tok => ({tight: true, order: +tok.attrGet("order") || 1})},
heading: {block: "heading", attrs: tok => ({level: +tok.tag.slice(1)})},
code_block: {block: "code_block"},
fence: {block: "code_block"},
hr: {node: "horizontal_rule"},
image: {node: "image", attrs: tok => ({
src: tok.attrGet("src"),
title: tok.attrGet("title") || null,
alt: tok.children[0] && tok.children[0].content || null
})},
hardbreak: {node: "hard_break"},
em: {mark: "em"},
strong: {mark: "strong"},
link: {mark: "link", attrs: tok => ({
href: tok.attrGet("href"),
title: tok.attrGet("title") || null
})},
code_inline: {mark: "code"}
};
return new MarkdownParser(schema, plugins, markdownit("commonmark", {html: false}), tokens);
module.exports = (s, plugins) => {
//console.log(s)
//console.log(s.nodes.code_block.create({ params: { language: 'javascript' } }))
schema = s
return compileMarkdownToProseMirror
}