Skip to content

Commit

Permalink
Make mermaid diagrams captionable figures.
Browse files Browse the repository at this point in the history
  • Loading branch information
christhekeele committed Oct 13, 2024
1 parent 5e5b292 commit 3f6b667
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 35 deletions.
22 changes: 20 additions & 2 deletions assets/packs/mermaid/main.css
Original file line number Diff line number Diff line change
@@ -1,9 +1,27 @@
#mermaid button#download {
#figure {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}

#figure figcaption {
border-radius: .5rem;
background-color: rgb(240 245 249);
padding: 0.5rem;
font-size: .875rem;
line-height: 1.25rem;
font-weight: 500;
color: rgb(97 117 138);
}

#contents button#download {
position: absolute;
display: none;
}

#mermaid:hover button#download {
#contents:hover button#download {
display: inline;
right: 0;
}

74 changes: 45 additions & 29 deletions assets/packs/mermaid/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,61 @@ import "./main.css";

mermaid.initialize({ startOnLoad: false });

export function init(ctx, {content, title}) {
export function init(ctx, {graph, caption, download}) {
ctx.importCSS("main.css")

function render() {
mermaid.render("graph1", content).then(({ svg, bindFunctions }) => {
ctx.root.innerHTML = `
<div id="mermaid">
${svg}
<button id="download" title="Download ${title}">⇩</button>
</div>
`;
mermaid.render("graph1", graph).then(({ svg, bindFunctions }) => {
let contents = document.createElement("div");
contents.id = "contents";
ctx.root.appendChild(contents);

ctx.root.querySelector("#download").addEventListener("click", (event) => {
var binaryData = [];
binaryData.push(svg);
const downloadBlob = URL.createObjectURL(new Blob(binaryData, {type: "image/svg+xml"}));

const downloadLink = document.createElement("a");
downloadLink.href = downloadBlob;
downloadLink.download = `${title}.svg`;
document.body.appendChild(downloadLink);

downloadLink.dispatchEvent(
new MouseEvent('click', {
bubbles: true,
cancelable: true,
view: window
})
);

document.body.removeChild(downloadLink);
});
let figure = document.createElement("figure");
figure.id = "figure";
figure.innerHTML = svg;
contents.appendChild(figure);

if (caption) {
let figcaption = document.createElement("figcaption");
figcaption.textContent = caption;
figure.appendChild(figcaption);
}

if (download) {
let downloadButton = document.createElement("button");
downloadButton.id = "download"
downloadButton.title = `Download ${download.title}`
downloadButton.textContent = "⇩"
contents.prepend(downloadButton);

contents.querySelector("#download").addEventListener("click", (event) => {
var binaryData = [];
binaryData.push(svg);
const downloadBlob = URL.createObjectURL(new Blob(binaryData, {type: "image/svg+xml"}));

const downloadLink = document.createElement("a");
downloadLink.href = downloadBlob;
downloadLink.download = download.filename;
contents.appendChild(downloadLink);

downloadLink.dispatchEvent(
new MouseEvent('click', {
bubbles: true,
cancelable: true,
view: window
})
);

contents.removeChild(downloadLink);
});
}

if (bindFunctions) {
bindFunctions(ctx.root);
}

// A workaround for https://github.com/mermaid-js/mermaid/issues/1758
const svgEl = ctx.root.querySelector("svg");
const svgEl = figure.querySelector("svg");
svgEl.removeAttribute("height");
});
}
Expand Down
40 changes: 36 additions & 4 deletions lib/kino/mermaid.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,45 @@ defmodule Kino.Mermaid do

@type t :: Kino.JS.t()

@download_defaults [title: "Diagram", filename: "diagram.svg"]

@doc """
Creates a new kino displaying the given Mermaid graph.
## Options
* `:caption` - an optional caption for the rendered diagram. Defaults to `false`.
* `:download` - whether or not to allow downloading the rendered Mermaid svg.
Defaults to `true`.
Downloads can be further customized by providing a keyword list
instead of a boolean, containing:
* `:title` - The alt text displayed for the download button.
* `:filename` - The name of the file to be downloaded.
"""
@spec new(binary(), Keyword.t()) :: t()
def new(content, options \\ []) do
options = Keyword.validate!(options, [:title])
title = Keyword.get(options, :title, "diagram")
Kino.JS.new(__MODULE__, %{content: content, title: title}, export: fn content -> {"mermaid", content} end)
def new(graph, options \\ []) do
options = Keyword.validate!(options, caption: false, download: true)

download =
if download = Keyword.fetch!(options, :download) do
case download do
true ->
@download_defaults

download_options when is_list(download_options) ->
Keyword.validate!(download_options, @download_defaults)
end
|> Map.new()
end

caption = Keyword.fetch!(options, :caption)

Kino.JS.new(__MODULE__, %{graph: graph, caption: caption, download: download},
export: fn graph -> {"mermaid", graph} end
)
end
end

0 comments on commit 3f6b667

Please sign in to comment.