MDExMermaid (MDExMermaid v0.3.0)

View Source

MDEx plugin for Mermaid.

Usage

Mix.install([
  {:mdex_mermaid, "~> 0.1"}
])

markdown = """
# Flowchart

```mermaid
graph TD;
    A-->B;
    A-->C;
    B-->D;
    C-->D;
```
"""

mdex = MDEx.new() |> MDExMermaid.attach()

MDEx.to_html!(mdex, document: markdown) |> IO.puts()
#=>
# <script type="module">
#   import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
#   const theme = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'default';
#   mermaid.initialize({securityLevel: 'loose', theme: theme});
# </script>
# <h1>Flowchart</h1>
# <pre id="mermaid-1" class="mermaid" phx-update="ignore">graph TD;
#     A-->B;
#     A-->C;
#     B-->D;
#     C-->D;
# </pre>

See attach/2 for more info.

Summary

Functions

Attaches the MDExMermaid plugin into the MDEx pipeline.

Types

mermaid_pre_attrs()

@type mermaid_pre_attrs() :: (seq :: pos_integer() -> String.t())

Functions

attach(pipe, options \\ [])

@spec attach(
  MDEx.Pipe.t(),
  keyword()
) :: MDEx.Pipe.t()

Attaches the MDExMermaid plugin into the MDEx pipeline.

Options

  • :mermaid_pre_attrs (mermaid_pre_attrs/0) - Function that generates the <pre> tag attributes for mermaid code blocks.
  • :mermaid_init (String.t/0) - The HTML tag(s) to inject into the document to initialize mermaid. If nil, the default script is used (see below).

:mermaid_pre_attrs

Whenever a code block tagged as mermaid is found, it gets converted into a <pre> tag using the following function to generate its attributes:

pre_attrs = fn seq -> ~s(id="mermaid-#{seq}" class="mermaid" phx-update="ignore") end
mdex = MDEx.new() |> MDExMermaid.attach(mermaid_pre_attrs: pre_attrs)

Which results in:

<pre id="mermaid-1" class="mermaid" phx-update="ignore">
  flowchart LR
    ...
</pre>

You can override it to include or manipulate the attributes but it's important to maintain unique IDs for each instance, otherwise the mermaid rendering will not work correctly, for eg:

fn seq -> ~s(id="mermaid-#{seq}" class="mermaid graph" phx-hook="MermaidHook" phx-update="ignore") end

:mermaid_init

The option :mermaid_init can be used to manipulate how mermaid is initialized. By default, the following script is injected into the top of the document:

<script type="module">
  import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs";
  const theme = window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "default";
  mermaid.initialize({securityLevel: "loose", theme: theme});
</script>

That script works well on static documents but you'll need to adjust it to initialize mermaid in environments that requires waiting for the DOM to be ready.

Examples

DOMContentLoaded

@mermaid_init """
<script defer src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs"></script>
<script>
  document.addEventListener("DOMContentLoaded", () => {
    const theme = window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "default";
    mermaid.initialize({securityLevel: "loose", theme: theme});
</script>
"""

mdex = MDEx.new() |> MDExMermaid.attach(mermaid_init: @mermaid_init)

Phoenix LiveView

To use MDExMermaid with Phoenix LiveView, you can:

  1. Load Mermaid (via CDN or npm)
  2. Create a LiveView hook to render diagrams
  3. Configure MDExMermaid with the appropriate attributes

Option 1: Using CDN

In your layout:

<script type="module">
  import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
  const theme = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'default';
  mermaid.initialize({ startOnLoad: false, securityLevel: 'loose', theme: theme });
  window.mermaid = mermaid;
</script>

Option 2: Using npm

Install Mermaid as a dependency:

cd assets && npm install mermaid

In your assets/js/app.js:

import mermaid from 'mermaid'

const theme = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'default';

mermaid.initialize({ 
  startOnLoad: false, 
  securityLevel: 'loose',
  theme: theme,
})

let hooks = {
  MermaidHook: {
    mounted() {
      mermaid.run({
        querySelector: '.mermaid'
      });
    }
  }
}

let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}, hooks: hooks})

Using in LiveView

html =
  MDEx.new()
  |> MDExMermaid.attach(
    mermaid_init: "", # already initialized
    mermaid_pre_attrs: fn seq ->
      ~s(id="mermaid-#{seq}" class="mermaid" phx-hook="MermaidHook" phx-update="ignore")
    end
  )
  |> MDEx.to_html!(document: markdown)

assign(socket, html: {:safe, html})}

Note that you can attach a JS hook per diagram or in a parent element to handle all diagrams at once, depending on your needs.

See this LiveView example