Skip to content
This repository has been archived by the owner on Nov 28, 2022. It is now read-only.

Commit

Permalink
feat: implement markdown/html in excerpt(#161)
Browse files Browse the repository at this point in the history
  • Loading branch information
0xVolodya committed Nov 11, 2018
1 parent f87fb13 commit aee9d82
Show file tree
Hide file tree
Showing 7 changed files with 1,770 additions and 1,345 deletions.
3 changes: 3 additions & 0 deletions example/swagger-files/response-schemas.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"/anything/array-of-primitives": {
"get": {
"summary": "Array of primitives",
"description": "sadf<h1>this is a paragraph</h1> <img src='x' onerror='alert('charlie')> Test \n > this is markdown",
"responses": {
"200": {
"description": "OK",
Expand All @@ -33,6 +34,7 @@
"/anything/object": {
"get": {
"summary": "Object",
"description": "<h3>this is a paragraph</h3>",
"responses": {
"200": {
"description": "OK",
Expand All @@ -50,6 +52,7 @@
"/anything/array-of-objects": {
"get": {
"summary": "Array of objects",
"description": "Test \n > this is markdown",
"responses": {
"200": {
"description": "OK",
Expand Down
6 changes: 2 additions & 4 deletions packages/api-explorer/src/Doc.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const CodeSample = require('./CodeSample');
const Response = require('./Response');
const ResponseSchema = require('./ResponseSchema');
const EndpointErrorBoundary = require('./EndpointErrorBoundary');
const markdown = require('../../markdown');

const Oas = require('./lib/Oas');
// const showCode = require('./lib/show-code');
Expand Down Expand Up @@ -315,10 +316,7 @@ class Doc extends React.Component {
<h2>{doc.title}</h2>
{doc.excerpt && (
<div className="excerpt">
{
// eslint-disable-next-line react/no-danger
<p dangerouslySetInnerHTML={{ __html: doc.excerpt }} />
}
{markdown(doc.excerpt)}
</div>
)}
</header>
Expand Down
56 changes: 39 additions & 17 deletions packages/markdown/__tests__/__snapshots__/index.test.js.snap
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`anchors 1`] = `
"<p><a href=\\"http://example.com\\" target=\\"_self\\">link</a><br/>
<a href=\\"\\" target=\\"_self\\">xss</a><br/>
<a href=\\"/docs/slug\\" target=\\"_self\\" class=\\"doc-link\\" data-sidebar=\\"slug\\">doc</a><br/>
<a href=\\"/reference#slug\\" target=\\"_self\\">ref</a><br/>
<a href=\\"/blog/slug\\" target=\\"_self\\">blog</a><br/>
<a href=\\"/page/slug\\" target=\\"_self\\">page</a><br/>
"<p><a href=\\"http://example.com\\" target=\\"_self\\">link</a>
<a href=\\"javascript:alert\\" target=\\"_self\\">xss</a>
<a href=\\"/docs/slug\\" target=\\"_self\\" class=\\"doc-link\\" data-sidebar=\\"slug\\">doc</a>
<a href=\\"/reference#slug\\" target=\\"_self\\">ref</a>
<a href=\\"/blog/slug\\" target=\\"_self\\">blog</a>
<a href=\\"/page/slug\\" target=\\"_self\\">page</a>
</p>"
`;
exports[`check list items 1`] = `
"<ul>
<li><input type=\\"checkbox\\" disabled=\\"\\"/> checklistitem1</li>
<li><input type=\\"checkbox\\" checked=\\"\\" disabled=\\"\\"/> checklistitem1</li>
<li class=\\"task-list-item\\"><input type=\\"checkbox\\" disabled=\\"\\"/> checklistitem1</li>
<li class=\\"task-list-item\\"><input type=\\"checkbox\\" checked=\\"\\" disabled=\\"\\"/> checklistitem1</li>
</ul>"
`;
Expand All @@ -25,13 +25,13 @@ exports[`code samples 1`] = `
`;
exports[`emojis 1`] = `
"<p><img src=\\"/img/emojis/joy.png\\" alt=\\":joy:\\" title=\\":joy:\\" class=\\"emoji\\" align=\\"absmiddle\\" height=\\"20\\" width=\\"20\\"/><br/>
<i class=\\"fa fa-lock\\"></i><br/>
:unknown-emoji:<br/>
"<p>:joy:
:fa-lock:
:unknown-emoji:
</p>"
`;
exports[`glossary 1`] = `"<p></p>"`;
exports[`glossary 1`] = `"<p>&lt;&gt;</p>"`;
exports[`headings 1`] = `
"<h1 class=\\"header-scroll\\"><div class=\\"anchor waypoint\\" id=\\"section-h1\\"></div>h1<a class=\\"fa fa-anchor\\" href=\\"#section-h1\\"></a></h1>
Expand All @@ -55,10 +55,32 @@ exports[`list items 1`] = `
exports[`should strip out inputs 1`] = `""`;
exports[`tables 1`] = `
"<div class=\\"marked-table\\"><table>
<thead><tr><th>Tables</th><th style=\\"text-align:center\\">Are</th><th style=\\"text-align:right\\">Cool</th></tr></thead>
<tbody><tr><td>col 3 is</td><td style=\\"text-align:center\\">right-aligned</td><td style=\\"text-align:right\\">$1600</td></tr><tr><td>col 2 is</td><td style=\\"text-align:center\\">centered</td><td style=\\"text-align:right\\">$12</td></tr><tr><td>zebra stripes</td><td style=\\"text-align:center\\">are neat</td><td style=\\"text-align:right\\">$1</td></tr></tbody>
</table></div>"
"
<div class=\\"marked-table\\"><table><thead><tr><th>Tables</th><th align=\\"center\\">Are</th><th align=\\"right\\">Cool</th></tr></thead><tbody><tr><td>col 3 is</td><td align=\\"center\\">right-aligned</td><td align=\\"right\\">$1600</td></tr><tr><td>col 2 is</td><td align=\\"center\\">centered</td><td align=\\"right\\">$12</td></tr><tr><td>zebra stripes</td><td align=\\"center\\">are neat</td><td align=\\"right\\">$1</td></tr></tbody></table></div>"
`;
exports[`variables 1`] = `"<p><span>APIKEY</span></p>"`;
exports[`variables 1`] = `"<p>&lt;&gt;</p>"`;
60 changes: 32 additions & 28 deletions packages/markdown/__tests__/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,12 @@ test('should render nothing if nothing passed in', () => {
expect(markdown('')).toBe(null);
});

test('`correctnewlines` option', () => {
expect(shallow(markdown('test\ntest\ntest', { correctnewlines: true })).html()).toBe(
'<p>test\ntest\ntest</p>',
);
expect(shallow(markdown('test\ntest\ntest', { correctnewlines: false })).html()).toBe(
'<p>test<br/>\ntest<br/>\ntest</p>',
);
test('should parse newline character', () => {
expect(
shallow(markdown('test\ntest\ntest'))
.html()
.includes('\n'),
).toBe(true);
});

test('variables', () => {
Expand All @@ -113,29 +112,34 @@ test('glossary', () => {
expect(shallow(markdown(`<<glossary:term>>`)).html()).toMatchSnapshot();
});

// TODO not sure if this needs to work or not?
// Isn't it a good thing to always strip HTML?
describe.skip('`stripHtml` option', () => {
test('should allow html by default', () => {
expect(markdown('<p>Test</p>')).toBe('<p><p>Test</p></p>\n');
expect(markdown('<p>Test</p>', { stripHtml: false })).toBe('<p><p>Test</p></p>\n');
});
test('should escape unknown tags', () => {
expect(shallow(markdown('<unknown-tag>Test</unknown-tag>')).html()).toBe('<p>Test</p>');
});

test('should escape unknown tags', () => {
expect(markdown('<unknown-tag>Test</unknown-tag>')).toBe(
'<p>&lt;unknown-tag&gt;Test&lt;/unknown-tag&gt;</p>\n',
);
});
test('should allow certain attributes', () => {
expect(shallow(markdown('<p id="test">Test</p>')).html()).toBe(
'<p id="user-content-test">Test</p>',
);
});

test('should allow certain attributes', () => {
expect(markdown('<p id="test">Test</p>')).toBe('<p><p id="test">Test</p></p>\n');
});
test('should strip unknown attributes', () => {
expect(shallow(markdown('<p unknown="test">Test</p>')).html()).toBe('<p>Test</p>');
});

test('should strip unknown attributes', () => {
expect(markdown('<p unknown="test">Test</p>')).toBe('<p><p>Test</p></p>\n');
});
test('should strip dangerous href', () => {
expect(shallow(markdown('<a href="jAva script:alert(\'bravo\')">delta</a>')).html()).toBe(
'<p><a href="" target="_self">delta</a></p>',
);
});

test('should strip dangerous iframe tag', () => {
expect(
shallow(markdown('<p><iframe src="javascript:alert(\'delta\')"></iframe></p>')).html(),
).toBe('<p></p>');
});

test('should escape everything if `stripHtml=true`', () => {
expect(markdown('<p>Test</p>', { stripHtml: true })).toBe('<p>&lt;p&gt;Test&lt;/p&gt;</p>\n');
});
test('should strip dangerous img attributes', () => {
expect(shallow(markdown('<img src="x" onerror="alert(\'charlie\')">')).html()).toBe(
'<img src="x"/>',
);
});
26 changes: 18 additions & 8 deletions packages/markdown/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
const React = require('react');
const remark = require('remark');
const reactRenderer = require('remark-react');
const breaks = require('remark-breaks');
const sanitize = require('hast-util-sanitize/lib/github.json');
const Variable = require('@readme/variable');
const remarkRehype = require('remark-rehype');
const rehypeRaw = require('rehype-raw');
const remarkParse = require('remark-parse');
const rehypeSanitize = require('rehype-sanitize');
const rehypeReact = require('rehype-react');
const rehype = require('rehype');

const variableParser = require('./variable-parser');
const gemojiParser = require('./gemoji-parser');
Expand All @@ -19,16 +23,22 @@ sanitize.ancestors.input = ['li'];

const GlossaryItem = require('./GlossaryItem');

module.exports = function markdown(text, opts = {}) {
module.exports = function markdown(text) {
if (!text) return null;
// strip out unsafe html
const parsedText = rehype()
.use(rehypeSanitize)
.processSync(text).contents;

return remark()
.use(variableParser.sanitize(sanitize))
.use(!opts.correctnewlines ? breaks : () => {})
.use(gemojiParser.sanitize(sanitize))
.use(reactRenderer, {
sanitize,
remarkReactComponents: {
.use(remarkParse)
.use(remarkRehype, { allowDangerousHTML: true })
.use(rehypeRaw)
.use(rehypeReact, {
createElement: React.createElement,
components: {
'readme-variable': Variable,
'readme-glossary-item': GlossaryItem,
table: table(sanitize),
Expand All @@ -45,5 +55,5 @@ module.exports = function markdown(text, opts = {}) {
div: props => React.createElement(React.Fragment, props),
},
})
.processSync(text).contents;
.processSync(parsedText).contents;
};
Loading

0 comments on commit aee9d82

Please sign in to comment.