From 0eb109cb738d7534407268d3be2913f074d75b93 Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Thu, 2 Mar 2017 12:02:55 -0800 Subject: [PATCH] Convert markdown-prosemirror parser/compiler to Remark --- package.json | 1 + src/components/MarkupItReactRenderer/index.js | 4 +- .../__snapshots__/parser.spec.js.snap | 324 ++++++++++++++++++ .../VisualEditor/__tests__/parser.spec.js | 91 +++++ .../VisualEditor/index.js | 190 ++++++---- .../VisualEditor/parser.js | 306 ++++------------- 6 files changed, 603 insertions(+), 313 deletions(-) create mode 100644 src/components/Widgets/MarkdownControlElements/VisualEditor/__tests__/__snapshots__/parser.spec.js.snap create mode 100644 src/components/Widgets/MarkdownControlElements/VisualEditor/__tests__/parser.spec.js diff --git a/package.json b/package.json index 0cd64b56..87016e79 100644 --- a/package.json +++ b/package.json @@ -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" }, diff --git a/src/components/MarkupItReactRenderer/index.js b/src/components/MarkupItReactRenderer/index.js index 03d46637..462170c5 100644 --- a/src/components/MarkupItReactRenderer/index.js +++ b/src/components/MarkupItReactRenderer/index.js @@ -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
; // eslint-disable-line react/no-danger } diff --git a/src/components/Widgets/MarkdownControlElements/VisualEditor/__tests__/__snapshots__/parser.spec.js.snap b/src/components/Widgets/MarkdownControlElements/VisualEditor/__tests__/__snapshots__/parser.spec.js.snap new file mode 100644 index 00000000..e434e6da --- /dev/null +++ b/src/components/Widgets/MarkdownControlElements/VisualEditor/__tests__/__snapshots__/parser.spec.js.snap @@ -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", +} +`; diff --git a/src/components/Widgets/MarkdownControlElements/VisualEditor/__tests__/parser.spec.js b/src/components/Widgets/MarkdownControlElements/VisualEditor/__tests__/parser.spec.js new file mode 100644 index 00000000..cd188b8e --- /dev/null +++ b/src/components/Widgets/MarkdownControlElements/VisualEditor/__tests__/parser.spec.js @@ -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(); + }); +}); diff --git a/src/components/Widgets/MarkdownControlElements/VisualEditor/index.js b/src/components/Widgets/MarkdownControlElements/VisualEditor/index.js index a00620ca..775045c6 100644 --- a/src/components/Widgets/MarkdownControlElements/VisualEditor/index.js +++ b/src/components/Widgets/MarkdownControlElements/VisualEditor/index.js @@ -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 (
- - - -
-
-
); + + + +
+
+
+ ); } } diff --git a/src/components/Widgets/MarkdownControlElements/VisualEditor/parser.js b/src/components/Widgets/MarkdownControlElements/VisualEditor/parser.js index 6234056f..5dd54dac 100644 --- a/src/components/Widgets/MarkdownControlElements/VisualEditor/parser.js +++ b/src/components/Widgets/MarkdownControlElements/VisualEditor/parser.js @@ -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` - // : 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 }