diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-16 15:09:12 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-16 15:09:12 +0300 |
commit | cbfe03ae04a52d9825ff7cbeccdfe5d313adf6a2 (patch) | |
tree | e4879b35d019d3bbba1689f3ac4c48b81bf7b451 /app/assets/javascripts/behaviors | |
parent | 3fd97b4bba24ca412112aad025a38a32c7a6cf8c (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/behaviors')
-rw-r--r-- | app/assets/javascripts/behaviors/markdown/render_mermaid.js | 153 |
1 files changed, 108 insertions, 45 deletions
diff --git a/app/assets/javascripts/behaviors/markdown/render_mermaid.js b/app/assets/javascripts/behaviors/markdown/render_mermaid.js index b5e17a0587d..fe63ebd470d 100644 --- a/app/assets/javascripts/behaviors/markdown/render_mermaid.js +++ b/app/assets/javascripts/behaviors/markdown/render_mermaid.js @@ -1,6 +1,7 @@ import flash from '~/flash'; import $ from 'jquery'; -import { sprintf, __ } from '../../locale'; +import { __, sprintf } from '~/locale'; +import { once } from 'lodash'; // Renders diagrams and flowcharts from text using Mermaid in any element with the // `js-render-mermaid` class. @@ -18,14 +19,10 @@ import { sprintf, __ } from '../../locale'; // This is an arbitrary number; Can be iterated upon when suitable. const MAX_CHAR_LIMIT = 5000; +let mermaidModule = {}; -function renderMermaids($els) { - if (!$els.length) return; - - // 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; - - import(/* webpackChunkName: 'mermaid' */ 'mermaid') +function importMermaidModule() { + return import(/* webpackChunkName: 'mermaid' */ 'mermaid') .then(mermaid => { mermaid.initialize({ // mermaid core options @@ -41,63 +38,127 @@ function renderMermaids($els) { securityLevel: 'strict', }); + mermaidModule = mermaid; + + return mermaid; + }) + .catch(err => { + flash(sprintf(__("Can't load mermaid module: %{err}"), { err })); + // eslint-disable-next-line no-console + console.error(err); + }); +} + +function fixElementSource(el) { + // 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>'); + + // Remove any extra spans added by the backend syntax highlighting. + Object.assign(el, { textContent: source }); + + return { source }; +} + +function renderMermaidEl(el) { + mermaidModule.init(undefined, el, id => { + const source = el.textContent; + const svg = document.getElementById(id); + + // 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; + } + + svg.classList.add('mermaid'); + + // pre > code > svg + svg.closest('pre').replaceWith(svg); + + // 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; + + svg.appendChild(sourceEl); + }); +} + +function renderMermaids($els) { + if (!$els.length) return; + + // 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; + + importMermaidModule() + .then(() => { let renderedChars = 0; $els.each((i, el) => { - // 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>'); - + const { source } = fixElementSource(el); /** * Restrict the rendering to a certain amount of character to * prevent mermaidjs from hanging up the entire thread and * causing a DoS. */ if ((source && source.length > MAX_CHAR_LIMIT) || renderedChars > MAX_CHAR_LIMIT) { - el.textContent = sprintf( - __( - 'Cannot render the image. Maximum character count (%{charLimit}) has been exceeded.', - ), - { charLimit: MAX_CHAR_LIMIT }, - ); + const html = ` + <div class="alert gl-alert gl-alert-warning alert-dismissible lazy-render-mermaid-container js-lazy-render-mermaid-container fade show" role="alert"> + <div> + <div class="display-flex"> + <div>${__( + 'Warning: Displaying this diagram might cause performance issues on this page.', + )}</div> + <div class="gl-alert-actions"> + <button class="js-lazy-render-mermaid btn gl-alert-action btn-warning btn-md new-gl-button">Display</button> + </div> + </div> + <button type="button" class="close" data-dismiss="alert" aria-label="Close"> + <span aria-hidden="true">×</span> + </button> + </div> + </div> + `; + + const $parent = $(el).parent(); + + if (!$parent.hasClass('lazy-alert-shown')) { + $parent.after(html); + $parent.addClass('lazy-alert-shown'); + } + return; } renderedChars += source.length; - // Remove any extra spans added by the backend syntax highlighting. - Object.assign(el, { textContent: source }); - - mermaid.init(undefined, el, id => { - const svg = document.getElementById(id); - - // 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; - } - - svg.classList.add('mermaid'); - - // pre > code > svg - svg.closest('pre').replaceWith(svg); - // 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; - - svg.appendChild(sourceEl); - }); + renderMermaidEl(el); }); }) .catch(err => { - flash(`Can't load mermaid module: ${err}`); + flash(sprintf(__('Encountered an error while rendering: %{err}'), { err })); + // eslint-disable-next-line no-console + console.error(err); }); } +const hookLazyRenderMermaidEvent = once(() => { + $(document.body).on('click', '.js-lazy-render-mermaid', function eventHandler() { + const parent = $(this).closest('.js-lazy-render-mermaid-container'); + const pre = parent.prev(); + + const el = pre.find('.js-render-mermaid'); + + parent.remove(); + + renderMermaidEl(el); + }); +}); + export default function renderMermaid($els) { if (!$els.length) return; @@ -112,4 +173,6 @@ export default function renderMermaid($els) { renderMermaids($(this).find('.js-render-mermaid')); } }); + + hookLazyRenderMermaidEvent(); } |