Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,32 @@ eval "$(gojekyll --completion-script-zsh)"
This project works on the GitHub Pages sites that I and other contributors care
about. It looks credible on a spot-check of other Jekyll sites.

### Math Support

gojekyll supports mathematical expressions using MathJax or KaTeX, compatible with Jekyll/kramdown syntax:

- Use `$$...$$` delimiters for both inline and display math
- Math expressions are preserved in the HTML output for client-side rendering
- Works with both MathJax 3 and KaTeX

**Example usage:**

```markdown
Inline math: The equation $$E=mc^2$$ is famous.

Display math:
$$
\int_0^\infty e^{-x} dx = 1
$$
```

**Setup:** Add MathJax or KaTeX scripts to your layout templates. See `example/_layouts/math.html` and `example/math-example.md` for complete examples.

### Current Limitations

Missing features:

- Pagination
- Math
- Plugin system. ([Some individual plugins](./docs/plugins.md) are emulated.)
- Liquid is run in strict mode: undefined filters and variables are errors.
- Missing markdown features:
Expand Down
61 changes: 61 additions & 0 deletions example/_layouts/math.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ page.title | default: site.title }}</title>

<!-- MathJax 3 Configuration -->
<script>
MathJax = {
tex: {
inlineMath: [['$$', '$$']],
displayMath: [['$$', '$$']],
processEscapes: true,
processEnvironments: true
},
options: {
skipHtmlTags: ['script', 'noscript', 'style', 'textarea', 'pre']
}
};
</script>
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js" id="MathJax-script" async></script>

<!-- Alternative: KaTeX (uncomment to use instead of MathJax)
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/katex.min.css">
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/katex.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/contrib/auto-render.min.js"
onload="renderMathInElement(document.body, {
delimiters: [
{left: '$$', right: '$$', display: true},
{left: '$$', right: '$$', display: false}
],
throwOnError: false
});"></script>
-->

<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif;
line-height: 1.6;
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
code {
background: #f4f4f4;
padding: 2px 6px;
border-radius: 3px;
}
pre {
background: #f4f4f4;
padding: 15px;
overflow-x: auto;
border-radius: 5px;
}
</style>
</head>
<body>
{{ content }}
</body>
</html>
67 changes: 67 additions & 0 deletions example/math-example.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
layout: math
title: Math Rendering Example
---

# Math Rendering with gojekyll

This page demonstrates mathematical expression rendering using MathJax or KaTeX.

## Inline Math

The famous equation $$E=mc^2$$ shows the relationship between energy and mass.

Here's another example: $$x_0 = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$$

## Display Math

The Fundamental Theorem of Calculus:

$$
\int_a^b f'(x) dx = f(b) - f(a)
$$

Maxwell's Equations:

$$
\begin{aligned}
\nabla \cdot \mathbf{E} &= \frac{\rho}{\epsilon_0} \\
\nabla \cdot \mathbf{B} &= 0 \\
\nabla \times \mathbf{E} &= -\frac{\partial \mathbf{B}}{\partial t} \\
\nabla \times \mathbf{B} &= \mu_0\left(\mathbf{J} + \epsilon_0 \frac{\partial \mathbf{E}}{\partial t}\right)
\end{aligned}
$$

## Matrices

$$
\begin{bmatrix}
a & b \\
c & d
\end{bmatrix}
\begin{bmatrix}
x \\
y
\end{bmatrix}
=
\begin{bmatrix}
ax + by \\
cx + dy
\end{bmatrix}
$$

## Complex Expression

The probability density function of the normal distribution:

$$
f(x | \mu, \sigma^2) = \frac{1}{\sqrt{2\pi\sigma^2}} e^{-\frac{(x-\mu)^2}{2\sigma^2}}
$$

## Note

Math expressions use the `$$...$$` delimiter for both inline and display math.
- **Inline**: `$$E=mc^2$$` renders as $$E=mc^2$$
- **Display**: Use `$$...$$` on its own lines for centered equations

The delimiters are preserved in the HTML and rendered by MathJax or KaTeX on the client side.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/bep/godartsass/v2 v2.5.0
github.com/danog/blackfriday/v2 v2.1.6
github.com/fsnotify/fsnotify v1.9.0
github.com/gohugoio/hugo-goldmark-extensions/passthrough v0.3.1
github.com/google/go-github v17.0.0+incompatible
github.com/jaschaephraim/lrserver v0.0.0-20240306232639-afed386b3640
github.com/k0kubun/pp v3.0.1+incompatible
Expand All @@ -20,6 +21,7 @@ require (
github.com/radovskyb/watcher v1.0.7
github.com/stretchr/testify v1.11.1
github.com/tdewolff/minify v2.3.6+incompatible
github.com/yuin/goldmark v1.7.13
golang.org/x/net v0.46.0
golang.org/x/oauth2 v0.30.0
gopkg.in/yaml.v2 v2.4.0
Expand Down
8 changes: 6 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,8 @@ github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
github.com/firefart/nonamedreturns v1.0.6 h1:vmiBcKV/3EqKY3ZiPxCINmpS431OcE1S47AQUwhrg8E=
github.com/firefart/nonamedreturns v1.0.6/go.mod h1:R8NisJnSIpvPWheCq0mNRXJok6D8h7fagJTF8EMEwCo=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo=
Expand Down Expand Up @@ -239,6 +239,8 @@ github.com/godoc-lint/godoc-lint v0.10.1/go.mod h1:KleLcHu/CGSvkjUH2RvZyoK1MBC7p
github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw=
github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gohugoio/hugo-goldmark-extensions/passthrough v0.3.1 h1:nUzXfRTszLliZuN0JTKeunXTRaiFX6ksaWP0puLLYAY=
github.com/gohugoio/hugo-goldmark-extensions/passthrough v0.3.1/go.mod h1:Wy8ThAA8p2/w1DY05vEzq6EIeI2mzDjvHsu7ULBVwog=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
Expand Down Expand Up @@ -664,6 +666,8 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA=
github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
gitlab.com/bosi/decorder v0.4.2 h1:qbQaV3zgwnBZ4zPMhGLW4KZe7A7NwxEhJx39R3shffo=
gitlab.com/bosi/decorder v0.4.2/go.mod h1:muuhHoaJkA9QLcYHq4Mj8FJUwDZ+EirSHRiaTcTf6T8=
go-simpler.org/assert v0.9.0 h1:PfpmcSvL7yAnWyChSjOz6Sp6m9j5lyK8Ok9pEL31YkQ=
Expand Down
89 changes: 49 additions & 40 deletions renderers/markdown.go
Original file line number Diff line number Diff line change
@@ -1,32 +1,45 @@
package renderers

import (
blackfriday "github.com/danog/blackfriday/v2"
"bytes"

"github.com/gohugoio/hugo-goldmark-extensions/passthrough"
"github.com/osteele/gojekyll/utils"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/extension"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/renderer/html"
)

const blackfridayFlags = 0 |
blackfriday.UseXHTML |
blackfriday.Smartypants |
blackfriday.SmartypantsFractions |
blackfriday.SmartypantsDashes |
blackfriday.SmartypantsLatexDashes |
blackfriday.FootnoteReturnLinks

const blackfridayExtensions = 0 |
blackfriday.NoIntraEmphasis |
blackfriday.Tables |
blackfriday.FencedCode |
blackfriday.Autolink |
blackfriday.Strikethrough |
blackfriday.SpaceHeadings |
blackfriday.HeadingIDs |
blackfriday.BackslashLineBreak |
blackfriday.DefinitionLists |
blackfriday.NoEmptyLineBeforeBlock |
// added relative to commonExtensions
blackfriday.AutoHeadingIDs |
blackfriday.Footnotes
// createGoldmarkConverter creates a Goldmark markdown converter configured
// to match Jekyll/kramdown behavior as closely as possible
func createGoldmarkConverter() goldmark.Markdown {
return goldmark.New(
goldmark.WithExtensions(
extension.GFM, // GitHub Flavored Markdown (includes tables, strikethrough, autolinks)
extension.Footnote, // Footnotes support
extension.DefinitionList, // Definition lists
extension.Typographer, // Smart quotes and dashes (like Smartypants)
passthrough.New(passthrough.Config{ // Math delimiters passthrough
// Inline math: $$...$$ → preserved as-is for client-side rendering
InlineDelimiters: []passthrough.Delimiters{
{Open: "$$", Close: "$$"},
},
// Block/display math: $$...$$ on separate lines
BlockDelimiters: []passthrough.Delimiters{
{Open: "$$", Close: "$$"},
},
}),
),
goldmark.WithParserOptions(
parser.WithAutoHeadingID(), // Automatic heading IDs
),
goldmark.WithRendererOptions(
html.WithXHTML(), // Use XHTML tags (like Blackfriday's UseXHTML)
html.WithUnsafe(), // Allow raw HTML (Jekyll/kramdown compatibility)
),
)
}

func renderMarkdown(md []byte) ([]byte, error) {
return renderMarkdownWithOptions(md, nil)
Expand Down Expand Up @@ -54,15 +67,15 @@ func renderMarkdownWithOptions(md []byte, opts *TOCOptions) ([]byte, error) {
opts.MaxLevel = 6
}

params := blackfriday.HTMLRendererParameters{
Flags: blackfridayFlags,
// Create Goldmark converter and render markdown to HTML
converter := createGoldmarkConverter()
var buf bytes.Buffer
if err := converter.Convert(md, &buf); err != nil {
return nil, utils.WrapError(err, "markdown conversion")
}
renderer := blackfriday.NewHTMLRenderer(params)
html := blackfriday.Run(
md,
blackfriday.WithRenderer(renderer),
blackfriday.WithExtensions(blackfridayExtensions),
)
html := buf.Bytes()

// Process inner markdown (for nested markdown rendering)
html, err := renderInnerMarkdown(html)
if err != nil {
return nil, utils.WrapError(err, "markdown")
Expand All @@ -81,14 +94,10 @@ func renderMarkdownWithOptions(md []byte, opts *TOCOptions) ([]byte, error) {
}

func _renderMarkdown(md []byte) ([]byte, error) {
params := blackfriday.HTMLRendererParameters{
Flags: blackfridayFlags,
converter := createGoldmarkConverter()
var buf bytes.Buffer
if err := converter.Convert(md, &buf); err != nil {
return nil, err
}
renderer := blackfriday.NewHTMLRenderer(params)
html := blackfriday.Run(
md,
blackfriday.WithRenderer(renderer),
blackfriday.WithExtensions(blackfridayExtensions),
)
return html, nil
return buf.Bytes(), nil
}
32 changes: 9 additions & 23 deletions renderers/markdown_attrs.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"io"
"regexp"

blackfriday "github.com/danog/blackfriday/v2"
"github.com/osteele/gojekyll/utils"
"golang.org/x/net/html"
)
Expand Down Expand Up @@ -152,30 +151,17 @@ loop:

// _renderMarkdownSpan processes inline markdown without creating block-level elements
func _renderMarkdownSpan(md []byte) ([]byte, error) {
// For span-level processing, we don't want to create block-level elements like paragraphs
// Instead, we just want inline formatting (bold, italic, links, etc.)
params := blackfriday.HTMLRendererParameters{
Flags: blackfridayFlags,
// For span-level processing with Goldmark, we just render normally
// and then strip the wrapping paragraph tags that Goldmark adds
html, err := _renderMarkdown(md)
if err != nil {
return nil, err
}
renderer := blackfriday.NewHTMLRenderer(params)

// Use only inline-level extensions for span mode
inlineExtensions := blackfriday.NoIntraEmphasis |
blackfriday.Autolink |
blackfriday.Strikethrough |
blackfriday.BackslashLineBreak

// Process the content without creating paragraphs - we're handling inline elements
content := bytes.TrimSpace(md)
html := blackfriday.Run(
content,
blackfriday.WithRenderer(renderer),
blackfriday.WithExtensions(inlineExtensions),
)

// Remove any potential wrapping paragraph tags that blackfriday might add

// Remove wrapping paragraph tags that Goldmark adds
html = bytes.TrimSpace(html)
html = bytes.TrimPrefix(html, []byte("<p>"))
html = bytes.TrimSuffix(html, []byte("</p>\n"))
html = bytes.TrimSuffix(html, []byte("</p>"))

return html, nil
}
Expand Down
Loading