diff --git a/src/components/Widgets/MarkitupReactRenderer.js b/src/components/Widgets/MarkitupReactRenderer.js index 40459162..a923fb65 100644 --- a/src/components/Widgets/MarkitupReactRenderer.js +++ b/src/components/Widgets/MarkitupReactRenderer.js @@ -1,43 +1,57 @@ import React, { PropTypes } from 'react'; -import MarkupIt, { Syntax, JSONUtils } from 'markup-it'; +import MarkupIt, { Syntax, JSONUtils, BLOCKS, STYLES, ENTITIES } from 'markup-it'; const defaultRenderers = { - 'doc': 'article', - 'header_one': 'h1', - 'header_two': 'h2', - 'header_three': 'h3', - 'header_four': 'h4', - 'header_five': 'h5', - 'header_six': 'h6', - 'paragraph': 'p', - 'ordered_list': 'ol', - 'unordered_list': 'ul', - 'list_item': 'li', - 'link': 'a', - 'image': 'img', - 'BOLD': 'strong', - 'ITALIC': 'em', - 'text': null, - 'unstyled': null, + [BLOCKS.DOCUMENT]: 'article', + [BLOCKS.TEXT]: null, + [BLOCKS.CODE]: 'code', + [BLOCKS.BLOCKQUOTE]: 'blockquote', + [BLOCKS.PARAGRAPH]: 'p', + [BLOCKS.FOOTNOTE]: 'footnote', + [BLOCKS.HTML]: (props) => null, + [BLOCKS.HR]: 'hr', + [BLOCKS.HEADING_1]: 'h1', + [BLOCKS.HEADING_2]: 'h2', + [BLOCKS.HEADING_3]: 'h3', + [BLOCKS.HEADING_4]: 'h4', + [BLOCKS.HEADING_5]: 'h5', + [BLOCKS.HEADING_6]: 'h6', + [BLOCKS.TABLE]: 'table', + [BLOCKS.TABLE_ROW]: 'tr', + [BLOCKS.TABLE_CELL]: 'td', + [BLOCKS.OL_LIST]: 'ol', + [BLOCKS.UL_LIST]: 'ul', + [BLOCKS.LIST_ITEM]: 'li', + + [STYLES.TEXT]: null, + [STYLES.BOLD]: 'strong', + [STYLES.ITALIC]: 'em', + [STYLES.CODE]: 'code', + [STYLES.STRIKETHROUGH]: 'del', + + [ENTITIES.LINK]: 'a', + [ENTITIES.IMAGE]: 'img', + [ENTITIES.FOOTNOTE_REF]: 'sup', + [ENTITIES.HARD_BREAK]: 'br' }; function renderToken(token, index = 0, key = '0') { const { type, data, text, tokens } = token; - const element = defaultRenderers[type]; + const nodeType = defaultRenderers[type]; key = `${key}.${index}`; // Only render if type is registered as renderer - if (typeof element !== 'undefined') { + if (typeof nodeType !== 'undefined') { let children = null; if (Array.isArray(tokens) && tokens.length) { children = tokens.map((token, idx) => renderToken(token, idx, key)); } else if (type === 'text') { children = text; } - if (element !== null) { + if (nodeType !== null) { // If this is a react element return React.createElement( - element, + nodeType, { key, ...data }, // Add key as a prop Array.isArray(children) && children.length === 1 ? children[0] : children); // Pass single child if possible diff --git a/src/components/Widgets/__tests__/MarkitupReactRenderer.spec.js b/src/components/Widgets/__tests__/MarkitupReactRenderer.spec.js index 4e9858d6..d2094e06 100644 --- a/src/components/Widgets/__tests__/MarkitupReactRenderer.spec.js +++ b/src/components/Widgets/__tests__/MarkitupReactRenderer.spec.js @@ -1,3 +1,5 @@ +/* eslint max-len:0 */ + import React from 'react'; import { shallow } from 'enzyme'; import markdownSyntax from 'markup-it/syntaxes/markdown'; @@ -5,39 +7,44 @@ import htmlSyntax from 'markup-it/syntaxes/html'; import MarkitupReactRenderer from '../MarkitupReactRenderer'; describe('MarkitupReactRenderer', () => { - it('should re-render properly after a value and syntax update', () => { - const component = shallow( - - ); - const tree1 = component.html(); - component.setProps({ - value: '

Title

', - syntax: htmlSyntax + + describe('basics', () => { + it('should re-render properly after a value and syntax update', () => { + const component = shallow( + + ); + const tree1 = component.html(); + component.setProps({ + value: '

Title

', + syntax: htmlSyntax + }); + const tree2 = component.html(); + expect(tree1).toEqual(tree2); + }); + + it('should not update the parser if syntax didn\'t change', () => { + const component = shallow( + + ); + const syntax1 = component.instance().props.syntax; + component.setProps({ + value: '## Title', + }); + const syntax2 = component.instance().props.syntax; + expect(syntax1).toEqual(syntax2); }); - const tree2 = component.html(); - expect(tree1).toEqual(tree2); }); - it('should not update the parser if syntax didn\'t change', () => { - const component = shallow( - - ); - const syntax1 = component.instance().props.syntax; - component.setProps({ - value: '## Title', - }); - const syntax2 = component.instance().props.syntax; - expect(syntax1).toEqual(syntax2); - }); - - it('should render markdown', () => { - const value = ` + describe('Markdown rendering', () => { + describe('General', () => { + it('should render markdown', () => { + const value = ` # H1 Text with **bold** & _em_ elements @@ -63,18 +70,69 @@ Text with **bold** & _em_ elements ###### H6 `; - const component = shallow( - - ); - const tree = component.html(); - expect(tree).toMatchSnapshot(); - }); + const component = shallow( + + ); + const tree = component.html(); + expect(tree).toMatchSnapshot(); + }); + }); - it('should render HTML as is using Markdown', () => { - const value = ` + describe('Links', () => { + it('should render links', () => { + const value = ` +I get 10 times more traffic from [Google] [1] than from [Yahoo] [2] or [MSN] [3]. + + [1]: http://google.com/ "Google" + [2]: http://search.yahoo.com/ "Yahoo Search" + [3]: http://search.msn.com/ "MSN Search" +`; + const component = shallow( + + ); + const tree = component.html(); + const expected = ''; + expect(tree).toEqual(expected); + }); + }); + + describe('Code', () => { + it('should render code', () => { + const value = 'Use the `printf()` function.'; + const component = shallow( + + ); + const tree = component.html(); + const expected = '

Use the printf() function.

'; + expect(tree).toEqual(expected); + }); + + it('should render code 2', () => { + const value = '``There is a literal backtick (`) here.``'; + const component = shallow( + + ); + const tree = component.html(); + const expected = '

There is a literal backtick (`) here.

'; + expect(tree).toEqual(expected); + }); + }); + + describe('HTML', () => { + it('should render HTML as is using Markdown', () => { + const value = ` # Title
@@ -82,37 +140,43 @@ Text with **bold** & _em_ elements
Testing HTML in Markdown
`; - const component = shallow( - - ); - const tree = component.html(); - expect(tree).toMatchSnapshot(); + const component = shallow( + + ); + const tree = component.html(); + expect(tree).toMatchSnapshot(); + }); + }); }); - it('should support custom syntax', () => { - const value = ''; - const component = shallow( - - ); - const tree = component.html(); - expect(tree).toMatchSnapshot(); + describe('custom elements', () => { + it('should support custom syntax', () => { + const value = ''; + const component = shallow( + + ); + const tree = component.html(); + expect(tree).toMatchSnapshot(); + }); }); - it('should render HTML', () => { - const value = '

Paragraph with inline element

'; - const component = shallow( - - ); - const tree = component.html(); - expect(tree).toMatchSnapshot(); + describe('HTML rendering', () => { + it('should render HTML', () => { + const value = '

Paragraph with inline element

'; + const component = shallow( + + ); + const tree = component.html(); + expect(tree).toMatchSnapshot(); + }); }); });