diff --git a/package.json b/package.json
index 4ea7e840..b155b71b 100644
--- a/package.json
+++ b/package.json
@@ -158,7 +158,10 @@
"redux-optimist": "^0.0.2",
"redux-thunk": "^1.0.3",
"rehype-parse": "^3.1.0",
+ "rehype-raw": "^1.0.0",
+ "rehype-react": "^3.0.0",
"rehype-remark": "^2.0.0",
+ "rehype-sanitize": "^2.0.0",
"rehype-stringify": "^3.0.0",
"remark-html": "^6.0.0",
"remark-parse": "^3.0.1",
diff --git a/src/components/MarkupItReactRenderer/index.js b/src/components/MarkupItReactRenderer/index.js
deleted file mode 100644
index f48f5888..00000000
--- a/src/components/MarkupItReactRenderer/index.js
+++ /dev/null
@@ -1,85 +0,0 @@
-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";
-
-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 : renderToStaticMarkup(preview)}
`;
- return unified().use(parseHtml, { fragment: true }).parse(output);
- }
- }
- }
-
- // 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/RawEditor/index.css b/src/components/Widgets/MarkdownControl/RawEditor/index.css
similarity index 100%
rename from src/components/Widgets/MarkdownControlElements/RawEditor/index.css
rename to src/components/Widgets/MarkdownControl/RawEditor/index.css
diff --git a/src/components/Widgets/MarkdownControlElements/RawEditor/index.js b/src/components/Widgets/MarkdownControl/RawEditor/index.js
similarity index 97%
rename from src/components/Widgets/MarkdownControlElements/RawEditor/index.js
rename to src/components/Widgets/MarkdownControl/RawEditor/index.js
index 4dba11fe..c83de244 100644
--- a/src/components/Widgets/MarkdownControlElements/RawEditor/index.js
+++ b/src/components/Widgets/MarkdownControl/RawEditor/index.js
@@ -3,6 +3,8 @@ import unified from 'unified';
import htmlToRehype from 'rehype-parse';
import rehypeToRemark from 'rehype-remark';
import remarkToMarkdown from 'remark-stringify';
+import rehypeSanitize from 'rehype-sanitize';
+import rehypeReparse from 'rehype-raw';
import CaretPosition from 'textarea-caret-position';
import TextareaAutosize from 'react-textarea-autosize';
import registry from '../../../../lib/registry';
@@ -25,9 +27,11 @@ function processUrl(url) {
function cleanupPaste(paste) {
return unified()
- .use(htmlToRehype)
+ .use(htmlToRehype, { fragment: true })
+ .use(rehypeSanitize)
+ .use(rehypeReparse)
.use(rehypeToRemark)
- .use(remarkToMarkdown)
+ .use(remarkToMarkdown, { commonmark: true, footnotes: true, pedantic: true })
.process(paste);
}
diff --git a/src/components/Widgets/MarkdownControlElements/Toolbar/Toolbar.css b/src/components/Widgets/MarkdownControl/Toolbar/Toolbar.css
similarity index 100%
rename from src/components/Widgets/MarkdownControlElements/Toolbar/Toolbar.css
rename to src/components/Widgets/MarkdownControl/Toolbar/Toolbar.css
diff --git a/src/components/Widgets/MarkdownControlElements/Toolbar/Toolbar.js b/src/components/Widgets/MarkdownControl/Toolbar/Toolbar.js
similarity index 100%
rename from src/components/Widgets/MarkdownControlElements/Toolbar/Toolbar.js
rename to src/components/Widgets/MarkdownControl/Toolbar/Toolbar.js
diff --git a/src/components/Widgets/MarkdownControlElements/Toolbar/ToolbarButton.css b/src/components/Widgets/MarkdownControl/Toolbar/ToolbarButton.css
similarity index 100%
rename from src/components/Widgets/MarkdownControlElements/Toolbar/ToolbarButton.css
rename to src/components/Widgets/MarkdownControl/Toolbar/ToolbarButton.css
diff --git a/src/components/Widgets/MarkdownControlElements/Toolbar/ToolbarButton.js b/src/components/Widgets/MarkdownControl/Toolbar/ToolbarButton.js
similarity index 100%
rename from src/components/Widgets/MarkdownControlElements/Toolbar/ToolbarButton.js
rename to src/components/Widgets/MarkdownControl/Toolbar/ToolbarButton.js
diff --git a/src/components/Widgets/MarkdownControlElements/Toolbar/ToolbarComponentsMenu.css b/src/components/Widgets/MarkdownControl/Toolbar/ToolbarComponentsMenu.css
similarity index 100%
rename from src/components/Widgets/MarkdownControlElements/Toolbar/ToolbarComponentsMenu.css
rename to src/components/Widgets/MarkdownControl/Toolbar/ToolbarComponentsMenu.css
diff --git a/src/components/Widgets/MarkdownControlElements/Toolbar/ToolbarComponentsMenu.js b/src/components/Widgets/MarkdownControl/Toolbar/ToolbarComponentsMenu.js
similarity index 100%
rename from src/components/Widgets/MarkdownControlElements/Toolbar/ToolbarComponentsMenu.js
rename to src/components/Widgets/MarkdownControl/Toolbar/ToolbarComponentsMenu.js
diff --git a/src/components/Widgets/MarkdownControlElements/Toolbar/ToolbarPluginForm.css b/src/components/Widgets/MarkdownControl/Toolbar/ToolbarPluginForm.css
similarity index 100%
rename from src/components/Widgets/MarkdownControlElements/Toolbar/ToolbarPluginForm.css
rename to src/components/Widgets/MarkdownControl/Toolbar/ToolbarPluginForm.css
diff --git a/src/components/Widgets/MarkdownControlElements/Toolbar/ToolbarPluginForm.js b/src/components/Widgets/MarkdownControl/Toolbar/ToolbarPluginForm.js
similarity index 100%
rename from src/components/Widgets/MarkdownControlElements/Toolbar/ToolbarPluginForm.js
rename to src/components/Widgets/MarkdownControl/Toolbar/ToolbarPluginForm.js
diff --git a/src/components/Widgets/MarkdownControlElements/Toolbar/ToolbarPluginFormControl.css b/src/components/Widgets/MarkdownControl/Toolbar/ToolbarPluginFormControl.css
similarity index 100%
rename from src/components/Widgets/MarkdownControlElements/Toolbar/ToolbarPluginFormControl.css
rename to src/components/Widgets/MarkdownControl/Toolbar/ToolbarPluginFormControl.css
diff --git a/src/components/Widgets/MarkdownControlElements/Toolbar/ToolbarPluginFormControl.js b/src/components/Widgets/MarkdownControl/Toolbar/ToolbarPluginFormControl.js
similarity index 100%
rename from src/components/Widgets/MarkdownControlElements/Toolbar/ToolbarPluginFormControl.js
rename to src/components/Widgets/MarkdownControl/Toolbar/ToolbarPluginFormControl.js
diff --git a/src/components/Widgets/MarkdownControlElements/VisualEditor/__tests__/__snapshots__/parser.spec.js.snap b/src/components/Widgets/MarkdownControl/VisualEditor/__tests__/__snapshots__/parser.spec.js.snap
similarity index 100%
rename from src/components/Widgets/MarkdownControlElements/VisualEditor/__tests__/__snapshots__/parser.spec.js.snap
rename to src/components/Widgets/MarkdownControl/VisualEditor/__tests__/__snapshots__/parser.spec.js.snap
diff --git a/src/components/Widgets/MarkdownControlElements/VisualEditor/__tests__/parser.spec.js b/src/components/Widgets/MarkdownControl/VisualEditor/__tests__/parser.spec.js
similarity index 100%
rename from src/components/Widgets/MarkdownControlElements/VisualEditor/__tests__/parser.spec.js
rename to src/components/Widgets/MarkdownControl/VisualEditor/__tests__/parser.spec.js
diff --git a/src/components/Widgets/MarkdownControlElements/VisualEditor/index.css b/src/components/Widgets/MarkdownControl/VisualEditor/index.css
similarity index 100%
rename from src/components/Widgets/MarkdownControlElements/VisualEditor/index.css
rename to src/components/Widgets/MarkdownControl/VisualEditor/index.css
diff --git a/src/components/Widgets/MarkdownControlElements/VisualEditor/index.js b/src/components/Widgets/MarkdownControl/VisualEditor/index.js
similarity index 96%
rename from src/components/Widgets/MarkdownControlElements/VisualEditor/index.js
rename to src/components/Widgets/MarkdownControl/VisualEditor/index.js
index a00620ca..15265c1d 100644
--- a/src/components/Widgets/MarkdownControlElements/VisualEditor/index.js
+++ b/src/components/Widgets/MarkdownControl/VisualEditor/index.js
@@ -11,6 +11,9 @@ import {
import { keymap } from 'prosemirror-keymap';
import { schema as markdownSchema, defaultMarkdownSerializer } from 'prosemirror-markdown';
import { baseKeymap, setBlockType, toggleMark } from 'prosemirror-commands';
+import unified from 'unified';
+import markdownToRemark from 'remark-parse';
+import remarkToMarkdown from 'remark-stringify';
import registry from '../../../../lib/registry';
import { createAssetProxy } from '../../../../valueObjects/AssetProxy';
import { buildKeymap } from './keymap';
@@ -147,7 +150,11 @@ export default class Editor extends Component {
const { serializer } = this.state;
const newState = this.view.state.applyAction(action);
const md = serializer.serialize(newState.doc);
- this.props.onChange(md);
+ const processedMarkdown = unified()
+ .use(markdownToRemark)
+ .use(remarkToMarkdown, { commonmark: true, footnotes: true, pedantic: true })
+ .processSync(md);
+ this.props.onChange(processedMarkdown.contents);
this.view.updateState(newState);
if (newState.selection !== this.state.selection) {
this.handleSelection(newState);
diff --git a/src/components/Widgets/MarkdownControlElements/VisualEditor/keymap.js b/src/components/Widgets/MarkdownControl/VisualEditor/keymap.js
similarity index 100%
rename from src/components/Widgets/MarkdownControlElements/VisualEditor/keymap.js
rename to src/components/Widgets/MarkdownControl/VisualEditor/keymap.js
diff --git a/src/components/Widgets/MarkdownControlElements/VisualEditor/markdownToProseMirror.js b/src/components/Widgets/MarkdownControl/VisualEditor/markdownToProseMirror.js
similarity index 100%
rename from src/components/Widgets/MarkdownControlElements/VisualEditor/markdownToProseMirror.js
rename to src/components/Widgets/MarkdownControl/VisualEditor/markdownToProseMirror.js
diff --git a/src/components/Widgets/MarkdownControlElements/VisualEditor/parser.js b/src/components/Widgets/MarkdownControl/VisualEditor/parser.js
similarity index 86%
rename from src/components/Widgets/MarkdownControlElements/VisualEditor/parser.js
rename to src/components/Widgets/MarkdownControl/VisualEditor/parser.js
index 7ef6e5a8..e06c4d2d 100644
--- a/src/components/Widgets/MarkdownControlElements/VisualEditor/parser.js
+++ b/src/components/Widgets/MarkdownControl/VisualEditor/parser.js
@@ -1,5 +1,5 @@
import unified from 'unified';
-import markdown from 'remark-parse';
+import remarkToMarkdown from 'remark-parse';
import { Mark } from 'prosemirror-model';
import markdownToProseMirror from './markdownToProseMirror';
@@ -12,7 +12,7 @@ const state = { activeMarks: Mark.none, textsArray: [] };
*/
function parser(src) {
const result = unified()
- .use(markdown, { commonmark: true, footnotes: true, pedantic: true })
+ .use(remarkToMarkdown, { commonmark: true, footnotes: true, pedantic: true })
.parse(src);
return unified()
diff --git a/src/components/Widgets/MarkdownControl.js b/src/components/Widgets/MarkdownControl/index.js
similarity index 80%
rename from src/components/Widgets/MarkdownControl.js
rename to src/components/Widgets/MarkdownControl/index.js
index 446890b4..a3c3b0ab 100644
--- a/src/components/Widgets/MarkdownControl.js
+++ b/src/components/Widgets/MarkdownControl/index.js
@@ -1,9 +1,8 @@
import React, { PropTypes } from 'react';
-import registry from '../../lib/registry';
-import RawEditor from './MarkdownControlElements/RawEditor';
-import VisualEditor from './MarkdownControlElements/VisualEditor';
-import { processEditorPlugins } from './richText';
-import { StickyContainer } from '../UI/Sticky/Sticky';
+import registry from '../../../lib/registry';
+import RawEditor from './RawEditor';
+import VisualEditor from './VisualEditor';
+import { StickyContainer } from '../../UI/Sticky/Sticky';
const MODE_STORAGE_KEY = 'cms.md-mode';
@@ -21,10 +20,6 @@ export default class MarkdownControl extends React.Component {
this.state = { mode: localStorage.getItem(MODE_STORAGE_KEY) || 'visual' };
}
- componentWillMount() {
- processEditorPlugins(registry.getEditorComponents());
- }
-
handleMode = (mode) => {
this.setState({ mode });
localStorage.setItem(MODE_STORAGE_KEY, mode);
diff --git a/src/components/Widgets/MarkdownControlElements/plugins.js b/src/components/Widgets/MarkdownControl/plugins.js
similarity index 100%
rename from src/components/Widgets/MarkdownControlElements/plugins.js
rename to src/components/Widgets/MarkdownControl/plugins.js
diff --git a/src/components/Widgets/MarkdownControlElements/RawEditor/prismMarkdown.js b/src/components/Widgets/MarkdownControlElements/RawEditor/prismMarkdown.js
deleted file mode 100644
index 1ea5e5d3..00000000
--- a/src/components/Widgets/MarkdownControlElements/RawEditor/prismMarkdown.js
+++ /dev/null
@@ -1,116 +0,0 @@
-const marks = {
- 'blockquote': {
- // > ...
- pattern: /^>(?:[\t ]*>)*/m,
- alias: 'punctuation'
- },
- 'code': [
- {
- // Prefixed by 4 spaces or 1 tab
- pattern: /^(?: {4}|\t).+/m,
- alias: 'keyword'
- },
- {
- // `code`
- // ``code``
- pattern: /``.+?``|`[^`\n]+`/,
- alias: 'keyword'
- }
- ],
- 'title': [
- {
- // title 1
- // =======
-
- // title 2
- // -------
- pattern: /\w+.*(?:\r?\n|\r)(?:==+|--+)/,
- alias: 'important',
- inside: {
- punctuation: /==+$|--+$/
- }
- },
- {
- // # title 1
- // ###### title 6
- pattern: /(^\s*)#+.+/m,
- lookbehind: true,
- alias: 'important',
- inside: {
- punctuation: /^#+|#+$/
- }
- }
- ],
- 'hr': {
- // ***
- // ---
- // * * *
- // -----------
- pattern: /(^\s*)([*-])([\t ]*\2){2,}(?=\s*$)/m,
- lookbehind: true,
- alias: 'punctuation'
- },
- 'list': {
- // * item
- // + item
- // - item
- // 1. item
- pattern: /(^\s*)(?:[*+-]|\d+\.)(?=[\t ].)/m,
- lookbehind: true,
- alias: 'punctuation'
- },
- 'url-reference': {
- // [id]: http://example.com "Optional title"
- // [id]: http://example.com 'Optional title'
- // [id]: http://example.com (Optional title)
- // [id]: "Optional title"
- pattern: /!?\[[^\]]+\]:[\t ]+(?:\S+|<(?:\\.|[^>\\])+>)(?:[\t ]+(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\)))?/,
- inside: {
- 'variable': {
- pattern: /^(!?\[)[^\]]+/,
- lookbehind: true
- },
- 'string': /(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\))$/,
- 'punctuation': /^[\[\]!:]|[<>]/
- },
- alias: 'url'
- },
- 'bold': {
- // **strong**
- // __strong__
-
- // Allow only one line break
- pattern: /(^|[^\\])(\*\*|__)(?:(?:\r?\n|\r)(?!\r?\n|\r)|.)+?\2/,
- lookbehind: true,
- inside: {
- 'punctuation': /^\*\*|^__|\*\*$|__$/
- }
- },
- 'italic': {
- // *em*
- // _em_
-
- // Allow only one line break
- pattern: /(^|[^\\])([*_])(?:(?:\r?\n|\r)(?!\r?\n|\r)|.)+?\2/,
- lookbehind: true,
- inside: {
- 'punctuation': /^[*_]|[*_]$/
- }
- },
- 'url': {
- // [example](http://example.com "Optional title")
- // [example] [id]
- pattern: /!?\[[^\]]+\](?:\([^\s)]+(?:[\t ]+"(?:\\.|[^"\\])*")?\)| ?\[[^\]\n]*\])/,
- inside: {
- 'variable': {
- pattern: /(!?\[)[^\]]+(?=\]$)/,
- lookbehind: true
- },
- 'string': {
- pattern: /"(?:\\.|[^"\\])*"(?=\)$)/
- }
- }
- }
-};
-
-export default marks;
diff --git a/src/components/Widgets/MarkdownPreview.js b/src/components/Widgets/MarkdownPreview.js
deleted file mode 100644
index 351ea9b9..00000000
--- a/src/components/Widgets/MarkdownPreview.js
+++ /dev/null
@@ -1,38 +0,0 @@
-import React, { PropTypes } from 'react';
-import { getSyntaxes } from './richText';
-import MarkupItReactRenderer from '../MarkupItReactRenderer/index';
-import previewStyle from './defaultPreviewStyle';
-
-const MarkdownPreview = ({ value, getAsset }) => {
- if (value == null) {
- return null;
- }
-
- const schema = {
- 'mediaproxy': ({ token }) => ( // eslint-disable-line
-
- ),
- };
-
- const { markdown } = getSyntaxes();
- return (
-
-
-
- );
-};
-
-MarkdownPreview.propTypes = {
- getAsset: PropTypes.func.isRequired,
- value: PropTypes.string,
-};
-
-export default MarkdownPreview;
diff --git a/src/components/MarkupItReactRenderer/__tests__/MarkupItReactRenderer.spec.js b/src/components/Widgets/MarkdownPreview/__tests__/MarkupItReactRenderer.spec.js
similarity index 78%
rename from src/components/MarkupItReactRenderer/__tests__/MarkupItReactRenderer.spec.js
rename to src/components/Widgets/MarkdownPreview/__tests__/MarkupItReactRenderer.spec.js
index 5b72258a..e8859a2a 100644
--- a/src/components/MarkupItReactRenderer/__tests__/MarkupItReactRenderer.spec.js
+++ b/src/components/Widgets/MarkdownPreview/__tests__/MarkupItReactRenderer.spec.js
@@ -3,7 +3,7 @@
import React from 'react';
import { shallow } from 'enzyme';
import { padStart } from 'lodash';
-import MarkupItReactRenderer from '../';
+import MarkdownPreview from '../index';
describe('MarkitupReactRenderer', () => {
describe('Markdown rendering', () => {
@@ -35,7 +35,7 @@ Text with **bold** & _em_ elements
###### H6
`;
- const component = shallow();
+ const component = shallow();
expect(component.html()).toMatchSnapshot();
});
});
@@ -44,7 +44,7 @@ Text with **bold** & _em_ elements
for (const heading of [...Array(6).keys()]) {
it(`should render Heading ${ heading + 1 }`, () => {
const value = padStart(' Title', heading + 7, '#');
- const component = shallow();
+ const component = shallow();
expect(component.html()).toMatchSnapshot();
});
}
@@ -55,15 +55,15 @@ Text with **bold** & _em_ elements
const value = `
1. ol item 1
1. ol item 2
- * Sublist 1
- * Sublist 2
- * Sublist 3
- 1. Sub-Sublist 1
- 1. Sub-Sublist 2
- 1. Sub-Sublist 3
+ * Sublist 1
+ * Sublist 2
+ * Sublist 3
+ 1. Sub-Sublist 1
+ 1. Sub-Sublist 2
+ 1. Sub-Sublist 3
1. ol item 3
`;
- const component = shallow();
+ const component = shallow();
expect(component.html()).toMatchSnapshot();
});
});
@@ -77,7 +77,7 @@ I get 10 times more traffic from [Google] [1] than from [Yahoo] [2] or [MSN] [3]
[2]: http://search.yahoo.com/ "Yahoo Search"
[3]: http://search.msn.com/ "MSN Search"
`;
- const component = shallow();
+ const component = shallow();
expect(component.html()).toMatchSnapshot();
});
});
@@ -85,13 +85,13 @@ I get 10 times more traffic from [Google] [1] than from [Yahoo] [2] or [MSN] [3]
describe('Code', () => {
it('should render code', () => {
const value = 'Use the `printf()` function.';
- const component = shallow();
+ const component = shallow();
expect(component.html()).toMatchSnapshot();
});
it('should render code 2', () => {
const value = '``There is a literal backtick (`) here.``';
- const component = shallow();
+ const component = shallow();
expect(component.html()).toMatchSnapshot();
});
});
@@ -113,7 +113,7 @@ I get 10 times more traffic from [Google] [1] than from [Yahoo] [2] or [MSN] [3]
Test
`;
- const component = shallow();
+ const component = shallow();
expect(component.html()).toMatchSnapshot();
});
});
@@ -122,7 +122,7 @@ I get 10 times more traffic from [Google] [1] than from [Yahoo] [2] or [MSN] [3]
describe('HTML rendering', () => {
it('should render HTML', () => {
const value = 'Paragraph with inline element
';
- const component = shallow();
+ const component = shallow();
expect(component.html()).toMatchSnapshot();
});
});
diff --git a/src/components/MarkupItReactRenderer/__tests__/__snapshots__/MarkupItReactRenderer.spec.js.snap b/src/components/Widgets/MarkdownPreview/__tests__/__snapshots__/MarkupItReactRenderer.spec.js.snap
similarity index 100%
rename from src/components/MarkupItReactRenderer/__tests__/__snapshots__/MarkupItReactRenderer.spec.js.snap
rename to src/components/Widgets/MarkdownPreview/__tests__/__snapshots__/MarkupItReactRenderer.spec.js.snap
diff --git a/src/components/Widgets/MarkdownPreview/cmsPluginRehype.js b/src/components/Widgets/MarkdownPreview/cmsPluginRehype.js
new file mode 100644
index 00000000..3efc0700
--- /dev/null
+++ b/src/components/Widgets/MarkdownPreview/cmsPluginRehype.js
@@ -0,0 +1,59 @@
+import React, { PropTypes } from "react";
+import { renderToStaticMarkup } from 'react-dom/server';
+import { Map } from 'immutable';
+import isString from 'lodash/isString';
+import isEmpty from 'lodash/isEmpty';
+import unified from 'unified';
+import htmlToRehype from 'rehype-parse';
+import registry from "../../../lib/registry";
+
+const cmsPluginRehype = ({ getAsset }) => {
+
+ const plugins = registry.getEditorComponents();
+
+ return transform;
+
+ function transform(node) {
+ // Handle externally defined plugins (they'll be wrapped in paragraphs)
+ if (node.tagName === 'p' && node.children.length === 1) {
+ if (node.children[0].type === 'text') {
+ const value = node.children[0].value;
+ const plugin = plugins.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 = `${isString(preview) ? preview : renderToStaticMarkup(preview)}
`;
+ return unified().use(htmlToRehype, { fragment: true }).parse(output).children[0];
+ }
+ }
+
+ // 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[0].tagName === 'img') {
+ const { src, alt } = node.children[0].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 = plugins.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 = `${isString(preview) ? preview : renderToStaticMarkup(preview)}
`;
+ return unified().use(htmlToRehype, { fragment: true }).parse(output).children[0];
+ }
+ }
+ }
+
+ if (!isEmpty(node.children)) {
+ node.children = node.children.map(childNode => transform(childNode, getAsset));
+ }
+
+ return node;
+ }
+};
+
+export default cmsPluginRehype;
diff --git a/src/components/Widgets/MarkdownPreview/index.js b/src/components/Widgets/MarkdownPreview/index.js
new file mode 100644
index 00000000..cfb09370
--- /dev/null
+++ b/src/components/Widgets/MarkdownPreview/index.js
@@ -0,0 +1,27 @@
+import React, { PropTypes } from 'react';
+import unified from 'unified';
+import markdownToRemark from 'remark-parse';
+import remarkToRehype from 'remark-rehype';
+import htmlToRehype from 'rehype-parse';
+import rehypeToReact from 'rehype-react';
+import cmsPluginToRehype from './cmsPluginRehype';
+import previewStyle from '../defaultPreviewStyle';
+
+const MarkdownPreview = ({ value, getAsset }) => {
+ const Markdown = unified()
+ .use(markdownToRemark, { commonmark: true, footnotes: true, pedantic: true })
+ .use(remarkToRehype, { allowDangerousHTML: true })
+ .use(cmsPluginToRehype, { getAsset })
+ .use(rehypeToReact, { createElement: React.createElement })
+ .processSync(value)
+ .contents;
+
+ return value === null ? null : {Markdown}
;
+};
+
+MarkdownPreview.propTypes = {
+ getAsset: PropTypes.func.isRequired,
+ value: PropTypes.string,
+};
+
+export default MarkdownPreview;
diff --git a/src/components/Widgets/richText.js b/src/components/Widgets/richText.js
deleted file mode 100644
index f6442449..00000000
--- a/src/components/Widgets/richText.js
+++ /dev/null
@@ -1,131 +0,0 @@
-/* eslint react/prop-types: 0, react/no-multi-comp: 0 */
-import React from 'react';
-import { List, Map } from 'immutable';
-import MarkupIt from 'markup-it';
-import markdownSyntax from 'markup-it/syntaxes/markdown';
-import htmlSyntax from 'markup-it/syntaxes/html';
-import reInline from 'markup-it/syntaxes/markdown/re/inline';
-import { Icon } from '../UI';
-
-/*
- * All Rich text widgets (Markdown, for example) should use Slate for text editing and
- * MarkupIt to convert between structured formats (Slate JSON, Markdown, HTML, etc.).
- * This module Processes and provides Slate nodes and MarkupIt syntaxes augmented with plugins
- */
-
-let processedPlugins = List([]);
-
-const nodes = {};
-let augmentedMarkdownSyntax = markdownSyntax;
-let augmentedHTMLSyntax = htmlSyntax;
-
-function processEditorPlugins(plugins) {
- // Since the plugin list is immutable, a simple comparisson is enough
- // to determine whether we need to process again.
- if (plugins === processedPlugins) return;
-
- plugins.forEach((plugin) => {
- const basicRule = MarkupIt.Rule(plugin.id).regExp(plugin.pattern, (state, match) => (
- { data: plugin.fromBlock(match) }
- ));
-
- const markdownRule = basicRule.toText((state, token) => (
- `${ plugin.toBlock(token.getData().toObject()) }\n\n`
- ));
-
- const htmlRule = basicRule.toText((state, token) => (
- plugin.toPreview(token.getData().toObject())
- ));
-
- const nodeRenderer = (props) => {
- const { node, state } = props;
- const isFocused = state.selection.hasEdgeIn(node);
- const className = isFocused ? 'plugin active' : 'plugin';
- return (
-
-
-
- {plugin.fields.map(field => `${ field.label }: “${ node.data.get(field.name) }”`)}
-
-
- );
- };
-
- augmentedMarkdownSyntax = augmentedMarkdownSyntax.addInlineRules(markdownRule);
- augmentedHTMLSyntax = augmentedHTMLSyntax.addInlineRules(htmlRule);
- nodes[plugin.id] = nodeRenderer;
- });
-
- processedPlugins = plugins;
-}
-
-function processAssetProxyPlugins(getAsset) {
- const assetProxyRule = MarkupIt.Rule('assetproxy').regExp(reInline.link, (state, match) => {
- if (match[0].charAt(0) !== '!') {
- // Return if this is not an image
- return;
- }
-
- const imgData = Map({
- alt: match[1],
- src: match[2],
- title: match[3],
- }).filter(Boolean);
-
- return {
- data: imgData,
- };
- });
- const assetProxyMarkdownRule = assetProxyRule.toText((state, token) => {
- const data = token.getData();
- const alt = data.get('alt', '');
- const src = data.get('src', '');
- const title = data.get('title', '');
-
- if (title) {
- return `![${ alt }](${ src } "${ title }")`;
- } else {
- return `![${ alt }](${ src })`;
- }
- });
- const assetProxyHTMLRule = assetProxyRule.toText((state, token) => {
- const data = token.getData();
- const alt = data.get('alt', '');
- const src = data.get('src', '');
- return ``;
- });
-
- nodes.assetproxy = (props) => {
- /* eslint react/prop-types: 0 */
- const { node, state } = props;
- const isFocused = state.selection.hasEdgeIn(node);
- const className = isFocused ? 'active' : null;
- const src = node.data.get('src');
- return (
-
- );
- };
- augmentedMarkdownSyntax = augmentedMarkdownSyntax.addInlineRules(assetProxyMarkdownRule);
- augmentedHTMLSyntax = augmentedHTMLSyntax.addInlineRules(assetProxyHTMLRule);
-}
-
-function getPlugins() {
- return processedPlugins.map(plugin => ({
- id: plugin.id,
- icon: plugin.icon,
- fields: plugin.fields,
- })).toArray();
-}
-
-function getNodes() {
- return nodes;
-}
-
-function getSyntaxes(getAsset) {
- if (getAsset) {
- processAssetProxyPlugins(getAsset);
- }
- return { markdown: augmentedMarkdownSyntax, html: augmentedHTMLSyntax };
-}
-
-export { processEditorPlugins, getNodes, getSyntaxes, getPlugins };
diff --git a/src/components/stories/MarkupItReactRenderer.js b/src/components/stories/MarkupItReactRenderer.js
deleted file mode 100644
index a28e6949..00000000
--- a/src/components/stories/MarkupItReactRenderer.js
+++ /dev/null
@@ -1,40 +0,0 @@
-import React from 'react';
-import markdownSyntax from 'markup-it/syntaxes/markdown';
-import htmlSyntax from 'markup-it/syntaxes/html';
-import MarkupItReactRenderer from '../MarkupItReactRenderer';
-import { storiesOf } from '@kadira/storybook';
-
-const mdContent = `
-# Title
-
-* List 1
-* List 2
-`;
-
-const htmlContent = `
-Title
-
-- List item 1
-- List item 2
-
-`;
-
-function getAsset(path) {
- return path;
-}
-
-storiesOf('MarkupItReactRenderer', module)
- .add('Markdown', () => (
-
-
- )).add('HTML', () => (
-
- ));
diff --git a/src/components/stories/index.js b/src/components/stories/index.js
index 1e73d155..c270c754 100644
--- a/src/components/stories/index.js
+++ b/src/components/stories/index.js
@@ -2,5 +2,4 @@ import './Card';
import './Icon';
import './Toast';
import './FindBar';
-import './MarkupItReactRenderer';
import './ScrollSync';
diff --git a/src/lib/registry.js b/src/lib/registry.js
index 8990c96a..6f5254df 100644
--- a/src/lib/registry.js
+++ b/src/lib/registry.js
@@ -1,5 +1,5 @@
import { Map } from 'immutable';
-import { newEditorPlugin } from '../components/Widgets/MarkdownControlElements/plugins';
+import { newEditorPlugin } from '../components/Widgets/MarkdownControl/plugins';
const _registry = {
templates: {},