From a20cb5b1c0e8ff0692a9772b92720521fca53306 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Thu, 14 May 2026 19:46:41 +0200 Subject: [PATCH] Revert "markup/tableofcontents: Skip empty TOC levels" This reverts commit 7d4af7a1797a6d17ff2475740e3bc6a156ecd8c1. Closes #14898 --- markup/goldmark/toc_integration_test.go | 95 --------- markup/tableofcontents/tableofcontents.go | 50 ++--- .../tableofcontents/tableofcontents_test.go | 183 +++--------------- 3 files changed, 49 insertions(+), 279 deletions(-) diff --git a/markup/goldmark/toc_integration_test.go b/markup/goldmark/toc_integration_test.go index f171af600..105e1a744 100644 --- a/markup/goldmark/toc_integration_test.go +++ b/markup/goldmark/toc_integration_test.go @@ -305,101 +305,6 @@ title: home b.AssertFileExists("public/index.html", true) } -func TestTableOfContentsSkippedHeadingLevelsIssue7128(t *testing.T) { - t.Parallel() - - filesTemplate := ` --- hugo.toml -- -disableKinds = ['home','rss','section','sitemap','taxonomy','term'] -TOC_CONFIG --- layouts/page.html -- -{{ .TableOfContents }} --- content/p1.md -- ---- -title: p1 ---- -## Extra-Curriculars - -#### Heading Four Here without Heading Three Before It - -## Technology - -#### Heading Four Here without Heading Three Before It -` - - t.Run("default end level", func(t *testing.T) { - t.Parallel() - - files := strings.ReplaceAll(filesTemplate, "TOC_CONFIG", "") - b := hugolib.Test(t, files) - - b.AssertFileContentExact("public/p1/index.html", ``) - }) - - t.Run("include skipped level", func(t *testing.T) { - t.Parallel() - - files := strings.ReplaceAll(filesTemplate, "TOC_CONFIG", ` -[markup.tableOfContents] -startLevel = 2 -endLevel = -1`) - b := hugolib.Test(t, files) - - b.AssertFileContentExact("public/p1/index.html", ``) - }) - - t.Run("starts at h4", func(t *testing.T) { - t.Parallel() - - files := ` --- hugo.toml -- -disableKinds = ['home','rss','section','sitemap','taxonomy','term'] -[markup.tableOfContents] -startLevel = 2 -endLevel = -1 --- layouts/page.html -- -{{ .TableOfContents }} --- content/p1.md -- ---- -title: p1 ---- -#### Heading Four First - -###### Heading Six After Heading Four -` - - b := hugolib.Test(t, files) - - b.AssertFileContentExact("public/p1/index.html", ``) - }) -} - // Issue 12605 func TestTableOfContentsWithGoldmarkExtras(t *testing.T) { t.Parallel() diff --git a/markup/tableofcontents/tableofcontents.go b/markup/tableofcontents/tableofcontents.go index 271c44e67..6c40c9a59 100644 --- a/markup/tableofcontents/tableofcontents.go +++ b/markup/tableofcontents/tableofcontents.go @@ -195,13 +195,23 @@ func (b *tocBuilder) Build() { func (b *tocBuilder) writeNav(h Headings) { b.s.WriteString("") } func (b *tocBuilder) writeHeadings(level, indent int, h Headings) { - headings := b.collectHeadings(level, h) - hasChildren := len(headings) > 0 + if level < b.startLevel { + for _, h := range h { + b.writeHeadings(level+1, indent, h.Headings) + } + return + } + + if b.stopLevel != -1 && level > b.stopLevel { + return + } + + hasChildren := len(h) > 0 if hasChildren { b.s.WriteString("\n") @@ -213,8 +223,8 @@ func (b *tocBuilder) writeHeadings(level, indent int, h Headings) { } } - for _, h := range headings { - b.writeHeading(h.level, indent+2, h.h) + for _, h := range h { + b.writeHeading(level+1, indent+2, h) } if hasChildren { @@ -229,35 +239,13 @@ func (b *tocBuilder) writeHeadings(level, indent int, h Headings) { } } -type tocHeading struct { - h *Heading - level int -} - -func (b *tocBuilder) collectHeadings(level int, h Headings) []tocHeading { - var out []tocHeading - - for _, h := range h { - if h.IsZero() || level < b.startLevel { - out = append(out, b.collectHeadings(level+1, h.Headings)...) - continue - } - - if b.stopLevel != -1 && level > b.stopLevel { - continue - } - - out = append(out, tocHeading{h: h, level: level}) - } - - return out -} - func (b *tocBuilder) writeHeading(level, indent int, h *Heading) { b.indent(indent) b.s.WriteString("
  • ") - b.s.WriteString("" + h.Title + "") - b.writeHeadings(level+1, indent, h.Headings) + if !h.IsZero() { + b.s.WriteString("" + h.Title + "") + } + b.writeHeadings(level, indent, h.Headings) b.s.WriteString("
  • \n") } diff --git a/markup/tableofcontents/tableofcontents_test.go b/markup/tableofcontents/tableofcontents_test.go index dfbb21a53..db50c91ab 100644 --- a/markup/tableofcontents/tableofcontents_test.go +++ b/markup/tableofcontents/tableofcontents_test.go @@ -14,7 +14,6 @@ package tableofcontents import ( - "strings" "testing" qt "github.com/frankban/quicktest" @@ -128,9 +127,21 @@ func TestTocMissingParent(t *testing.T) { got := string(tocHTML) c.Assert(got, qt.Equals, ``, qt.Commentf(got)) @@ -147,159 +158,25 @@ func TestTocMissingParent(t *testing.T) { got = string(tocHTML) c.Assert(got, qt.Equals, ``, qt.Commentf(got)) } -func TestTocMissingIntermediateLevels(t *testing.T) { - c := qt.New(t) - - type item struct { - title string - id string - row int - level int - } - - for _, test := range []struct { - name string - items []item - expected string - }{ - { - name: "h2 to h4", - items: []item{ - {title: "H2", id: "h2", level: 1}, - {title: "H4", id: "h4", level: 3}, - }, - expected: ``, - }, - { - name: "h2 to h5", - items: []item{ - {title: "H2", id: "h2", level: 1}, - {title: "H5", id: "h5", level: 4}, - }, - expected: ``, - }, - { - name: "h2 to h6", - items: []item{ - {title: "H2", id: "h2", level: 1}, - {title: "H6", id: "h6", level: 5}, - }, - expected: ``, - }, - { - name: "h3 to h5", - items: []item{ - {title: "H3", id: "h3", level: 2}, - {title: "H5", id: "h5", level: 4}, - }, - expected: ``, - }, - { - name: "starts at h4", - items: []item{ - {title: "H4", id: "h4", level: 3}, - }, - expected: ``, - }, - { - name: "starts at h6", - items: []item{ - {title: "H6", id: "h6", level: 5}, - }, - expected: ``, - }, - } { - c.Run(test.name, func(c *qt.C) { - toc := &Fragments{} - for _, item := range test.items { - toc.addAt(&Heading{Title: item.title, ID: item.id}, item.row, item.level) - } - - tocHTML, err := toc.ToHTML(2, -1, false) - c.Assert(err, qt.IsNil) - got := string(tocHTML) - c.Assert(got, qt.Equals, test.expected, qt.Commentf(got)) - c.Assert(got, qt.Not(qt.Contains), "
  • \n") - c.Assert(hasListItemWithoutAnchor(got), qt.Equals, false) - }) - } - - toc := &Fragments{} - toc.addAt(&Heading{Title: "H2", ID: "h2"}, 0, 1) - toc.addAt(&Heading{Title: "H4", ID: "h4"}, 0, 3) - - tocHTML, err := toc.ToHTML(2, 3, false) - c.Assert(err, qt.IsNil) - got := string(tocHTML) - c.Assert(got, qt.Equals, ``, qt.Commentf(got)) - c.Assert(got, qt.Not(qt.Contains), "
  • \n") - c.Assert(hasListItemWithoutAnchor(got), qt.Equals, false) -} - -func hasListItemWithoutAnchor(s string) bool { - for { - i := strings.Index(s, "
  • ") - if i == -1 { - return false - } - s = s[i+len("
  • "):] - if !strings.HasPrefix(strings.TrimLeft(s, " \n\t\r"), "