Skip to content

Commit

Permalink
Updating markdown implementation to allow for GFM (#109)
Browse files Browse the repository at this point in the history
  • Loading branch information
cohitre authored Jun 3, 2024
1 parent 8a8ddca commit 79f382e
Show file tree
Hide file tree
Showing 6 changed files with 337 additions and 25 deletions.
55 changes: 45 additions & 10 deletions packages/block-text/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions packages/block-text/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@usewaypoint/block-text",
"version": "0.0.4",
"version": "0.0.5",
"description": "@usewaypoint/document compatible Text component",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
Expand All @@ -25,9 +25,11 @@
"zod": "^1 || ^2 || ^3"
},
"devDependencies": {
"@testing-library/react": "^14.2.1"
"@testing-library/react": "^14.2.1",
"@types/insane": "^1.0.0"
},
"dependencies": {
"markdown-parser-react": "^1.1.2"
"insane": "^2.6.2",
"marked": "^12.0.2"
}
}
106 changes: 106 additions & 0 deletions packages/block-text/src/EmailMarkdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import insane, { AllowedTags } from 'insane';
import { marked, Renderer } from 'marked';
import React, { CSSProperties, useMemo } from 'react';

const ALLOWED_TAGS: AllowedTags[] = [
'a',
'article',
'b',
'blockquote',
'br',
'caption',
'code',
'del',
'details',
'div',
'em',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'hr',
'i',
'img',
'ins',
'kbd',
'li',
'main',
'ol',
'p',
'pre',
'section',
'span',
'strong',
'sub',
'summary',
'sup',
'table',
'tbody',
'td',
'th',
'thead',
'tr',
'u',
'ul',
];
const GENERIC_ALLOWED_ATTRIBUTES = ['style', 'title'];

function sanitizer(html: string): string {
return insane(html, {
allowedTags: ALLOWED_TAGS,
allowedAttributes: {
...ALLOWED_TAGS.reduce<Record<string, string[]>>((res, tag) => {
res[tag] = [...GENERIC_ALLOWED_ATTRIBUTES];
return res;
}, {}),
img: ['src', 'srcset', 'alt', 'width', 'height', ...GENERIC_ALLOWED_ATTRIBUTES],
table: ['width', ...GENERIC_ALLOWED_ATTRIBUTES],
td: ['width', ...GENERIC_ALLOWED_ATTRIBUTES],
a: ['href', 'target', ...GENERIC_ALLOWED_ATTRIBUTES],
},
});
}

class CustomRenderer extends Renderer {
table(header: string, body: string) {
return `<table width="100%">
<thead>
${header}</thead>
<tbody>
${body}</tbody>
</table>`;
}

link(href: string, title: string | null, text: string) {
if (!title) {
return `<a href="${href}" target="_blank">${text}</a>`;
}
return `<a href="${href}" title="${title}" target="_blank">${text}</a>`;
}
}

function renderMarkdownString(str: string): string {
const html = marked.parse(str, {
async: false,
breaks: true,
gfm: true,
pedantic: false,
silent: false,
renderer: new CustomRenderer(),
});
if (typeof html !== 'string') {
throw new Error('marked.parse did not return a string');
}
return sanitizer(html);
}

type Props = {
style: CSSProperties;
markdown: string;
};
export default function EmailMarkdown({ markdown, ...props }: Props) {
const data = useMemo(() => renderMarkdownString(markdown), [markdown]);
return <div {...props} dangerouslySetInnerHTML={{ __html: data }} />;
}
145 changes: 140 additions & 5 deletions packages/block-text/src/__snapshots__/index.spec.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,55 @@ exports[`block-text renders with default values 1`] = `
exports[`block-text renders with safe markdown 1`] = `
<DocumentFragment>
<div>
<div>
<h2>
This is &lt;span&gt;markdown&lt;/span&gt;
</h2>
</div>
<p>
This
<span>
text
</span>
block has the
<strong>
Markdown
</strong>
option
<em>
turned on
</em>
.
</p>
<ul>
<li>
One
</li>
<li>
Two
</li>
<li>
Three
</li>
</ul>
<p>
Powered by
<a
href="https://usewaypoint.com"
target="_blank"
>
Waypoint
</a>
</p>
</div>
</DocumentFragment>
`;
Expand All @@ -25,3 +69,94 @@ exports[`block-text renders without markdown 1`] = `
</div>
</DocumentFragment>
`;

exports[`block-text sanitizes HTML 1`] = `
<DocumentFragment>
<div>
<img
src="x"
/>
<p>
<a
target="_blank"
>
a
</a>
<br />
<a
target="_blank"
>
Basic
</a>
<br />
<a
target="_blank"
>
Local Storage
</a>
<br />
<a
target="_blank"
>
CaseInsensitive
</a>
<br />
<a
target="_blank"
>
URL
</a>
</p>
<p>
<a
target="_blank"
>
In Quotes
</a>
<br />
[a](j a v a s c r i p t:prompt(document.cookie))
<br />
<a
target="_blank"
>
a
</a>
<br />
<a
target="_blank"
>
a
</a>
<br />
<img
alt="Uh oh..."
src="%22onerror=%22alert('XSS')"
/>
<br />
<img
alt="Uh oh..."
src="https://www.example.com/image.png%22onload=%22alert('XSS')"
/>
<br />
<img
alt="Escape SRC - onload"
src="https://www.example.com/image.png%22onload=%22alert('ImageOnLoad')"
/>
<br />
<img
alt="Escape SRC - onerror"
src="%22onerror=%22alert('ImageOnError')"
/>
</p>
</div>
</DocumentFragment>
`;
Loading

0 comments on commit 79f382e

Please sign in to comment.