Skip to content

Commit 8b00030

Browse files
bepxingzihaiclaude
committed
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>
1 parent c485516 commit 8b00030

3 files changed

Lines changed: 68 additions & 1 deletion

File tree

markup/goldmark/convert.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,12 @@ func newMarkdown(pcfg converter.ProviderConfig) goldmark.Markdown {
122122
)
123123
}
124124

125+
if cfg.Extensions.Passthrough.Enable {
126+
tocRendererOptions = append(tocRendererOptions,
127+
renderer.WithNodeRenderers(util.Prioritized(&tocPassthroughRenderer{}, 90)),
128+
)
129+
}
130+
125131
var (
126132
extensions = []goldmark.Extender{
127133
hugocontext.New(pcfg.Logger),

markup/goldmark/toc.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
emojiAst "github.com/yuin/goldmark-emoji/ast"
2525

2626
"github.com/gohugoio/hugo-goldmark-extensions/extras"
27+
"github.com/gohugoio/hugo-goldmark-extensions/passthrough"
2728
"github.com/gohugoio/hugo/markup/tableofcontents"
2829

2930
"github.com/yuin/goldmark"
@@ -116,7 +117,9 @@ func (t *tocTransformer) Transform(n *ast.Document, reader text.Reader, pc parse
116117
ast.KindAutoLink,
117118
ast.KindRawHTML,
118119
ast.KindText,
119-
ast.KindString:
120+
ast.KindString,
121+
passthrough.KindPassthroughInline,
122+
passthrough.KindPassthroughBlock:
120123
err := t.r.Render(&headingText, reader.Source(), n)
121124
if err != nil {
122125
return s, err
@@ -171,6 +174,30 @@ func newTOCSanitizerPolicy() *bluemonday.Policy {
171174
return p
172175
}
173176

177+
// tocPassthroughRenderer renders passthrough nodes as raw text for the TOC.
178+
type tocPassthroughRenderer struct{}
179+
180+
func (r *tocPassthroughRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
181+
reg.Register(passthrough.KindPassthroughInline, r.render)
182+
reg.Register(passthrough.KindPassthroughBlock, r.render)
183+
}
184+
185+
func (r *tocPassthroughRenderer) render(w util.BufWriter, src []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
186+
if !entering {
187+
return ast.WalkContinue, nil
188+
}
189+
switch nn := node.(type) {
190+
case *passthrough.PassthroughInline:
191+
w.Write(nn.Segment.Value(src))
192+
case *passthrough.PassthroughBlock:
193+
for i := range nn.Lines().Len() {
194+
line := nn.Lines().At(i)
195+
w.Write(line.Value(src))
196+
}
197+
}
198+
return ast.WalkSkipChildren, nil
199+
}
200+
174201
var whiteSpaceRe = regexp.MustCompile(`\s+`)
175202

176203
// sanitizeTOCHeadingTitle sanitizes s for use as a TOC heading title.

markup/goldmark/toc_integration_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,3 +364,37 @@ title: home
364364
`<li><a href="#1st">1^st^</a></li>`,
365365
)
366366
}
367+
368+
// Issue 14677
369+
func TestTableOfContentsWithPassthrough(t *testing.T) {
370+
t.Parallel()
371+
372+
files := `
373+
-- hugo.toml --
374+
disableKinds = ['page','rss','section','sitemap','taxonomy','term']
375+
[markup.goldmark.extensions.passthrough]
376+
enable = true
377+
[markup.goldmark.extensions.passthrough.delimiters]
378+
inline = [['$', '$']]
379+
-- content/_index.md --
380+
---
381+
title: home
382+
---
383+
## Heading with **$a$**
384+
385+
## Heading with $b$
386+
387+
## Heading with *$x + y$*
388+
389+
-- layouts/home.html --
390+
{{ .TableOfContents }}
391+
`
392+
393+
b := hugolib.Test(t, files)
394+
395+
b.AssertFileContent("public/index.html",
396+
`<li><a href="#heading-with">Heading with <strong>$a$</strong></a></li>`,
397+
`<li><a href="#heading-with-1">Heading with $b$</a></li>`,
398+
`<li><a href="#heading-with-2">Heading with <em>$x + y$</em></a></li>`,
399+
)
400+
}

0 commit comments

Comments
 (0)