Fix panic when passthrough elements are used in headings

Fixes #14677

Co-Authored-By: xingzihai <1315258019@qq.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bjørn Erik Pedersen
2026-04-08 13:30:14 +02:00
parent c48551677c
commit 8b00030b34
3 changed files with 68 additions and 1 deletions
+6
View File
@@ -122,6 +122,12 @@ func newMarkdown(pcfg converter.ProviderConfig) goldmark.Markdown {
)
}
if cfg.Extensions.Passthrough.Enable {
tocRendererOptions = append(tocRendererOptions,
renderer.WithNodeRenderers(util.Prioritized(&tocPassthroughRenderer{}, 90)),
)
}
var (
extensions = []goldmark.Extender{
hugocontext.New(pcfg.Logger),
+28 -1
View File
@@ -24,6 +24,7 @@ import (
emojiAst "github.com/yuin/goldmark-emoji/ast"
"github.com/gohugoio/hugo-goldmark-extensions/extras"
"github.com/gohugoio/hugo-goldmark-extensions/passthrough"
"github.com/gohugoio/hugo/markup/tableofcontents"
"github.com/yuin/goldmark"
@@ -116,7 +117,9 @@ func (t *tocTransformer) Transform(n *ast.Document, reader text.Reader, pc parse
ast.KindAutoLink,
ast.KindRawHTML,
ast.KindText,
ast.KindString:
ast.KindString,
passthrough.KindPassthroughInline,
passthrough.KindPassthroughBlock:
err := t.r.Render(&headingText, reader.Source(), n)
if err != nil {
return s, err
@@ -171,6 +174,30 @@ func newTOCSanitizerPolicy() *bluemonday.Policy {
return p
}
// tocPassthroughRenderer renders passthrough nodes as raw text for the TOC.
type tocPassthroughRenderer struct{}
func (r *tocPassthroughRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
reg.Register(passthrough.KindPassthroughInline, r.render)
reg.Register(passthrough.KindPassthroughBlock, r.render)
}
func (r *tocPassthroughRenderer) render(w util.BufWriter, src []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
if !entering {
return ast.WalkContinue, nil
}
switch nn := node.(type) {
case *passthrough.PassthroughInline:
w.Write(nn.Segment.Value(src))
case *passthrough.PassthroughBlock:
for i := range nn.Lines().Len() {
line := nn.Lines().At(i)
w.Write(line.Value(src))
}
}
return ast.WalkSkipChildren, nil
}
var whiteSpaceRe = regexp.MustCompile(`\s+`)
// sanitizeTOCHeadingTitle sanitizes s for use as a TOC heading title.
+34
View File
@@ -364,3 +364,37 @@ title: home
`<li><a href="#1st">1^st^</a></li>`,
)
}
// Issue 14677
func TestTableOfContentsWithPassthrough(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
disableKinds = ['page','rss','section','sitemap','taxonomy','term']
[markup.goldmark.extensions.passthrough]
enable = true
[markup.goldmark.extensions.passthrough.delimiters]
inline = [['$', '$']]
-- content/_index.md --
---
title: home
---
## Heading with **$a$**
## Heading with $b$
## Heading with *$x + y$*
-- layouts/home.html --
{{ .TableOfContents }}
`
b := hugolib.Test(t, files)
b.AssertFileContent("public/index.html",
`<li><a href="#heading-with">Heading with <strong>$a$</strong></a></li>`,
`<li><a href="#heading-with-1">Heading with $b$</a></li>`,
`<li><a href="#heading-with-2">Heading with <em>$x + y$</em></a></li>`,
)
}