fix visual editor tests, parse/serialize consistency

This commit is contained in:
Shawn Erquhart 2017-06-09 23:49:14 -04:00
parent b22323201d
commit bd767308cd
33 changed files with 125 additions and 441 deletions

View File

@ -158,7 +158,10 @@
"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-parse": "^3.1.0",
"rehype-raw": "^1.0.0",
"rehype-react": "^3.0.0",
"rehype-remark": "^2.0.0", "rehype-remark": "^2.0.0",
"rehype-sanitize": "^2.0.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",

View File

@ -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 = `<div>${typeof preview === 'string' ? preview : renderToStaticMarkup(preview)}</div>`;
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' ?
<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 = {
value: PropTypes.string,
getAsset: PropTypes.func.isRequired,
};

View File

@ -3,6 +3,8 @@ import unified from 'unified';
import htmlToRehype from 'rehype-parse'; import htmlToRehype from 'rehype-parse';
import rehypeToRemark from 'rehype-remark'; import rehypeToRemark from 'rehype-remark';
import remarkToMarkdown from 'remark-stringify'; import remarkToMarkdown from 'remark-stringify';
import rehypeSanitize from 'rehype-sanitize';
import rehypeReparse from 'rehype-raw';
import CaretPosition from 'textarea-caret-position'; import CaretPosition from 'textarea-caret-position';
import TextareaAutosize from 'react-textarea-autosize'; import TextareaAutosize from 'react-textarea-autosize';
import registry from '../../../../lib/registry'; import registry from '../../../../lib/registry';
@ -25,9 +27,11 @@ function processUrl(url) {
function cleanupPaste(paste) { function cleanupPaste(paste) {
return unified() return unified()
.use(htmlToRehype) .use(htmlToRehype, { fragment: true })
.use(rehypeSanitize)
.use(rehypeReparse)
.use(rehypeToRemark) .use(rehypeToRemark)
.use(remarkToMarkdown) .use(remarkToMarkdown, { commonmark: true, footnotes: true, pedantic: true })
.process(paste); .process(paste);
} }

View File

@ -11,6 +11,9 @@ import {
import { keymap } from 'prosemirror-keymap'; import { keymap } from 'prosemirror-keymap';
import { schema as markdownSchema, defaultMarkdownSerializer } from 'prosemirror-markdown'; import { schema as markdownSchema, defaultMarkdownSerializer } from 'prosemirror-markdown';
import { baseKeymap, setBlockType, toggleMark } from 'prosemirror-commands'; 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 registry from '../../../../lib/registry';
import { createAssetProxy } from '../../../../valueObjects/AssetProxy'; import { createAssetProxy } from '../../../../valueObjects/AssetProxy';
import { buildKeymap } from './keymap'; import { buildKeymap } from './keymap';
@ -147,7 +150,11 @@ export default class Editor extends Component {
const { serializer } = this.state; const { serializer } = this.state;
const newState = this.view.state.applyAction(action); const newState = this.view.state.applyAction(action);
const md = serializer.serialize(newState.doc); 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); this.view.updateState(newState);
if (newState.selection !== this.state.selection) { if (newState.selection !== this.state.selection) {
this.handleSelection(newState); this.handleSelection(newState);

View File

@ -1,5 +1,5 @@
import unified from 'unified'; import unified from 'unified';
import markdown from 'remark-parse'; import remarkToMarkdown from 'remark-parse';
import { Mark } from 'prosemirror-model'; import { Mark } from 'prosemirror-model';
import markdownToProseMirror from './markdownToProseMirror'; import markdownToProseMirror from './markdownToProseMirror';
@ -12,7 +12,7 @@ const state = { activeMarks: Mark.none, textsArray: [] };
*/ */
function parser(src) { function parser(src) {
const result = unified() const result = unified()
.use(markdown, { commonmark: true, footnotes: true, pedantic: true }) .use(remarkToMarkdown, { commonmark: true, footnotes: true, pedantic: true })
.parse(src); .parse(src);
return unified() return unified()

View File

@ -1,9 +1,8 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import registry from '../../lib/registry'; import registry from '../../../lib/registry';
import RawEditor from './MarkdownControlElements/RawEditor'; import RawEditor from './RawEditor';
import VisualEditor from './MarkdownControlElements/VisualEditor'; import VisualEditor from './VisualEditor';
import { processEditorPlugins } from './richText'; import { StickyContainer } from '../../UI/Sticky/Sticky';
import { StickyContainer } from '../UI/Sticky/Sticky';
const MODE_STORAGE_KEY = 'cms.md-mode'; 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' }; this.state = { mode: localStorage.getItem(MODE_STORAGE_KEY) || 'visual' };
} }
componentWillMount() {
processEditorPlugins(registry.getEditorComponents());
}
handleMode = (mode) => { handleMode = (mode) => {
this.setState({ mode }); this.setState({ mode });
localStorage.setItem(MODE_STORAGE_KEY, mode); localStorage.setItem(MODE_STORAGE_KEY, mode);

View File

@ -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]: <http://example.com> "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;

View File

@ -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
<img
src={getAsset(token.getIn(['data', 'src']))}
alt={token.getIn(['data', 'alt'])}
/>
),
};
const { markdown } = getSyntaxes();
return (
<div style={previewStyle}>
<MarkupItReactRenderer
value={value}
syntax={markdown}
schema={schema}
getAsset={getAsset}
/>
</div>
);
};
MarkdownPreview.propTypes = {
getAsset: PropTypes.func.isRequired,
value: PropTypes.string,
};
export default MarkdownPreview;

View File

@ -3,7 +3,7 @@
import React from 'react'; import React from 'react';
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import { padStart } from 'lodash'; import { padStart } from 'lodash';
import MarkupItReactRenderer from '../'; import MarkdownPreview from '../index';
describe('MarkitupReactRenderer', () => { describe('MarkitupReactRenderer', () => {
describe('Markdown rendering', () => { describe('Markdown rendering', () => {
@ -35,7 +35,7 @@ Text with **bold** & _em_ elements
###### H6 ###### H6
`; `;
const component = shallow(<MarkupItReactRenderer value={value} />); const component = shallow(<MarkdownPreview value={value} />);
expect(component.html()).toMatchSnapshot(); expect(component.html()).toMatchSnapshot();
}); });
}); });
@ -44,7 +44,7 @@ Text with **bold** & _em_ elements
for (const heading of [...Array(6).keys()]) { for (const heading of [...Array(6).keys()]) {
it(`should render Heading ${ heading + 1 }`, () => { it(`should render Heading ${ heading + 1 }`, () => {
const value = padStart(' Title', heading + 7, '#'); const value = padStart(' Title', heading + 7, '#');
const component = shallow(<MarkupItReactRenderer value={value} />); const component = shallow(<MarkdownPreview value={value} />);
expect(component.html()).toMatchSnapshot(); expect(component.html()).toMatchSnapshot();
}); });
} }
@ -55,15 +55,15 @@ Text with **bold** & _em_ elements
const value = ` const value = `
1. ol item 1 1. ol item 1
1. ol item 2 1. ol item 2
* Sublist 1 * Sublist 1
* Sublist 2 * Sublist 2
* Sublist 3 * Sublist 3
1. Sub-Sublist 1 1. Sub-Sublist 1
1. Sub-Sublist 2 1. Sub-Sublist 2
1. Sub-Sublist 3 1. Sub-Sublist 3
1. ol item 3 1. ol item 3
`; `;
const component = shallow(<MarkupItReactRenderer value={value} />); const component = shallow(<MarkdownPreview value={value} />);
expect(component.html()).toMatchSnapshot(); 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" [2]: http://search.yahoo.com/ "Yahoo Search"
[3]: http://search.msn.com/ "MSN Search" [3]: http://search.msn.com/ "MSN Search"
`; `;
const component = shallow(<MarkupItReactRenderer value={value} />); const component = shallow(<MarkdownPreview value={value} />);
expect(component.html()).toMatchSnapshot(); 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', () => { describe('Code', () => {
it('should render code', () => { it('should render code', () => {
const value = 'Use the `printf()` function.'; const value = 'Use the `printf()` function.';
const component = shallow(<MarkupItReactRenderer value={value} />); const component = shallow(<MarkdownPreview value={value} />);
expect(component.html()).toMatchSnapshot(); expect(component.html()).toMatchSnapshot();
}); });
it('should render code 2', () => { it('should render code 2', () => {
const value = '``There is a literal backtick (`) here.``'; const value = '``There is a literal backtick (`) here.``';
const component = shallow(<MarkupItReactRenderer value={value} />); const component = shallow(<MarkdownPreview value={value} />);
expect(component.html()).toMatchSnapshot(); expect(component.html()).toMatchSnapshot();
}); });
}); });
@ -113,7 +113,7 @@ I get 10 times more traffic from [Google] [1] than from [Yahoo] [2] or [MSN] [3]
<h1 style="display: block; border: 10px solid #f00; width: 100%">Test</h1> <h1 style="display: block; border: 10px solid #f00; width: 100%">Test</h1>
`; `;
const component = shallow(<MarkupItReactRenderer value={value} />); const component = shallow(<MarkdownPreview value={value} />);
expect(component.html()).toMatchSnapshot(); 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', () => { describe('HTML rendering', () => {
it('should render HTML', () => { it('should render HTML', () => {
const value = '<p>Paragraph with <em>inline</em> element</p>'; const value = '<p>Paragraph with <em>inline</em> element</p>';
const component = shallow(<MarkupItReactRenderer value={value} />); const component = shallow(<MarkdownPreview value={value} />);
expect(component.html()).toMatchSnapshot(); expect(component.html()).toMatchSnapshot();
}); });
}); });

View File

@ -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 = `<div>${isString(preview) ? preview : renderToStaticMarkup(preview)}</div>`;
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 = `<div>${isString(preview) ? preview : renderToStaticMarkup(preview)}</div>`;
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;

View File

@ -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 : <div style={previewStyle}>{Markdown}</div>;
};
MarkdownPreview.propTypes = {
getAsset: PropTypes.func.isRequired,
value: PropTypes.string,
};
export default MarkdownPreview;

View File

@ -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 (
<div {...props.attributes} className={className}>
<div className="plugin_icon" contentEditable={false}><Icon type={plugin.icon} /></div>
<div className="plugin_fields" contentEditable={false}>
{plugin.fields.map(field => `${ field.label }: “${ node.data.get(field.name) }`)}
</div>
</div>
);
};
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 `<img src=${ getAsset(src) } alt=${ alt } />`;
});
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 (
<img {...props.attributes} src={getAsset(src)} className={className} />
);
};
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 };

View File

@ -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 = `
<h1>Title</h1>
<ol>
<li>List item 1</li>
<li>List item 2</li>
</ol>
`;
function getAsset(path) {
return path;
}
storiesOf('MarkupItReactRenderer', module)
.add('Markdown', () => (
<MarkupItReactRenderer
value={mdContent}
syntax={markdownSyntax}
getAsset={getAsset}
/>
)).add('HTML', () => (
<MarkupItReactRenderer
value={htmlContent}
syntax={htmlSyntax}
getAsset={getAsset}
/>
));

View File

@ -2,5 +2,4 @@ import './Card';
import './Icon'; import './Icon';
import './Toast'; import './Toast';
import './FindBar'; import './FindBar';
import './MarkupItReactRenderer';
import './ScrollSync'; import './ScrollSync';

View File

@ -1,5 +1,5 @@
import { Map } from 'immutable'; import { Map } from 'immutable';
import { newEditorPlugin } from '../components/Widgets/MarkdownControlElements/plugins'; import { newEditorPlugin } from '../components/Widgets/MarkdownControl/plugins';
const _registry = { const _registry = {
templates: {}, templates: {},