# `MDExKatex`
[🔗](https://github.com/leandrocp/mdex_katex/blob/v0.2.1/lib/mdex_katex.ex#L1)

[MDEx](https://mdelixir.dev) plugin for [KaTeX](https://katex.org).

## Usage

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

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

In text, Euler's identity is $e^{i\\pi} + 1 = 0$.

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

The quadratic formula:

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

mdex =
  MDEx.new(markdown: markdown, extension: [math_dollars: true])
  |> 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, .katex-inline').forEach(el => {
#       const latex = el.dataset.latex;
#       const mathStyle = el.dataset.mathStyle;
#       if (latex && mathStyle) {
#         const displayMode = mathStyle == "display" ? true : false
#         katex.render(latex, el, {
#           displayMode: displayMode,
#           throwOnError: false,
#           trust: true,
#         });
#       }
#     });
#   });
# </script>
# <h1>Einstein's Mass-Energy Equivalence</h1>
# <p>In text, Euler's identity is <span id="katex-inline-1" class="katex-inline" phx-update="ignore" data-math-style="inline" data-latex="e^{i\pi} + 1 = 0"></span>.</p>
# <div id="katex-1" class="katex-block" phx-update="ignore" data-math-style="display" data-latex="E = mc^2"></div>
# <p>The quadratic formula:</p>
# <div id="katex-2" class="katex-block" phx-update="ignore" data-math-style="display" data-latex="x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}"></div>
````

To customize KaTeX without replacing the whole init script:

````elixir
MDEx.new(markdown: markdown)
|> MDExKatex.attach(katex_options: [trust: false, output: "mathml"])
|> MDEx.to_html!()
````

See [KaTeX Options](https://katex.org/docs/options) for the supported render options.

For KaTeX options that require JavaScript functions, pass a raw object expression string:

````elixir
MDEx.new(markdown: markdown)
|> MDExKatex.attach(
  katex_options: "{strict: (errorCode) => 'ignore', trust: (context) => false}"
)
|> MDEx.to_html!()
````

**Note:** `math` and `katex` code fences render as display math. Dollar math is also supported when `MDEx` enables `extension: [math_dollars: true]`; inline formulas use the `.katex-inline` class and display formulas use `.katex-block`.

Quick reference:

````markdown
Inline math: $e^{i\pi} + 1 = 0$

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

See [attach/2](https://hexdocs.pm/mdex_katex/MDExKatex.html#attach/2) for integration examples (static HTML, Phoenix LiveView, custom styling) and configuration options.

# `katex_block_attrs`

```elixir
@type katex_block_attrs() :: (seq :: pos_integer() -&gt; String.t())
```

# `katex_inline_attrs`

```elixir
@type katex_inline_attrs() :: (seq :: pos_integer() -&gt; String.t())
```

# `katex_options`

```elixir
@type katex_options() :: keyword() | map() | String.t()
```

# `attach`

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

Attaches the MDExKatex plugin into the MDEx document.

- KaTeX is loaded from https://www.jsdelivr.com/package/npm/katex
- Renders mathematical expressions using LaTeX syntax
- Recognizes both `math` and `katex` code fences, and dollar math when the [extension option](https://hexdocs.pm/mdex/MDEx.Document.html#t:extension_options/0) `math_dollars` is true.

## Options
  - `:katex_block_attrs` (`t:katex_block_attrs/0`) - Function that generates the display math tag attributes for `math`/`katex` code fences and `$$...$$` expressions.
  - `:katex_inline_attrs` (`t:katex_inline_attrs/0`) - Function that generates the inline math tag attributes for `$...$` expressions.
  - `:katex_options` (`t:katex_options/0`) - KaTeX render options merged with `displayMode` for each formula. Accepts a keyword list, map, or raw JavaScript object expression. See [KaTeX Options](https://katex.org/docs/options).
  - `:katex_init` (`t: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 display math node 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-math-style="display" 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_inline_attrs

Whenever inline dollar math is found, it gets converted into a `<span>` tag using the following function to generate its attributes:

    inline_attrs = fn seq -> ~s(id="katex-inline-#{seq}" class="katex-inline" phx-update="ignore") end
    mdex = MDEx.new(markdown: "Euler wrote $e^{i\pi} + 1 = 0$", extension: [math_dollars: true])
    |> MDExKatex.attach(katex_inline_attrs: inline_attrs)

Which results in:

    <p>Euler wrote <span id="katex-inline-1" class="katex-inline" data-math-style="inline" data-latex="e^{ipi} + 1 = 0" phx-update="ignore"></span></p>

### :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:

```html
<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, .katex-inline').forEach(el => {
      const latex = el.dataset.latex;
      const mathStyle = el.dataset.mathStyle;
      if (latex && mathStyle) {
        const displayMode = mathStyle == "display" ? true : false
        katex.render(latex, el, {
          displayMode: displayMode,
          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.

### :katex_options

Use `:katex_options` to customize the options passed to `katex.render/3` without replacing the whole init script:

    mdex =
      MDEx.new(markdown: markdown)
      |> MDExKatex.attach(katex_options: [trust: false, output: "mathml"])

For options that require JavaScript functions, pass a raw object expression string:

    mdex =
      MDEx.new(markdown: markdown)
      |> MDExKatex.attach(
        katex_options: "{strict: (errorCode) => 'ignore', trust: (context) => false}"
      )

`displayMode` is always controlled by the markdown node type and overrides any user-provided value.

## Examples

See the [examples](https://github.com/leandrocp/mdex_katex/tree/main/examples) directory for complete working examples.

### Static HTML

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

```elixir
html = MDEx.new(markdown: markdown, extension: [math_dollars: true]) |> 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](https://github.com/leandrocp/mdex_katex/blob/main/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:

```html
<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:

```bash
cd assets && npm install katex
```

In your `assets/js/app.js`:

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

let hooks = {
  KaTeXHook: {
    mounted() {
      const latex = this.el.dataset.latex;
      const mathStyle = this.el.dataset.mathStyle;
      if (latex && mathStyle) {
        const displayMode = mathStyle == "display" ? true : false
        katex.render(latex, this.el, {
          displayMode: displayMode,
          throwOnError: false,
          trust: true,
        });
      }
    },
  }
}

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

### Using in LiveView

```elixir
html =
  MDEx.new(markdown: markdown, extension: [math_dollars: true])
  |> 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,
    katex_inline_attrs: fn seq ->
      ~s(id="katex-inline-#{seq}" class="katex-inline" 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](https://github.com/leandrocp/mdex_katex/blob/main/examples/live_view.exs) for a complete working example with both individual hooks and global hooks patterns.

### Custom Styling

Target `.katex-block` for display math and `.katex-inline` for inline math:

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

.katex-inline {
  padding: 0;
  background: transparent;
}
```

---

*Consult [api-reference.md](api-reference.md) for complete listing*
