From 8bb18452e80013067764ef738eeaab834e63507b Mon Sep 17 00:00:00 2001 From: Shawn Erquhart Date: Thu, 25 May 2017 13:51:43 -0400 Subject: [PATCH] implement initial unified/remark preview update --- package.json | 2 +- src/components/MarkupItReactRenderer/index.js | 92 ++++++++++++++++--- .../VisualEditor/__tests__/parser.spec.js | 3 +- .../VisualEditor/parser.js | 3 +- 4 files changed, 81 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index 09f91261..cbfddc32 100644 --- a/package.json +++ b/package.json @@ -157,6 +157,7 @@ "redux-notifications": "^2.1.1", "redux-optimist": "^0.0.2", "redux-thunk": "^1.0.3", + "rehype-parse": "^3.1.0", "rehype-stringify": "^3.0.0", "remark-html": "^6.0.0", "remark-parse": "^3.0.1", @@ -168,7 +169,6 @@ "slug": "^0.9.1", "textarea-caret-position": "^0.1.1", "unified": "^6.1.4", - "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 320a0f75..b152187d 100644 --- a/src/components/MarkupItReactRenderer/index.js +++ b/src/components/MarkupItReactRenderer/index.js @@ -1,30 +1,92 @@ import React, { PropTypes } from "react"; +import { renderToStaticMarkup } from 'react-dom/server'; +import { Map } from 'immutable'; import unified from 'unified'; import markdown from 'remark-parse'; import rehype from 'remark-rehype'; +import parseHtml from 'rehype-parse'; import html from 'rehype-stringify'; import registry from "../../lib/registry"; -export default class MarkupItReactRenderer extends React.Component { - constructor(props) { - super(props); - this.plugins = {}; - // TODO add back support for this. - registry.getEditorComponents().forEach((component) => { - this.plugins[component.get("id")] = component; - }); +const getPlugins = () => registry.getEditorComponents(); + +const renderEditorPlugins = ({ getAsset }) => { + return tree => { + const result = renderEditorPluginsProcessor(tree, getAsset); + return result; + }; +}; + +const renderEditorPluginsProcessor = (node, getAsset) => { + + if (node.children) { + + node.children = node.children.map(n => renderEditorPluginsProcessor(n, getAsset)); + + // Handle externally defined plugins (they'll be wrapped in paragraphs) + if (node.tagName === 'p' && node.children.length === 1 && node.children[0].type === 'text') { + const value = node.children[0].value; + const plugin = getPlugins().find(plugin => plugin.get('pattern').test(value)); + if (plugin) { + const data = plugin.get('fromBlock')(value.match(plugin.get('pattern'))); + const preview = plugin.get('toPreview')(data); + const output = typeof preview === 'string' ? +
: + preview; + + const result = unified() + .use(parseHtml, { fragment: true }) + .parse(renderToStaticMarkup(output)); + + return result.children[0]; + } + } } - render() { - const doc = unified() - .use(markdown, { commonmark: true, footnotes: true, pedantic: true }) - .use(rehype, { allowDangerousHTML: true }) - .use(html, { allowDangerousHTML: true }) - .processSync(this.props.value); - return
; // eslint-disable-line react/no-danger + // 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.tagName === 'img') { + const { src, alt } = node.properties; + + // 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 plugin = getPlugins().get('image'); + if (plugin) { + const matches = [ , alt, src ]; + const data = plugin.get('fromBlock')(matches); + const extendedData = { ...data, image: getAsset(data.image).toString() }; + const preview = plugin.get('toPreview')(extendedData); + const output = typeof preview === 'string' ? +
: + preview; + + const result = unified() + .use(parseHtml, { fragment: true }) + .parse(renderToStaticMarkup(output)); + + return result.children[0]; + } } + + return node; +}; + +const MarkupItReactRenderer = ({ value, getAsset }) => { + const doc = unified() + .use(markdown, { commonmark: true, footnotes: true, pedantic: true }) + .use(rehype, { allowDangerousHTML: true }) + .use(renderEditorPlugins, { getAsset }) + .use(html, { allowDangerousHTML: true }) + .processSync(value); + + return
; // eslint-disable-line react/no-danger } +export default MarkupItReactRenderer; + MarkupItReactRenderer.propTypes = { value: PropTypes.string, + getAsset: PropTypes.func.isRequired, }; diff --git a/src/components/Widgets/MarkdownControlElements/VisualEditor/__tests__/parser.spec.js b/src/components/Widgets/MarkdownControlElements/VisualEditor/__tests__/parser.spec.js index 5b8fff61..594f2a32 100644 --- a/src/components/Widgets/MarkdownControlElements/VisualEditor/__tests__/parser.spec.js +++ b/src/components/Widgets/MarkdownControlElements/VisualEditor/__tests__/parser.spec.js @@ -1,8 +1,7 @@ import { fromJS } from 'immutable'; import { Schema } from "prosemirror-model"; import { schema } from "prosemirror-markdown"; - -const makeParser = require("../parser"); +import makeParser from '../parser'; const testSchema = new Schema({ nodes: schema.nodeSpec, diff --git a/src/components/Widgets/MarkdownControlElements/VisualEditor/parser.js b/src/components/Widgets/MarkdownControlElements/VisualEditor/parser.js index 98fa1ad9..96c8c3af 100644 --- a/src/components/Widgets/MarkdownControlElements/VisualEditor/parser.js +++ b/src/components/Widgets/MarkdownControlElements/VisualEditor/parser.js @@ -6,7 +6,6 @@ import unified from 'unified'; import markdown from 'remark-parse'; -import visit from 'unist-util-visit'; import { Mark } from 'prosemirror-model'; let schema; @@ -133,6 +132,8 @@ const processMdastNode = node => { activeMarks = mark.removeFromSet(activeMarks); return; } + + return node; }; const compileMarkdownToProseMirror = src => {