diff --git a/docs/src/modules/components/AppTableOfContents.js b/docs/src/modules/components/AppTableOfContents.js
index 7cd92269a00257..217333658de183 100644
--- a/docs/src/modules/components/AppTableOfContents.js
+++ b/docs/src/modules/components/AppTableOfContents.js
@@ -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';
@@ -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 = [];
@@ -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([]);
diff --git a/docs/src/modules/components/MarkdownElement.js b/docs/src/modules/components/MarkdownElement.js
index 1ca0f06935ca05..bd60873db434bc 100644
--- a/docs/src/modules/components/MarkdownElement.js
+++ b/docs/src/modules/components/MarkdownElement.js
@@ -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
-// 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 `
(.*)<\/p>[\r\n]/; @@ -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); +}