Usage
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:
MDEx.new(markdown: markdown)
|> MDExKatex.attach(katex_options: [trust: false, output: "mathml"])
|> MDEx.to_html!()See KaTeX Options for the supported render options.
For KaTeX options that require JavaScript functions, pass a raw object expression string:
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:
Inline math: $e^{i\pi} + 1 = 0$
```math
E = mc^2
```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
@type katex_block_attrs() :: (seq :: pos_integer() -> String.t())
@type katex_inline_attrs() :: (seq :: pos_integer() -> String.t())
Functions
@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
mathandkatexcode fences, and dollar math when the extension optionmath_dollarsis true.
Options
:katex_block_attrs(katex_block_attrs/0) - Function that generates the display math tag attributes formath/katexcode fences and$$...$$expressions.:katex_inline_attrs(katex_inline_attrs/0) - Function that generates the inline math tag attributes for$...$expressions.:katex_options(katex_options/0) - KaTeX render options merged withdisplayModefor each formula. Accepts a keyword list, map, or raw JavaScript object expression. See KaTeX Options.:katex_init(String.t/0) - The HTML tag(s) to inject into the document to initialize KaTeX. Ifnil, 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:
<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 directory for complete working examples.
Static HTML
The output includes all necessary scripts and can be used directly:
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 for a complete working example.
Phoenix LiveView
To use MDExKatex with Phoenix LiveView, you can:
- Load KaTeX (via CDN or npm)
- Create a LiveView hook to render formulas
- 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 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
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 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:
.katex-block {
padding: 1em;
margin: 1em 0;
background: #f5f5f5;
border-radius: 4px;
}
.katex-inline {
padding: 0;
background: transparent;
}