Commit 3d162d19 authored by Jacob Schatz's avatar Jacob Schatz

Merge branch 'dm-copy-mermaid-as-gfm' into 'master'

Copy Mermaid graphs as GFM

Closes #41211

See merge request gitlab-org/gitlab-ce!16046
parents b2b78017 288b2760
......@@ -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\`\`\``;
......
......@@ -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}`);
......
......@@ -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
......
......@@ -284,6 +284,102 @@ describe 'Copy as GFM', :js do
expect(output_gfm.strip).to eq(gfm.strip)
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--&gt;B;
</text>
</svg>
HTML
output_gfm = html_to_gfm(html)
expect(output_gfm.strip).to eq(gfm.strip)
end
verify(
'SanitizationFilter',
......
......@@ -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
......
......@@ -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--&gt;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
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment