diff options
-rw-r--r-- | app/assets/javascripts/behaviors/copy_as_gfm.js | 12 | ||||
-rw-r--r-- | app/assets/javascripts/render_mermaid.js | 20 | ||||
-rw-r--r-- | lib/banzai/filter/mermaid_filter.rb | 11 | ||||
-rw-r--r-- | spec/features/copy_as_gfm_spec.rb | 96 | ||||
-rw-r--r-- | spec/features/markdown_spec.rb | 2 | ||||
-rw-r--r-- | spec/lib/banzai/filter/mermaid_filter_spec.rb | 4 |
6 files changed, 131 insertions, 14 deletions
diff --git a/app/assets/javascripts/behaviors/copy_as_gfm.js b/app/assets/javascripts/behaviors/copy_as_gfm.js index e7dc4ef8304..c6eca72c51b 100644 --- a/app/assets/javascripts/behaviors/copy_as_gfm.js +++ b/app/assets/javascripts/behaviors/copy_as_gfm.js @@ -74,6 +74,18 @@ const gfmRules = { return `![${el.dataset.title}](${el.getAttribute('src')})`; }, }, + MermaidFilter: { + 'svg.mermaid'(el, text) { + const sourceEl = el.querySelector('text.source'); + if (!sourceEl) return false; + + return `\`\`\`mermaid\n${CopyAsGFM.nodeToGFM(sourceEl)}\n\`\`\``; + }, + 'svg.mermaid style, svg.mermaid g'(el, text) { + // We don't want to include the content of these elements in the copied text. + return ''; + }, + }, MathFilter: { 'pre.code.math[data-math-style=display]'(el, text) { return `\`\`\`math\n${text.trim()}\n\`\`\``; diff --git a/app/assets/javascripts/render_mermaid.js b/app/assets/javascripts/render_mermaid.js index 41942c04a4e..b7cde6fb092 100644 --- a/app/assets/javascripts/render_mermaid.js +++ b/app/assets/javascripts/render_mermaid.js @@ -24,7 +24,25 @@ export default function renderMermaid($els) { }); $els.each((i, el) => { - mermaid.init(undefined, el); + const source = el.textContent; + + mermaid.init(undefined, el, (id) => { + const svg = document.getElementById(id); + + 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); + }); }); }).catch((err) => { Flash(`Can't load mermaid module: ${err}`); diff --git a/lib/banzai/filter/mermaid_filter.rb b/lib/banzai/filter/mermaid_filter.rb index b545b947a2c..65c131e08d9 100644 --- a/lib/banzai/filter/mermaid_filter.rb +++ b/lib/banzai/filter/mermaid_filter.rb @@ -2,16 +2,7 @@ module Banzai module Filter class MermaidFilter < HTML::Pipeline::Filter def call - doc.css('pre[lang="mermaid"]').add_class('mermaid') - doc.css('pre[lang="mermaid"]').add_class('js-render-mermaid') - - # The `<code></code>` blocks are added in the lib/banzai/filter/syntax_highlight_filter.rb - # We want to keep context and consistency, so we the blocks are added for all filters. - # Details: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15107/diffs?diff_id=7962900#note_45495859 - doc.css('pre[lang="mermaid"]').each do |pre| - document = pre.at('code') - document.replace(document.content) - end + doc.css('pre[lang="mermaid"] > code').add_class('js-render-mermaid') doc end diff --git a/spec/features/copy_as_gfm_spec.rb b/spec/features/copy_as_gfm_spec.rb index 1fcb8d5bc67..d8f1a919522 100644 --- a/spec/features/copy_as_gfm_spec.rb +++ b/spec/features/copy_as_gfm_spec.rb @@ -285,6 +285,102 @@ describe 'Copy as GFM', :js do end verify( + 'MermaidFilter: mermaid as converted from GFM to HTML', + + <<-GFM.strip_heredoc + ```mermaid + graph TD; + A-->B; + ``` + GFM + ) + + aggregate_failures('MermaidFilter: mermaid as transformed from HTML to SVG') do + gfm = <<-GFM.strip_heredoc + ```mermaid + graph TD; + A-->B; + ``` + GFM + + html = <<-HTML.strip_heredoc + <svg id="mermaidChart1" xmlns="http://www.w3.org/2000/svg" height="100%" viewBox="0 0 87.234375 174" style="max-width:87.234375px;" class="mermaid"> + <style> + .mermaid { + /* Flowchart variables */ + /* Sequence Diagram variables */ + /* Gantt chart variables */ + /** Section styling */ + /* Grid and axis */ + /* Today line */ + /* Task styling */ + /* Default task */ + /* Specific task settings for the sections*/ + /* Active task */ + /* Completed task */ + /* Tasks on the critical line */ + } + </style> + <g> + <g class="output"> + <g class="clusters"></g> + <g class="edgePaths"> + <g class="edgePath" style="opacity: 1;"> + <path class="path" d="M33.6171875,52L33.6171875,77L33.6171875,102" marker-end="url(#arrowhead65)" style="fill:none"></path> + <defs> + <marker id="arrowhead65" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"> + <path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path> + </marker> + </defs> + </g> + </g> + <g class="edgeLabels"> + <g class="edgeLabel" style="opacity: 1;" transform=""> + <g transform="translate(0,0)" class="label"> + <foreignObject width="0" height="0"> + <div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"> + <span class="edgeLabel"></span> + </div> + </foreignObject> + </g> + </g> + </g> + <g class="nodes"> + <g class="node" id="A" transform="translate(33.6171875,36)" style="opacity: 1;"> + <rect rx="0" ry="0" x="-13.6171875" y="-16" width="27.234375" height="32"></rect> + <g class="label" transform="translate(0,0)"> + <g transform="translate(-3.6171875,-6)"> + <foreignObject width="7.234375" height="12"> + <div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;">A</div> + </foreignObject> + </g> + </g> + </g> + <g class="node" id="B" transform="translate(33.6171875,118)" style="opacity: 1;"> + <rect rx="0" ry="0" x="-13.6171875" y="-16" width="27.234375" height="32"> + </rect> + <g class="label" transform="translate(0,0)"> + <g transform="translate(-3.6171875,-6)"> + <foreignObject width="7.234375" height="12"> + <div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;">B</div> + </foreignObject> + </g> + </g> + </g> + </g> + </g> + </g> + <text class="source" display="none">graph TD; + A-->B; + </text> + </svg> + HTML + + output_gfm = html_to_gfm(html) + expect(output_gfm.strip).to eq(gfm.strip) + end + + verify( 'SanitizationFilter', <<-GFM.strip_heredoc diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb index e285befc66f..a2b78a5e021 100644 --- a/spec/features/markdown_spec.rb +++ b/spec/features/markdown_spec.rb @@ -71,7 +71,7 @@ describe 'GitLab Markdown' do it 'parses mermaid code block' do aggregate_failures do - expect(doc).to have_selector('pre.code.js-render-mermaid') + expect(doc).to have_selector('pre[lang=mermaid] > code.js-render-mermaid') end end diff --git a/spec/lib/banzai/filter/mermaid_filter_spec.rb b/spec/lib/banzai/filter/mermaid_filter_spec.rb index 532d25e121d..f6474c8936d 100644 --- a/spec/lib/banzai/filter/mermaid_filter_spec.rb +++ b/spec/lib/banzai/filter/mermaid_filter_spec.rb @@ -3,9 +3,9 @@ require 'spec_helper' describe Banzai::Filter::MermaidFilter do include FilterSpecHelper - it 'adds `js-render-mermaid` class to the `pre` tag' do + it 'adds `js-render-mermaid` class to the `code` tag' do doc = filter("<pre class='code highlight js-syntax-highlight mermaid' lang='mermaid' v-pre='true'><code>graph TD;\n A-->B;\n</code></pre>") - result = doc.xpath('descendant-or-self::pre').first + result = doc.css('code').first expect(result[:class]).to include('js-render-mermaid') end |