MDExKatex (MDExKatex v0.1.1)

View Source

MDEx plugin for KaTeX.

Usage

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

markdown = """
# Einstein's Mass-Energy Equivalence

```math
E = mc^2
```

The quadratic formula:

```math
x = \\frac{-b \\pm \\sqrt{b^2 - 4ac}}{2a}
```
"""

mdex = MDEx.new(markdown: markdown) |> MDExKatex.attach()

MDEx.to_html!(mdex) |> IO.puts()
#=>
# <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16/dist/katex.min.css">
# <script defer src="https://cdn.jsdelivr.net/npm/katex@0.16/dist/katex.min.js"></script>
# <script defer src="https://cdn.jsdelivr.net/npm/katex@0.16/dist/contrib/auto-render.min.js" onload="renderMathInElement(document.body, {delimiters: [{left: '$$', right: '$$', display: true}]});"></script>
# <script>
#   document.addEventListener("DOMContentLoaded", () => {
#     document.querySelectorAll('.katex-block').forEach(el => {
#       const latex = el.dataset.latex;
#       if (latex) {
#         katex.render(latex, el, {
#           displayMode: true,
#           throwOnError: false,
#           trust: true
#         });
#       }
#     });
#   });
# </script>
# <h1>Einstein's Mass-Energy Equivalence</h1>
# <div id="katex-1" class="katex-block" phx-update="ignore" data-latex="E = mc^2"></div>
# <p>The quadratic formula:</p>
# <div id="katex-2" class="katex-block" phx-update="ignore" data-latex="x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}"></div>

Note: Both math and katex code fences are supported.

See attach/2 for integration examples (static HTML, Phoenix LiveView, custom styling) and configuration options.

Summary

Functions

Attaches the MDExKatex plugin into the MDEx document.

Types

katex_block_attrs()

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

Functions

attach(document, options \\ [])

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

Attaches the MDExKatex plugin into the MDEx document.

Options

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

:katex_block_attrs

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

block_attrs = fn seq -> ~s(id="katex-#{seq}" class="katex-block" phx-update="ignore") end
mdex = MDEx.new() |> MDExKatex.attach(katex_block_attrs: block_attrs)

Which results in:

<div id="katex-1" class="katex-block" data-latex="E = mc^2" phx-update="ignore"></div>

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

fn seq -> ~s(id="katex-#{seq}" class="katex-block formula" phx-hook="KaTeXHook" phx-update="ignore") end

:katex_init

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

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16/dist/katex.min.css">
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16/dist/katex.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16/dist/contrib/auto-render.min.js"></script>
<script>
  document.addEventListener("DOMContentLoaded", () => {
    document.querySelectorAll('.katex-block').forEach(el => {
      const latex = el.dataset.latex;
      if (latex) {
        katex.render(latex, el, {
          displayMode: true,
          throwOnError: false,
          trust: true
        });
      }
    });
  });
</script>

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

Examples

See the examples directory for complete working examples.

Static HTML

The output includes all necessary scripts and can be used directly:

html = MDEx.new(markdown: markdown) |> MDExKatex.attach() |> MDEx.to_html!()
File.write!("output.html", html)

For embedding in existing HTML documents, extract content between initialization scripts and your markdown content.

See examples/static.exs for a complete working example.

Phoenix LiveView

To use MDExKatex with Phoenix LiveView, you can:

  1. Load KaTeX (via CDN or npm)
  2. Create a LiveView hook to render formulas
  3. Configure MDExKatex with the appropriate attributes

Option 1: Using CDN

In your layout:

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16/dist/katex.min.css">
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16/dist/katex.min.js"></script>

Option 2: Using npm

Install KaTeX as a dependency:

cd assets && npm install katex

In your assets/js/app.js:

import katex from 'katex';
import 'katex/dist/katex.min.css';

let hooks = {
  KaTeXHook: {
    mounted() {
      const elements = this.el.querySelectorAll('.katex-block');
      elements.forEach(el => {
        const latex = el.dataset.latex;
        if (latex) {
          katex.render(latex, el, {
            displayMode: true,
            throwOnError: false,
            trust: true
          });
        }
      });
    }
  }
}

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

Using in LiveView

html =
  MDEx.new(markdown: markdown)
  |> MDExKatex.attach(
    katex_init: "", # already initialized
    katex_block_attrs: fn seq ->
      ~s(id="katex-#{seq}" class="katex-block" phx-hook="KaTeXHook" phx-update="ignore")
    end
  )
  |> MDEx.to_html!()

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

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

See examples/live_view.exs for a complete working example with both individual hooks and global hooks patterns.

Custom Styling

Target the .katex-block class:

.katex-block {
  padding: 1em;
  margin: 1em 0;
  background: #f5f5f5;
  border-radius: 4px;
}