Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mermaidjs 10.2.3 #1957

Merged
merged 12 commits into from
Jun 13, 2023
10 changes: 10 additions & 0 deletions nbconvert/exporters/html.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,15 @@ class HTMLExporter(TemplateExporter):
""",
).tag(config=True)

mermaid_js_url = Unicode(
"https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.2.3/mermaid.esm.min.mjs",
help="""
URL to load MermaidJS from.

Defaults to loading from cdnjs.
""",
)

jquery_url = Unicode(
"https://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js",
help="""
Expand Down Expand Up @@ -303,6 +312,7 @@ def resources_include_url(name):
resources["include_url"] = resources_include_url
resources["require_js_url"] = self.require_js_url
resources["mathjax_url"] = self.mathjax_url
resources["mermaid_js_url"] = self.mermaid_js_url
resources["jquery_url"] = self.jquery_url
resources["jupyter_widgets_base_url"] = self.jupyter_widgets_base_url
resources["widget_renderer_url"] = self.widget_renderer_url
Expand Down
12 changes: 12 additions & 0 deletions nbconvert/filters/markdown_mistune.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,10 @@ def block_code(self, code, info=None):
"""Handle block code."""
lang = ""
lexer = None

if info and info.startswith("mermaid"):
return self.block_mermaidjs(code)

if info:
try:
lang = info.strip().split(None, 1)[0]
Expand All @@ -190,6 +194,14 @@ def block_code(self, code, info=None):
formatter = HtmlFormatter()
return highlight(code, lexer, formatter)

def block_mermaidjs(self, code, info=None):
"""Handle mermaid syntax."""
return (
"""<div class="jp-Mermaid"><pre class="mermaid">\n"""
f"""{code.strip()}"""
"""\n</pre></div>"""
)

def block_html(self, html):
"""Handle block html."""
if self.embed_images:
Expand Down
13 changes: 13 additions & 0 deletions nbconvert/filters/tests/test_markdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,19 @@ def test_markdown2rst(self):
tokens[index],
)

def test_mermaid_markdown(self):
code = """flowchart LR
chicken --> egg --> chicken"""
case = f"""```mermaid\n {code}\n```"""

output_check = (
"""<div class="jp-Mermaid"><pre class="mermaid">\n"""
f"""{code.strip()}"""
"""\n</pre></div>"""
)

self._try_markdown(markdown2html, case, output_check)

def _try_markdown(self, method, test, tokens):
results = method(test)
if isinstance(tokens, (str,)):
Expand Down
6 changes: 6 additions & 0 deletions share/templates/classic/index.html.j2
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@
{%- block html_head_js_requirejs -%}
<script src="{{ resources.require_js_url }}"></script>
{%- endblock html_head_js_requirejs -%}
{%- block html_head_js_mermaidjs -%}
<script type="module">
import mermaid from '{{ resources.mermaid_js_url }}';
mermaid.initialize({ startOnLoad: true });
</script>
{%- endblock html_head_js_mermaidjs -%}
{%- endblock html_head_js -%}

{% block jupyter_widgets %}
Expand Down
5 changes: 5 additions & 0 deletions share/templates/lab/index.html.j2
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{%- extends 'base.html.j2' -%}
{% from 'mathjax.html.j2' import mathjax %}
{% from 'mermaidjs.html.j2' import mermaid_js %}
{% from 'jupyter_widgets.html.j2' import jupyter_widgets %}

{%- block header -%}
Expand Down Expand Up @@ -149,6 +150,10 @@ body[data-format='mobile'] .jp-OutputArea-child .jp-OutputArea-output {
{{ mathjax(resources.mathjax_url) }}
{%- endblock html_head_js_mathjax -%}

{%- block html_head_js_mermaidjs -%}
{{ mermaid_js(resources.mermaid_js_url) }}
{%- endblock html_head_js_mermaidjs -%}

{%- block html_head_css -%}
{%- endblock html_head_css -%}

Expand Down
114 changes: 114 additions & 0 deletions share/templates/lab/mermaidjs.html.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
{%- macro mermaid_js(
url="https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.0.2/mermaid.esm.min.mjs"
) -%}
<script type="module">
document.addEventListener("DOMContentLoaded", async () => {
const diagrams = document.querySelectorAll(".jp-Mermaid > pre.mermaid");
// do not load mermaidjs if not needed
if (!diagrams.length) {
return;
}
const mermaid = (await import("{{ url }}")).default;

mermaid.initialize({
maxTextSize: 100000,
startOnLoad: false,
fontFamily: window
.getComputedStyle(document.body)
.getPropertyValue("--jp-ui-font-family"),
theme: document.querySelector("body[data-jp-theme-light='true']")
? "default"
: "dark",
});

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;
}
}
img.setAttribute('src', `data:image/svg+xml,${encodeURIComponent(svg)}`);
return img;
}

async function makeMermaidError(text) {
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');
code.innerText = text;
pre.appendChild(code);
summary.appendChild(pre);
result.appendChild(summary);

const warning = document.createElement('pre');
warning.innerText = errorMessage;
result.appendChild(warning);
return result;
}

async function renderOneMarmaid(src) {
const id = `jp-mermaid-${_nextMermaidId++}`;
const parent = src.parentNode;
let raw = src.textContent.trim();
const el = document.createElement("div");
el.style.visibility = "hidden";
document.body.appendChild(el);
let result = null;
try {
const { svg } = await mermaid.render(id, raw, el);
result = makeMermaidImage(svg);
} catch (err) {
parent.classList.add("jp-mod-warning");
result = await makeMermaidError(raw);
} finally {
el.remove();
}
parent.classList.add("jp-RenderedMermaid");
parent.appendChild(result);
}

void Promise.all([...diagrams].map(renderOneMarmaid));
});
</script>
<style>
.jp-RenderedMarkdown .jp-Mermaid:not(.jp-RenderedMermaid) {
display: none;
}
.jp-RenderedMarkdown .jp-RenderedMermaid.jp-mod-warning {
width: auto;
padding: 10px;
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 {
margin-top: 1em;
}
.jp-RenderedMarkdown .jp-RenderedMermaid.jp-mod-warning summary {
color: var(--jp-warn-color2);
}
.jp-RenderedMarkdown .jp-RenderedMermaid.jp-mod-warning summary > pre {
display: inline-block;
}
.jp-RenderedMermaid > .mermaid {
display: none;
}
</style>
<!-- End of mermaid configuration -->
{%- endmacro %}
6 changes: 6 additions & 0 deletions share/templates/reveal/index.html.j2
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@
{%- block html_head_js_requirejs -%}
<script src="{{ resources.require_js_url }}"></script>
{%- endblock html_head_js_requirejs -%}
{%- block html_head_js_mermaidjs -%}
<script type="module">
import mermaid from '{{ resources.mermaid_js_url }}';
mermaid.initialize({ startOnLoad: true });
</script>
{%- endblock html_head_js_mermaidjs -%}
{%- endblock html_head_js -%}

{% block jupyter_widgets %}
Expand Down