Skip to content

Commit

Permalink
MermaidJS 10.3.1, accessibility features, handle MIME (#2034)
Browse files Browse the repository at this point in the history
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
bollwyvl and pre-commit-ci[bot] authored Aug 27, 2023
1 parent fad2fdf commit 9e8d252
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 31 deletions.
3 changes: 2 additions & 1 deletion nbconvert/exporters/html.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ class HTMLExporter(TemplateExporter):
).tag(config=True)

mermaid_js_url = Unicode(
"https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.2.3/mermaid.esm.min.mjs",
"https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.3.1/mermaid.esm.min.mjs",
help="""
URL to load MermaidJS from.
Expand Down Expand Up @@ -189,6 +189,7 @@ def default_config(self):
"text/html",
"text/markdown",
"image/svg+xml",
"text/vnd.mermaid",
"text/latex",
"image/png",
"image/jpeg",
Expand Down
36 changes: 36 additions & 0 deletions nbconvert/exporters/tests/test_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,42 @@ def test_javascript_output(self):
(output, resources) = HTMLExporter(template_name="classic").from_notebook_node(nb)
self.assertIn("javascript_output", output)

def test_mermaid_output(self):
nb = v4.new_notebook(
cells=[
v4.new_code_cell(
outputs=[
v4.new_output(
output_type="display_data",
data={"text/vnd.mermaid": "flowchart LR\na --> b"},
)
]
)
]
)
(output, resources) = HTMLExporter(template_name="lab").from_notebook_node(nb)
self.assertIn("""<div class="jp-Mermaid">""", output)
self.assertIn("""<pre class="mermaid">""", output)

def test_mermaid_prerendered_output(self):
nb = v4.new_notebook(
cells=[
v4.new_code_cell(
outputs=[
v4.new_output(
output_type="display_data",
data={
"image/svg+xml": "<svg></svg>",
"text/vnd.mermaid": "flowchart LR\na --> b",
},
)
]
)
]
)
(output, resources) = HTMLExporter(template_name="lab").from_notebook_node(nb)
self.assertNotIn("""<div class="jp-Mermaid">""", output)

def test_attachments(self):
(output, resources) = HTMLExporter(template_name="classic").from_file(
self._get_notebook(nb_name="attachment.ipynb")
Expand Down
3 changes: 3 additions & 0 deletions share/templates/base/display_priority.j2
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
{%- elif type == 'text/latex' -%}
{%- block data_latex -%}
{%- endblock -%}
{%- elif type == 'text/vnd.mermaid' -%}
{%- block data_mermaid -%}
{%- endblock -%}
{%- elif type == 'application/javascript' -%}
{%- block data_javascript -%}
{%- endblock -%}
Expand Down
8 changes: 8 additions & 0 deletions share/templates/lab/base.html.j2
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,14 @@ unknown type {{ cell.type }}
</div>
{%- endblock data_svg %}

{% block data_mermaid scoped -%}
<div class="jp-Mermaid">
<pre class="mermaid">
{{ output.data['text/vnd.mermaid'].strip() }}
</pre>
</div>
{%- endblock data_mermaid %}

{% block data_html scoped -%}
<div class="jp-RenderedHTMLCommon jp-RenderedHTML jp-OutputArea-output {{ extra_class }}" data-mime-type="text/html">
{%- if resources.should_sanitize_html %}
Expand Down
100 changes: 70 additions & 30 deletions share/templates/lab/mermaidjs.html.j2
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{%- macro mermaid_js(
url="https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.0.2/mermaid.esm.min.mjs"
url="https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.3.1/mermaid.esm.min.mjs"
) -%}
<script type="module">
document.addEventListener("DOMContentLoaded", async () => {
Expand All @@ -9,6 +9,7 @@ url="https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.0.2/mermaid.esm.min.mjs"
return;
}
const mermaid = (await import("{{ url }}")).default;
const parser = new DOMParser();
mermaid.initialize({
maxTextSize: 100000,
Expand All @@ -24,39 +25,52 @@ url="https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.0.2/mermaid.esm.min.mjs"
let _nextMermaidId = 0;
function makeMermaidImage(svg) {
const img = document.createElement('img');
const maxWidth = svg.match(/max-width: (\d+)/);
if (maxWidth && maxWidth[1]) {
const width = parseInt(maxWidth[1]);
if (width && !Number.isNaN(width) && Number.isFinite(width)) {
img.width = width;
}
const img = document.createElement("img");
const doc = parser.parseFromString(svg, "image/svg+xml");
const svgEl = doc.querySelector("svg");
const { maxWidth } = svgEl?.style || {};
const firstTitle = doc.querySelector("title");
const firstDesc = doc.querySelector("desc");
img.setAttribute("src", `data:image/svg+xml,${encodeURIComponent(svg)}`);
if (maxWidth) {
img.width = parseInt(maxWidth);
}
if (firstTitle) {
img.setAttribute("alt", firstTitle.textContent);
}
if (firstDesc) {
const caption = document.createElement("figcaption");
caption.className = "sr-only";
caption.textContent = firstDesc.textContent;
return [img, caption];
}
img.setAttribute('src', `data:image/svg+xml,${encodeURIComponent(svg)}`);
return img;
return [img];
}
async function makeMermaidError(text) {
let errorMessage = '';
let errorMessage = "";
try {
await mermaid.parse(text);
} catch (err) {
errorMessage = `${err}`;
}
const result = document.createElement('details');
const summary = document.createElement('summary');
const pre = document.createElement('pre');
const code = document.createElement('code');
const result = document.createElement("details");
result.className = 'jp-RenderedMermaid-Details';
const summary = document.createElement("summary");
summary.className = 'jp-RenderedMermaid-Summary';
const pre = document.createElement("pre");
const code = document.createElement("code");
code.innerText = text;
pre.appendChild(code);
summary.appendChild(pre);
result.appendChild(summary);
const warning = document.createElement('pre');
const warning = document.createElement("pre");
warning.innerText = errorMessage;
result.appendChild(warning);
return result;
return [result];
}
async function renderOneMarmaid(src) {
Expand All @@ -66,49 +80,75 @@ url="https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.0.2/mermaid.esm.min.mjs"
const el = document.createElement("div");
el.style.visibility = "hidden";
document.body.appendChild(el);
let result = null;
let results = null;
let output = null;
try {
const { svg } = await mermaid.render(id, raw, el);
result = makeMermaidImage(svg);
results = makeMermaidImage(svg);
output = document.createElement("figure");
results.map(output.appendChild, output);
} catch (err) {
parent.classList.add("jp-mod-warning");
result = await makeMermaidError(raw);
results = await makeMermaidError(raw);
output = results[0];
} finally {
el.remove();
}
parent.classList.add("jp-RenderedMermaid");
parent.appendChild(result);
parent.appendChild(output);
}
void Promise.all([...diagrams].map(renderOneMarmaid));
});
</script>
<style>
.jp-RenderedMarkdown .jp-Mermaid:not(.jp-RenderedMermaid) {
.jp-Mermaid:not(.jp-RenderedMermaid) {
display: none;
}
.jp-RenderedMarkdown .jp-RenderedMermaid.jp-mod-warning {
.jp-RenderedMermaid {
overflow: auto;
display: flex;
}
.jp-RenderedMermaid.jp-mod-warning {
width: auto;
padding: 10px;
padding: 0.5em;
margin-top: 0.5em;
border: var(--jp-border-width) solid var(--jp-warn-color2);
border-radius: var(--jp-border-radius);
color: var(--jp-ui-font-color1);
font-size: var(--jp-ui-font-size1);
white-space: pre-wrap;
word-wrap: break-word;
}
.jp-RenderedMarkdown .jp-RenderedMermaid.jp-mod-warning details > pre {
.jp-RenderedMermaid figure {
margin: 0;
overflow: auto;
max-width: 100%;
}
.jp-RenderedMermaid img {
max-width: 100%;
}
.jp-RenderedMermaid-Details > pre {
margin-top: 1em;
}
.jp-RenderedMarkdown .jp-RenderedMermaid.jp-mod-warning summary {
.jp-RenderedMermaid-Summary {
color: var(--jp-warn-color2);
}
.jp-RenderedMarkdown .jp-RenderedMermaid.jp-mod-warning summary > pre {
display: inline-block;
}
.jp-RenderedMermaid > .mermaid {
.jp-RenderedMermaid:not(.jp-mod-warning) pre {
display: none;
}
.jp-RenderedMermaid-Summary > pre {
display: inline-block;
white-space: normal;
}
</style>
<!-- End of mermaid configuration -->
{%- endmacro %}

0 comments on commit 9e8d252

Please sign in to comment.