Current section

Files

Jump to
mdex_mermaid usage-rules.md
Raw

usage-rules.md

# MDExMermaid Usage Rules
MDExMermaid is a plugin for [MDEx](https://hex.pm/packages/mdex) that enables rendering [Mermaid](https://mermaid.js.org) diagrams from markdown code blocks.
## When to Use
Use MDExMermaid when you need to:
- Render Mermaid diagrams from markdown content
- Support flowcharts, sequence diagrams, gantt charts, and other Mermaid diagram types
- Integrate Mermaid diagrams into Phoenix LiveView applications
- Convert markdown with Mermaid code blocks to HTML
## Core API
### Basic Usage
The main function is `MDExMermaid.attach/2` which attaches the plugin to an MDEx document:
````elixir
markdown = """
```mermaid
graph TD;
A-->B;
```
"""
mdex = MDEx.new(markdown: markdown) |> MDExMermaid.attach()
html = MDEx.to_html!(mdex)
````
### Function Signature
```elixir
MDExMermaid.attach(document, options \\ []) :: MDEx.Document.t()
```
**Options:**
- `:mermaid_init` - HTML to initialize Mermaid (default: auto-inject CDN script)
- `:mermaid_pre_attrs` - Function to generate `<pre>` tag attributes
## Common Patterns
### Static HTML Documents
For simple static documents, the default configuration works out of the box:
```elixir
MDEx.new(markdown: markdown)
|> MDExMermaid.attach()
|> MDEx.to_html!()
```
This injects the default initialization script and renders all mermaid code blocks.
### Custom Pre Attributes
The `:mermaid_pre_attrs` option takes a function that receives a sequence number and returns attribute string:
```elixir
pre_attrs = fn seq ->
~s(id="diagram-#{seq}" class="mermaid custom-class")
end
MDEx.new(markdown: markdown)
|> MDExMermaid.attach(mermaid_pre_attrs: pre_attrs)
|> MDEx.to_html!()
```
**IMPORTANT:** Always include unique IDs in custom pre attributes. The sequence number ensures uniqueness.
### Phoenix LiveView Integration
For LiveView apps, disable auto-initialization since you'll manage Mermaid in your JS:
```elixir
# In your LiveView
def render(assigns) do
~H"""
<div><%= {:safe, @html} %></div>
"""
end
def mount(_params, _session, socket) do
html =
MDEx.new(markdown: markdown)
|> MDExMermaid.attach(
mermaid_init: "", # Don't inject init script
mermaid_pre_attrs: fn seq ->
~s(id="mermaid-#{seq}" class="mermaid" phx-hook="MermaidHook" phx-update="ignore")
end
)
|> MDEx.to_html!()
{:ok, assign(socket, html: html)}
end
```
**Client-side JavaScript (using npm):**
```javascript
// assets/js/app.js
import mermaid from 'mermaid'
mermaid.initialize({
startOnLoad: false,
securityLevel: 'loose',
theme: 'default'
})
let hooks = {
MermaidHook: {
mounted() {
mermaid.run({ querySelector: '.mermaid' })
}
}
}
let liveSocket = new LiveSocket("/live", Socket, {
params: {_csrf_token: csrfToken},
hooks: hooks
})
```
**Client-side JavaScript (using CDN in layout):**
```html
<!-- In your root layout -->
<script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
const theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'default';
mermaid.initialize({ startOnLoad: false, securityLevel: 'loose', theme: theme });
window.mermaid = mermaid;
</script>
```
### DOMContentLoaded Pattern
For pages that require waiting for DOM ready:
```elixir
@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('(prefers-color-scheme: dark)').matches ? 'dark' : 'default';
mermaid.initialize({securityLevel: 'loose', theme: theme});
});
</script>
"""
MDEx.new(markdown: markdown)
|> MDExMermaid.attach(mermaid_init: @mermaid_init)
|> MDEx.to_html!()
```
### Theme Support
The default configuration auto-detects dark/light mode based on `prefers-color-scheme`:
```javascript
const theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'default';
mermaid.initialize({ theme: theme });
```
For custom themes, override `:mermaid_init` with your own configuration.
## Best Practices
### Always Use Unique IDs
Each mermaid diagram must have a unique ID for proper rendering:
```elixir
# GOOD - uses sequence number for uniqueness
fn seq -> ~s(id="mermaid-#{seq}" class="mermaid") end
# BAD - all diagrams have same ID
fn _seq -> ~s(id="mermaid" class="mermaid") end
```
### Include phx-update="ignore" in LiveView
Always use `phx-update="ignore"` to prevent LiveView from re-rendering diagrams:
```elixir
fn seq -> ~s(id="mermaid-#{seq}" class="mermaid" phx-update="ignore") end
```
### Set startOnLoad: false in LiveView
When using with LiveView, disable auto-start and use hooks for manual control:
```javascript
mermaid.initialize({ startOnLoad: false })
```
### Security Level
Use `securityLevel: 'loose'` for most applications. Use `'strict'` only if you're rendering untrusted user content:
```javascript
// For trusted content (most apps)
mermaid.initialize({ securityLevel: 'loose' })
// For untrusted user content
mermaid.initialize({ securityLevel: 'strict' })
```
## Common Anti-Patterns
### DON'T: Duplicate initialization
```elixir
# BAD - Mermaid initialized both in layout AND via plugin
MDEx.new(markdown: markdown)
|> MDExMermaid.attach() # Injects init script
|> MDEx.to_html!()
# ... and also <script> in layout
```
**Instead:** Choose one initialization method - either let the plugin handle it or manage it yourself with `mermaid_init: ""`.
### DON'T: Forget sequence numbers in custom attrs
```elixir
# BAD - ignores sequence number
fn _seq -> ~s(id="mermaid-1" class="mermaid") end
```
**Instead:** Always use the sequence parameter:
```elixir
fn seq -> ~s(id="mermaid-#{seq}" class="mermaid") end
```
### DON'T: Use startOnLoad: true with LiveView
```javascript
// BAD - conflicts with LiveView rendering
mermaid.initialize({ startOnLoad: true })
```
**Instead:** Use hooks and manual rendering:
```javascript
mermaid.initialize({ startOnLoad: false })
// Then use hooks to call mermaid.run()
```
### DON'T: Forget phx-update="ignore"
```elixir
# BAD - LiveView will re-render and break diagrams
fn seq -> ~s(id="mermaid-#{seq}" class="mermaid" phx-hook="MermaidHook") end
```
**Instead:**
```elixir
fn seq -> ~s(id="mermaid-#{seq}" class="mermaid" phx-hook="MermaidHook" phx-update="ignore") end
```
### DON'T: Call attach() after to_html()
```elixir
# BAD - attach must be called before to_html
html = MDEx.to_html!(mdex)
mdex = MDExMermaid.attach(mdex) # Too late!
```
**Instead:** Build the pipeline in order:
```elixir
MDEx.new(markdown: markdown)
|> MDExMermaid.attach()
|> MDEx.to_html!()
```
## Pipeline Order
MDExMermaid uses MDEx's plugin system. The correct order is:
1. Create MDEx document: `MDEx.new/1`
2. Attach plugins: `MDExMermaid.attach/2`
3. Convert to HTML: `MDEx.to_html!/1`
```elixir
MDEx.new(markdown: markdown)
|> MDExMermaid.attach(options)
|> MDEx.to_html!()
```
## Code Block Detection
MDExMermaid only processes code blocks tagged as `mermaid`:
````markdown
```mermaid
graph TD;
A-->B;
```
````
Other code blocks are left untouched.
## Examples
See the [examples directory](https://github.com/leandrocp/mdex_mermaid/tree/main/examples) for complete working examples including LiveView integration.