Skip to content

Commit

Permalink
Enable adding YouTube videos with the ::youtube[] markdown tag (#5104)
Browse files Browse the repository at this point in the history
  • Loading branch information
willemarcel authored Apr 26, 2022
1 parent 27ab132 commit 852005d
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 1 deletion.
68 changes: 67 additions & 1 deletion frontend/src/utils/htmlFromMarkdown.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,59 @@
import { marked } from 'marked';
import DOMPurify from 'dompurify';

const VIDEO_TAG_REGEXP = new RegExp(/^::youtube\[(.*)\]$/);

const parseMarkdown = (markdownText) => {
marked.use({
gfm: false,
extensions: [
{
name: 'videoExt',
level: 'inline',
start: (src) => {
const m = src.match(/^::youtube/);

if (m) {
return m.index;
}
},
tokenizer: function(src) {
const match = VIDEO_TAG_REGEXP.exec(src);

if (match) {
return {
type: 'videoExt',
raw: match[0],
text: match[1].trim(),
tokens: []
};
}
},
renderer: function(token) {
const videoId = token.text;

return `
<iframe
src="https://www.youtube.com/embed/${videoId}"
width="480" height="270"
style="border: 0;"
title="YouTube Video"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen>
</iframe>
`;
}
}
]
});
return marked.parse(markdownText);
}

/* per https://stackoverflow.com/a/34688574/272018 */
export const htmlFromMarkdown = (markdownText) => {
DOMPurify.addHook('afterSanitizeAttributes', function (node) {

// set all elements owning target to target=_blank
if ('target' in node) {
node.setAttribute('target', '_blank');
Expand All @@ -16,7 +66,23 @@ export const htmlFromMarkdown = (markdownText) => {
node.setAttribute('xlink:show', 'new');
}
});
return { __html: DOMPurify.sanitize(marked.parse(markdownText)) };

DOMPurify.addHook("uponSanitizeElement", (node, data) => {
if (data.tagName === "iframe") {
const src = node.getAttribute("src") || "";
// allow only youtube urls to be embedded in iframes
if (!src.startsWith("https://www.youtube.com/embed/")) {
return node.parentNode?.removeChild(node);
}
}
});

const config = {
ADD_TAGS: ['iframe'],
ADD_ATTR: ["allow", "allowfullscreen", "frameborder"]
};

return { __html: DOMPurify.sanitize(parseMarkdown(markdownText), config) };
};

export const formatUserNamesToLink = (text) => {
Expand Down
23 changes: 23 additions & 0 deletions frontend/src/utils/tests/htmlFromMarkdown.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,29 @@ test('htmlFromMarkdown returns correct content', () => {
);
});

test('htmlFromMarkdown with youtube tag', () => {
const html = htmlFromMarkdown('::youtube[UzT0i5XhsOQ]').__html;
expect(html).toContain('<iframe');
expect(html).toContain('width="480"');
expect(html).toContain('height="270"');
expect(html).toContain('style="border: 0;"');
expect(html).toContain('title="YouTube Video"');
expect(html).toContain('frameborder="0"');
expect(html).toContain('allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"');
expect(html).toContain('allowfullscreen=""');
expect(html).toContain(
'src="https://www.youtube.com/embed/UzT0i5XhsOQ"',
);
});

test('htmlFromMarkdown should not render other iframes', () => {
const html = htmlFromMarkdown('<iframe src="https://osm.org"></iframe>').__html;
expect(html).not.toContain('<iframe');
expect(html).not.toContain(
'src="https://osm.org"',
);
});

test('formatUserNamesToLink returns correct content', () => {
expect(
formatUserNamesToLink(
Expand Down

0 comments on commit 852005d

Please sign in to comment.