Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 35 additions & 41 deletions docs/src/modules/components/AppTableOfContents.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
/* eslint-disable react/no-danger */
import React from 'react';
import PropTypes from 'prop-types';
import marked from 'marked/lib/marked';
import throttle from 'lodash/throttle';
import clsx from 'clsx';
import Box from '@material-ui/core/Box';
import { useSelector } from 'react-redux';
import { makeStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import { render as renderMarkdown } from 'docs/src/modules/utils/parseMarkdown';
import textToHash from 'docs/src/modules/utils/textToHash';
import DiamondSponsors from 'docs/src/modules/components/DiamondSponsors';
import Link from 'docs/src/modules/components/Link';
Expand Down Expand Up @@ -60,43 +60,6 @@ const useStyles = makeStyles((theme) => ({
active: {},
}));

const renderer = new marked.Renderer();

function setRenderer(itemsCollector, unique) {
renderer.heading = (text2, level) => {
const text = text2
.replace(
/([\uE000-\uF8FF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDFFF]|[\u2011-\u26FF]|\uD83E[\uDD10-\uDDFF])/g,
'',
) // remove emojis
.replace(/<\/?[^>]+(>|$)/g, ''); // remove HTML

if (level === 2) {
itemsCollector.current.push({
text,
level,
hash: textToHash(text, unique),
children: [],
});
} else if (level === 3) {
if (!itemsCollector.current[itemsCollector.current.length - 1]) {
throw new Error(`Missing parent level for: ${text}`);
}

itemsCollector.current[itemsCollector.current.length - 1].children.push({
text,
level,
hash: textToHash(text, unique),
});
}
};
}

function getItemsServer(contents, itemsCollector) {
marked(contents.join(''), { renderer });
return itemsCollector.current;
}

function getItemsClient(items) {
const itemsClient = [];

Expand Down Expand Up @@ -145,9 +108,40 @@ export default function AppTableOfContents(props) {
const t = useSelector((state) => state.options.t);

const itemsServer = React.useMemo(() => {
const itemsCollectorRef = { current: [] };
setRenderer(itemsCollectorRef, {});
return getItemsServer(contents, itemsCollectorRef);
const items = [];
const unique = {};

renderMarkdown(contents.join(''), {
heading: (text2, level) => {
const text = text2
.replace(
/([\uE000-\uF8FF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDFFF]|[\u2011-\u26FF]|\uD83E[\uDD10-\uDDFF])/g,
'',
) // remove emojis
.replace(/<\/?[^>]+(>|$)/g, ''); // remove HTML

if (level === 2) {
items.push({
text,
level,
hash: textToHash(text, unique),
children: [],
});
} else if (level === 3) {
if (!items[items.length - 1]) {
throw new Error(`Missing parent level for: ${text}`);
}

items[items.length - 1].children.push({
text,
level,
hash: textToHash(text, unique),
});
}
},
});

return items;
}, [contents]);

const itemsClientRef = React.useRef([]);
Expand Down
181 changes: 80 additions & 101 deletions docs/src/modules/components/MarkdownElement.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,11 @@ import React from 'react';
import PropTypes from 'prop-types';
import clsx from 'clsx';
import { useSelector } from 'react-redux';
import marked from 'marked/lib/marked';
import { withStyles } from '@material-ui/core/styles';
import textToHash from 'docs/src/modules/utils/textToHash';
import { render as renderMarkdown } from 'docs/src/modules/utils/parseMarkdown';
import prism from 'docs/src/modules/components/prism';

// Monkey patch to preserve non-breaking spaces
Copy link
Member Author

@eps1lon eps1lon Apr 14, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They no longer replace non-breaking spaces (https://github.com/markedjs/marked/blob/v0.8.2/lib/marked.js#L553) but they also do not replace the newline symbol \u2424 with newline escape sequences anymore. It's unclear if this affects us.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No clue, the original motivation was #10252. Probably worth trying +1.

// https://github.com/chjj/marked/blob/6b0416d10910702f73da9cb6bb3d4c8dcb7dead7/lib/marked.js#L142-L150
marked.Lexer.prototype.lex = function lex(src) {
src = src
.replace(/\r\n|\r/g, '\n')
.replace(/\t/g, ' ')
.replace(/\u2424/g, '\n');

return this.token(src, true);
};

const renderer = new marked.Renderer();
renderer.heading = (text, level) => {
// Small title. No need for an anchor.
// It's reducing the risk of duplicated id and it's fewer elements in the DOM.
if (level >= 4) {
return `<h${level}>${text}</h${level}>`;
}

// eslint-disable-next-line no-underscore-dangle
const hash = textToHash(text, global.__MARKED_UNIQUE__);

return [
`<h${level}>`,
`<a class="anchor-link" id="${hash}"></a>`,
text,
`<a class="anchor-link-style" aria-hidden="true" aria-label="anchor" href="#${hash}">`,
'<svg><use xlink:href="#anchor-link-icon" /></svg>',
'</a>',
`</h${level}>`,
].join('');
};

const externs = [
'https://material.io/',
'https://getbootstrap.com/',
Expand All @@ -50,70 +17,6 @@ const externs = [
'https://ui-kit.co/',
];

renderer.link = (href, title, text) => {
let more = '';

if (externs.some((domain) => href.indexOf(domain) !== -1)) {
more = ' target="_blank" rel="noopener nofollow"';
}

// eslint-disable-next-line no-underscore-dangle
const userLanguage = global.__MARKED_USER_LANGUAGE__;
let finalHref = href;

if (userLanguage !== 'en' && finalHref.indexOf('/') === 0 && finalHref !== '/size-snapshot') {
finalHref = `/${userLanguage}${finalHref}`;
}

return `<a href="${finalHref}"${more}>${text}</a>`;
};

const markedOptions = {
gfm: true,
tables: true,
breaks: false,
pedantic: false,
sanitize: false,
smartLists: true,
smartypants: false,
highlight(code, language) {
let prismLanguage;
switch (language) {
case 'ts':
prismLanguage = prism.languages.tsx;
break;

case 'js':
case 'sh':
prismLanguage = prism.languages.jsx;
break;

case 'diff':
prismLanguage = { ...prism.languages.diff };
// original `/^[-<].*$/m` matches lines starting with `<` which matches
// <SomeComponent />
// we will only use `-` as the deleted marker
prismLanguage.deleted = /^[-].*$/m;
break;

default:
prismLanguage = prism.languages[language];
break;
}

if (!prismLanguage) {
if (language) {
throw new Error(`unsupported language: "${language}", "${code}"`);
} else {
prismLanguage = prism.languages.jsx;
}
}

return prism.highlight(code, prismLanguage);
},
renderer,
};

const styles = (theme) => ({
root: {
...theme.typography.body1,
Expand Down Expand Up @@ -301,14 +204,90 @@ function MarkdownElement(props) {

const userLanguage = useSelector((state) => state.options.userLanguage);

// eslint-disable-next-line no-underscore-dangle
global.__MARKED_USER_LANGUAGE__ = userLanguage;
const renderedMarkdown = React.useMemo(() => {
return renderMarkdown(text, {
highlight(code, language) {
let prismLanguage;
switch (language) {
case 'ts':
prismLanguage = prism.languages.tsx;
break;

case 'js':
case 'sh':
prismLanguage = prism.languages.jsx;
break;

case 'diff':
prismLanguage = { ...prism.languages.diff };
// original `/^[-<].*$/m` matches lines starting with `<` which matches
// <SomeComponent />
// we will only use `-` as the deleted marker
prismLanguage.deleted = /^[-].*$/m;
break;

default:
prismLanguage = prism.languages[language];
break;
}

if (!prismLanguage) {
if (language) {
throw new Error(`unsupported language: "${language}", "${code}"`);
} else {
prismLanguage = prism.languages.jsx;
}
}

return prism.highlight(code, prismLanguage);
},
heading: (headingText, level) => {
// Small title. No need for an anchor.
// It's reducing the risk of duplicated id and it's fewer elements in the DOM.
if (level >= 4) {
return `<h${level}>${headingText}</h${level}>`;
}

// eslint-disable-next-line no-underscore-dangle
const hash = textToHash(headingText, global.__MARKED_UNIQUE__);

return [
`<h${level}>`,
`<a class="anchor-link" id="${hash}"></a>`,
headingText,
`<a class="anchor-link-style" aria-hidden="true" aria-label="anchor" href="#${hash}">`,
'<svg><use xlink:href="#anchor-link-icon" /></svg>',
'</a>',
`</h${level}>`,
].join('');
},
link: (href, title, linkText) => {
let more = '';

if (externs.some((domain) => href.indexOf(domain) !== -1)) {
more = ' target="_blank" rel="noopener nofollow"';
}

let finalHref = href;

if (
userLanguage !== 'en' &&
finalHref.indexOf('/') === 0 &&
finalHref !== '/size-snapshot'
) {
finalHref = `/${userLanguage}${finalHref}`;
}

return `<a href="${finalHref}"${more}>${linkText}</a>`;
},
});
}, [text, userLanguage]);

/* eslint-disable react/no-danger */
return (
<div
className={clsx(classes.root, 'markdown-body', className)}
dangerouslySetInnerHTML={{ __html: marked(text, markedOptions) }}
dangerouslySetInnerHTML={{ __html: renderedMarkdown }}
{...other}
/>
);
Expand Down
30 changes: 30 additions & 0 deletions docs/src/modules/utils/parseMarkdown.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import marked from 'marked/lib/marked';

const headerRegExp = /---[\r\n]([\s\S]*)[\r\n]---/;
const titleRegExp = /# (.*)[\r\n]/;
const descriptionRegExp = /<p class="description">(.*)<\/p>[\r\n]/;
Expand Down Expand Up @@ -63,3 +65,31 @@ export function getDescription(markdown) {

return matches[1];
}

/**
* Render markdown used in the Material-UI docs
*
* @param {string} markdown
* @param {object} [options]
* @param {function} [options.highlight] - https://marked.js.org/#/USING_ADVANCED.md#highlight
* @param {object} [options.rest] - properties from https://marked.js.org/#/USING_PRO.md#renderer
*/
export function render(markdown, options = {}) {
const { highlight, ...rendererOptions } = options;

const renderer = Object.assign(new marked.Renderer(), rendererOptions);

const markedOptions = {
gfm: true,
tables: true,
breaks: false,
pedantic: false,
sanitize: false,
smartLists: true,
smartypants: false,
highlight,
renderer,
};

return marked(markdown, markedOptions);
}