implement initial unified/remark preview update

This commit is contained in:
Shawn Erquhart 2017-05-25 13:51:43 -04:00
parent 5048c7ca1d
commit 8bb18452e8
4 changed files with 81 additions and 19 deletions

View File

@ -157,6 +157,7 @@
"redux-notifications": "^2.1.1", "redux-notifications": "^2.1.1",
"redux-optimist": "^0.0.2", "redux-optimist": "^0.0.2",
"redux-thunk": "^1.0.3", "redux-thunk": "^1.0.3",
"rehype-parse": "^3.1.0",
"rehype-stringify": "^3.0.0", "rehype-stringify": "^3.0.0",
"remark-html": "^6.0.0", "remark-html": "^6.0.0",
"remark-parse": "^3.0.1", "remark-parse": "^3.0.1",
@ -168,7 +169,6 @@
"slug": "^0.9.1", "slug": "^0.9.1",
"textarea-caret-position": "^0.1.1", "textarea-caret-position": "^0.1.1",
"unified": "^6.1.4", "unified": "^6.1.4",
"unist-util-visit": "^1.1.1",
"uuid": "^2.0.3", "uuid": "^2.0.3",
"whatwg-fetch": "^1.0.0" "whatwg-fetch": "^1.0.0"
}, },

View File

@ -1,30 +1,92 @@
import React, { PropTypes } from "react"; import React, { PropTypes } from "react";
import { renderToStaticMarkup } from 'react-dom/server';
import { Map } from 'immutable';
import unified from 'unified'; import unified from 'unified';
import markdown from 'remark-parse'; import markdown from 'remark-parse';
import rehype from 'remark-rehype'; import rehype from 'remark-rehype';
import parseHtml from 'rehype-parse';
import html from 'rehype-stringify'; import html from 'rehype-stringify';
import registry from "../../lib/registry"; import registry from "../../lib/registry";
export default class MarkupItReactRenderer extends React.Component { const getPlugins = () => registry.getEditorComponents();
constructor(props) {
super(props); const renderEditorPlugins = ({ getAsset }) => {
this.plugins = {}; return tree => {
// TODO add back support for this. const result = renderEditorPluginsProcessor(tree, getAsset);
registry.getEditorComponents().forEach((component) => { return result;
this.plugins[component.get("id")] = component; };
}); };
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' ?
<div dangerouslySetInnerHTML={{ __html: preview }}/> :
preview;
const result = unified()
.use(parseHtml, { fragment: true })
.parse(renderToStaticMarkup(output));
return result.children[0];
}
}
} }
render() { // Handle the internally defined image plugin. At this point the token has
const doc = unified() // already been parsed as an image by Remark, so we have to catch it by
.use(markdown, { commonmark: true, footnotes: true, pedantic: true }) // checking for the 'image' type.
.use(rehype, { allowDangerousHTML: true }) if (node.tagName === 'img') {
.use(html, { allowDangerousHTML: true }) const { src, alt } = node.properties;
.processSync(this.props.value);
return <div dangerouslySetInnerHTML={{ __html: doc }} />; // eslint-disable-line react/no-danger // 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' ?
<div dangerouslySetInnerHTML={{ __html: preview }}/> :
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 <div dangerouslySetInnerHTML={{ __html: doc }} />; // eslint-disable-line react/no-danger
} }
export default MarkupItReactRenderer;
MarkupItReactRenderer.propTypes = { MarkupItReactRenderer.propTypes = {
value: PropTypes.string, value: PropTypes.string,
getAsset: PropTypes.func.isRequired,
}; };

View File

@ -1,8 +1,7 @@
import { fromJS } from 'immutable'; import { fromJS } from 'immutable';
import { Schema } from "prosemirror-model"; import { Schema } from "prosemirror-model";
import { schema } from "prosemirror-markdown"; import { schema } from "prosemirror-markdown";
import makeParser from '../parser';
const makeParser = require("../parser");
const testSchema = new Schema({ const testSchema = new Schema({
nodes: schema.nodeSpec, nodes: schema.nodeSpec,

View File

@ -6,7 +6,6 @@
import unified from 'unified'; import unified from 'unified';
import markdown from 'remark-parse'; import markdown from 'remark-parse';
import visit from 'unist-util-visit';
import { Mark } from 'prosemirror-model'; import { Mark } from 'prosemirror-model';
let schema; let schema;
@ -133,6 +132,8 @@ const processMdastNode = node => {
activeMarks = mark.removeFromSet(activeMarks); activeMarks = mark.removeFromSet(activeMarks);
return; return;
} }
return node;
}; };
const compileMarkdownToProseMirror = src => { const compileMarkdownToProseMirror = src => {