2018-05-09 12:01:36 +05:30
|
|
|
import flash from '~/flash';
|
2020-03-13 15:44:24 +05:30
|
|
|
import $ from 'jquery';
|
2019-03-13 22:55:13 +05:30
|
|
|
import { sprintf, __ } from '../../locale';
|
2018-05-09 12:01:36 +05:30
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
// Renders diagrams and flowcharts from text using Mermaid in any element with the
|
|
|
|
// `js-render-mermaid` class.
|
|
|
|
//
|
|
|
|
// Example markup:
|
|
|
|
//
|
|
|
|
// <pre class="js-render-mermaid">
|
|
|
|
// graph TD;
|
|
|
|
// A-- > B;
|
|
|
|
// A-- > C;
|
|
|
|
// B-- > D;
|
|
|
|
// C-- > D;
|
|
|
|
// </pre>
|
|
|
|
//
|
|
|
|
|
2019-09-04 21:01:54 +05:30
|
|
|
// This is an arbitrary number; Can be iterated upon when suitable.
|
2019-03-13 22:55:13 +05:30
|
|
|
const MAX_CHAR_LIMIT = 5000;
|
|
|
|
|
2020-03-13 15:44:24 +05:30
|
|
|
function renderMermaids($els) {
|
2018-03-17 18:26:18 +05:30
|
|
|
if (!$els.length) return;
|
|
|
|
|
2020-03-13 15:44:24 +05:30
|
|
|
// A diagram may have been truncated in search results which will cause errors, so abort the render.
|
|
|
|
if (document.querySelector('body').dataset.page === 'search:show') return;
|
|
|
|
|
2018-12-13 13:39:08 +05:30
|
|
|
import(/* webpackChunkName: 'mermaid' */ 'mermaid')
|
|
|
|
.then(mermaid => {
|
|
|
|
mermaid.initialize({
|
|
|
|
// mermaid core options
|
|
|
|
mermaid: {
|
|
|
|
startOnLoad: false,
|
|
|
|
},
|
|
|
|
// mermaidAPI options
|
|
|
|
theme: 'neutral',
|
|
|
|
flowchart: {
|
|
|
|
htmlLabels: false,
|
|
|
|
},
|
2019-09-30 23:59:55 +05:30
|
|
|
securityLevel: 'strict',
|
2018-12-13 13:39:08 +05:30
|
|
|
});
|
2018-03-17 18:26:18 +05:30
|
|
|
|
2019-09-30 23:59:55 +05:30
|
|
|
let renderedChars = 0;
|
|
|
|
|
2018-12-13 13:39:08 +05:30
|
|
|
$els.each((i, el) => {
|
2019-09-30 21:07:59 +05:30
|
|
|
// Mermaid doesn't like `<br />` tags, so collapse all like tags into `<br>`, which is parsed correctly.
|
|
|
|
const source = el.textContent.replace(/<br\s*\/>/g, '<br>');
|
2018-03-17 18:26:18 +05:30
|
|
|
|
2019-03-13 22:55:13 +05:30
|
|
|
/**
|
|
|
|
* Restrict the rendering to a certain amount of character to
|
|
|
|
* prevent mermaidjs from hanging up the entire thread and
|
|
|
|
* causing a DoS.
|
|
|
|
*/
|
2019-09-30 23:59:55 +05:30
|
|
|
if ((source && source.length > MAX_CHAR_LIMIT) || renderedChars > MAX_CHAR_LIMIT) {
|
2019-03-13 22:55:13 +05:30
|
|
|
el.textContent = sprintf(
|
|
|
|
__(
|
|
|
|
'Cannot render the image. Maximum character count (%{charLimit}) has been exceeded.',
|
|
|
|
),
|
|
|
|
{ charLimit: MAX_CHAR_LIMIT },
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-09-30 23:59:55 +05:30
|
|
|
renderedChars += source.length;
|
2018-12-13 13:39:08 +05:30
|
|
|
// Remove any extra spans added by the backend syntax highlighting.
|
|
|
|
Object.assign(el, { textContent: source });
|
2018-03-17 18:26:18 +05:30
|
|
|
|
2018-12-13 13:39:08 +05:30
|
|
|
mermaid.init(undefined, el, id => {
|
|
|
|
const svg = document.getElementById(id);
|
2018-03-17 18:26:18 +05:30
|
|
|
|
2019-09-30 21:07:59 +05:30
|
|
|
// As of https://github.com/knsv/mermaid/commit/57b780a0d,
|
|
|
|
// Mermaid will make two init callbacks:one to initialize the
|
|
|
|
// flow charts, and another to initialize the Gannt charts.
|
|
|
|
// Guard against an error caused by double initialization.
|
|
|
|
if (svg.classList.contains('mermaid')) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-12-13 13:39:08 +05:30
|
|
|
svg.classList.add('mermaid');
|
2018-03-17 18:26:18 +05:30
|
|
|
|
2018-12-13 13:39:08 +05:30
|
|
|
// pre > code > svg
|
|
|
|
svg.closest('pre').replaceWith(svg);
|
2018-03-17 18:26:18 +05:30
|
|
|
|
2018-12-13 13:39:08 +05:30
|
|
|
// We need to add the original source into the DOM to allow Copy-as-GFM
|
|
|
|
// to access it.
|
|
|
|
const sourceEl = document.createElement('text');
|
|
|
|
sourceEl.classList.add('source');
|
|
|
|
sourceEl.setAttribute('display', 'none');
|
|
|
|
sourceEl.textContent = source;
|
2018-03-17 18:26:18 +05:30
|
|
|
|
2018-12-13 13:39:08 +05:30
|
|
|
svg.appendChild(sourceEl);
|
|
|
|
});
|
2018-03-17 18:26:18 +05:30
|
|
|
});
|
2018-12-13 13:39:08 +05:30
|
|
|
})
|
|
|
|
.catch(err => {
|
|
|
|
flash(`Can't load mermaid module: ${err}`);
|
2018-03-17 18:26:18 +05:30
|
|
|
});
|
|
|
|
}
|
2020-03-13 15:44:24 +05:30
|
|
|
|
|
|
|
export default function renderMermaid($els) {
|
|
|
|
if (!$els.length) return;
|
|
|
|
|
|
|
|
const visibleMermaids = $els.filter(function filter() {
|
|
|
|
return $(this).closest('details').length === 0;
|
|
|
|
});
|
|
|
|
|
|
|
|
renderMermaids(visibleMermaids);
|
|
|
|
|
|
|
|
$els.closest('details').one('toggle', function toggle() {
|
|
|
|
if (this.open) {
|
|
|
|
renderMermaids($(this).find('.js-render-mermaid'));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|