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 index 4c22ae72..74a86aab 100644 --- a/src/components/Widgets/MarkdownControlElements/VisualEditor/__tests__/__snapshots__/parser.spec.js.snap +++ b/src/components/Widgets/MarkdownControlElements/VisualEditor/__tests__/__snapshots__/parser.spec.js.snap @@ -1075,6 +1075,26 @@ Object { } `; +exports[`Compile markdown to Prosemirror document structure should compile plugins 1`] = ` +Object { + "content": Array [ + Object { + "type": "paragraph", + }, + Object { + "content": Array [ + Object { + "text": "{{< test >}}", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "doc", +} +`; + exports[`Compile markdown to Prosemirror document structure should compile simple markdown 1`] = ` Object { "content": Array [ diff --git a/src/components/Widgets/MarkdownControlElements/VisualEditor/__tests__/parser.spec.js b/src/components/Widgets/MarkdownControlElements/VisualEditor/__tests__/parser.spec.js index 07972fa1..5b8fff61 100644 --- a/src/components/Widgets/MarkdownControlElements/VisualEditor/__tests__/parser.spec.js +++ b/src/components/Widgets/MarkdownControlElements/VisualEditor/__tests__/parser.spec.js @@ -1,3 +1,4 @@ +import { fromJS } from 'immutable'; import { Schema } from "prosemirror-model"; import { schema } from "prosemirror-markdown"; @@ -7,7 +8,51 @@ const testSchema = new Schema({ nodes: schema.nodeSpec, marks: schema.markSpec, }); -const parser = makeParser(testSchema); + +// Temporary plugins test, uses preloaded plugins from ../parser +// TODO: make the parser more testable +const testPlugins = fromJS([ + { + label: 'Image', + id: 'image', + fromBlock: match => match && { + image: match[2], + alt: match[1], + }, + toBlock: data => `![${ data.alt }](${ data.image })`, + toPreview: data => {data.alt}, + pattern: /^!\[([^\]]+)]\(([^)]+)\)$/, + fields: [{ + label: 'Image', + name: 'image', + widget: 'image', + }, { + label: 'Alt Text', + name: 'alt', + }], + }, + { + id: "youtube", + label: "Youtube", + fields: [{name: 'id', label: 'Youtube Video ID'}], + pattern: /^{{<\s?youtube (\S+)\s?>}}/, + fromBlock: function(match) { + return { + id: match[1] + }; + }, + toBlock: function(obj) { + return '{{< youtube ' + obj.id + ' >}}'; + }, + toPreview: function(obj) { + return ( + 'Youtube Video' + ); + } + }, +]); + +const parser = makeParser(testSchema, testPlugins); describe("Compile markdown to Prosemirror document structure", () => { it("should compile simple markdown", () => { @@ -127,6 +172,15 @@ How far is it to [Google](https://google.com) land? expect(parser(value)).toMatchSnapshot(); }); + it("should compile plugins", () => { + const value = ` +![test](test.png) + +{{< test >}} +`; + expect(parser(value)).toMatchSnapshot(); + }); + it("should compile kitchen sink example", () => { const value = ` # An exhibit of Markdown diff --git a/src/components/Widgets/MarkdownControlElements/VisualEditor/parser.js b/src/components/Widgets/MarkdownControlElements/VisualEditor/parser.js index fe015ff8..a4605fcf 100644 --- a/src/components/Widgets/MarkdownControlElements/VisualEditor/parser.js +++ b/src/components/Widgets/MarkdownControlElements/VisualEditor/parser.js @@ -9,6 +9,7 @@ const visit = require("unist-util-visit"); const { Mark } = require("prosemirror-model"); let schema; +let plugins let activeMarks = Mark.none; let textsArray = []; @@ -36,6 +37,38 @@ const processMdastNode = node => { textsArray = []; return pNode; } else if (node.type === "paragraph") { + + // TODO: improve plugin handling + + // Handle externally defined plugins (they'll be wrapped in paragraphs) + if (node.children.length === 1 && node.children[0].type === 'text') { + const value = node.children[0].value; + const plugin = plugins.find(plugin => plugin.get('pattern').test(value)); + if (plugin) { + const nodeType = schema.nodes[`plugin_${plugin.get('id')}`]; + const data = plugin.get('fromBlock').call(plugin, value.match(plugin.get('pattern'))); + return nodeType.create(data); + } + } + + // Handle the internally defined image plugin. At this point the token has + // already been parsed as an image by Remark, so we have to catch it by + // checking for the 'image' type. + if (node.children.length === 1 && node.children[0].type === 'image') { + const { url, alt } = node.children[0]; + + // Until we improve the editor components API for built in components, + // we'll mock the result of String.prototype.match to pass in to the image + // plugin's fromBlock method. + const matches = [ , alt, url ]; + const plugin = plugins.find(plugin => plugin.id === 'image'); + if (plugin) { + const nodeType = schema.nodes.plugin_image; + const data = plugin.get('fromBlock').call(plugin, matches); + return nodeType.create(data); + } + } + node.children.forEach(childNode => processMdastNode(childNode)); const pNode = schema.node("paragraph", {}, textsArray); textsArray = []; @@ -121,9 +154,10 @@ const compileMarkdownToProseMirror = src => { return doc; }; -module.exports = (s, plugins) => { +module.exports = (s, p) => { //console.log(s) //console.log(s.nodes.code_block.create({ params: { language: 'javascript' } })) schema = s; + plugins = p; return compileMarkdownToProseMirror; };