Skip to content

Commit

Permalink
Add pre and post fields for overriding ToC heading
Browse files Browse the repository at this point in the history
Pre and post allow users to add semantically correct headings to ToC
without breaking existing functionality.

See gohugoio#8338
  • Loading branch information
IveGotNorto committed Jun 17, 2021
1 parent a886dd5 commit 0b8a19a
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 15 deletions.
6 changes: 6 additions & 0 deletions docs/content/en/getting-started/configuration-markup.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,12 @@ endLevel
ordered
: Whether or not to generate an ordered list instead of an unordered list.

pre
: HTML to immediately precede the outer list element. Default value is `<nav id="TableOfContents">`.

post
: HTML to immediately follow the outer list element. Default value is `</nav>`.


## Markdown Render Hooks

Expand Down
2 changes: 2 additions & 0 deletions hugolib/page__per_output.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ func newPageContentOutput(p *pageState, po *pageOutput) (*pageContentOutput, err
cfg.TableOfContents.StartLevel,
cfg.TableOfContents.EndLevel,
cfg.TableOfContents.Ordered,
cfg.TableOfContents.Pre,
cfg.TableOfContents.Post,
),
)
} else {
Expand Down
2 changes: 1 addition & 1 deletion markup/goldmark/convert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ description

toc, ok := b.(converter.TableOfContentsProvider)
c.Assert(ok, qt.Equals, true)
tocHTML := toc.TableOfContents().ToHTML(1, 2, false)
tocHTML := toc.TableOfContents().ToHTML(1, 2, false, nil, nil)
c.Assert(tocHTML, qt.Contains, "TableOfContents")
}

Expand Down
6 changes: 3 additions & 3 deletions markup/goldmark/toc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ And then some.
c.Assert(err, qt.IsNil)
b, err := conv.Convert(converter.RenderContext{Src: []byte(content), RenderTOC: true})
c.Assert(err, qt.IsNil)
got := b.(converter.TableOfContentsProvider).TableOfContents().ToHTML(2, 3, false)
got := b.(converter.TableOfContentsProvider).TableOfContents().ToHTML(2, 3, false, nil, nil)
c.Assert(got, qt.Equals, `<nav id="TableOfContents">
<ul>
<li><a href="#first-h2---now-with-typography">First h2&mdash;now with typography!</a>
Expand Down Expand Up @@ -110,7 +110,7 @@ func TestEscapeToc(t *testing.T) {
// content := ""
b, err := safeConv.Convert(converter.RenderContext{Src: []byte(content), RenderTOC: true})
c.Assert(err, qt.IsNil)
got := b.(converter.TableOfContentsProvider).TableOfContents().ToHTML(1, 2, false)
got := b.(converter.TableOfContentsProvider).TableOfContents().ToHTML(1, 2, false, nil, nil)
c.Assert(got, qt.Equals, `<nav id="TableOfContents">
<ul>
<li><a href="#a--b--c--d">A &lt; B &amp; C &gt; D</a></li>
Expand All @@ -122,7 +122,7 @@ func TestEscapeToc(t *testing.T) {

b, err = unsafeConv.Convert(converter.RenderContext{Src: []byte(content), RenderTOC: true})
c.Assert(err, qt.IsNil)
got = b.(converter.TableOfContentsProvider).TableOfContents().ToHTML(1, 2, false)
got = b.(converter.TableOfContentsProvider).TableOfContents().ToHTML(1, 2, false, nil, nil)
c.Assert(got, qt.Equals, `<nav id="TableOfContents">
<ul>
<li><a href="#a--b--c--d">A &lt; B &amp; C &gt; D</a></li>
Expand Down
24 changes: 21 additions & 3 deletions markup/tableofcontents/tableofcontents.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,15 @@ func (toc *Root) AddAt(h Header, row, level int) {
}

// ToHTML renders the ToC as HTML.
func (toc Root) ToHTML(startLevel, stopLevel int, ordered bool) string {
func (toc Root) ToHTML(startLevel, stopLevel int, ordered bool, pre *string, post *string) string {
b := &tocBuilder{
s: strings.Builder{},
h: toc.Headers,
startLevel: startLevel,
stopLevel: stopLevel,
ordered: ordered,
pre: pre,
post: post,
}
b.Build()
return b.s.String()
Expand All @@ -81,16 +83,26 @@ type tocBuilder struct {
startLevel int
stopLevel int
ordered bool
pre *string
post *string
}

func (b *tocBuilder) Build() {
b.writeNav(b.h)
}

func (b *tocBuilder) writeNav(h Headers) {
b.s.WriteString("<nav id=\"TableOfContents\">")
if b.pre != nil {
b.s.WriteString(*b.pre)
} else {
b.s.WriteString("<nav id=\"TableOfContents\">")
}
b.writeHeaders(1, 0, b.h)
b.s.WriteString("</nav>")
if b.post != nil {
b.s.WriteString(*b.post)
} else {
b.s.WriteString("</nav>")
}
}

func (b *tocBuilder) writeHeaders(level, indent int, h Headers) {
Expand Down Expand Up @@ -154,6 +166,8 @@ var DefaultConfig = Config{
StartLevel: 2,
EndLevel: 3,
Ordered: false,
Pre: nil,
Post: nil,
}

type Config struct {
Expand All @@ -167,4 +181,8 @@ type Config struct {

// Whether to produce a ordered list or not.
Ordered bool

// Override default behavior for ToC navigation heading.
Pre *string
Post *string
}
50 changes: 42 additions & 8 deletions markup/tableofcontents/tableofcontents_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func TestToc(t *testing.T) {
toc.AddAt(Header{Text: "1-H3-1", ID: "1-h2-2"}, 0, 2)
toc.AddAt(Header{Text: "Header 2", ID: "h1-2"}, 1, 0)

got := toc.ToHTML(1, -1, false)
got := toc.ToHTML(1, -1, false, nil, nil)
c.Assert(got, qt.Equals, `<nav id="TableOfContents">
<ul>
<li><a href="#h1-1">Header 1</a>
Expand All @@ -47,15 +47,15 @@ func TestToc(t *testing.T) {
</ul>
</nav>`, qt.Commentf(got))

got = toc.ToHTML(1, 1, false)
got = toc.ToHTML(1, 1, false, nil, nil)
c.Assert(got, qt.Equals, `<nav id="TableOfContents">
<ul>
<li><a href="#h1-1">Header 1</a></li>
<li><a href="#h1-2">Header 2</a></li>
</ul>
</nav>`, qt.Commentf(got))

got = toc.ToHTML(1, 2, false)
got = toc.ToHTML(1, 2, false, nil, nil)
c.Assert(got, qt.Equals, `<nav id="TableOfContents">
<ul>
<li><a href="#h1-1">Header 1</a>
Expand All @@ -68,15 +68,15 @@ func TestToc(t *testing.T) {
</ul>
</nav>`, qt.Commentf(got))

got = toc.ToHTML(2, 2, false)
got = toc.ToHTML(2, 2, false, nil, nil)
c.Assert(got, qt.Equals, `<nav id="TableOfContents">
<ul>
<li><a href="#1-h2-1">1-H2-1</a></li>
<li><a href="#1-h2-2">1-H2-2</a></li>
</ul>
</nav>`, qt.Commentf(got))

got = toc.ToHTML(1, -1, true)
got = toc.ToHTML(1, -1, true, nil, nil)
c.Assert(got, qt.Equals, `<nav id="TableOfContents">
<ol>
<li><a href="#h1-1">Header 1</a>
Expand All @@ -94,6 +94,40 @@ func TestToc(t *testing.T) {
</nav>`, qt.Commentf(got))
}

func TestPrePostToc(t *testing.T) {
c := qt.New(t)

toc := &Root{}

pre := "<nav id=\"TableOfContents\"><h1>Woo</h1>"
post := "</nav>"
empty := ""

toc.AddAt(Header{Text: "Header 1", ID: "h1-1"}, 0, 0)

got := toc.ToHTML(1, -1, false, &pre, &post)
c.Assert(got, qt.Equals, `<nav id="TableOfContents"><h1>Woo</h1>
<ul>
<li><a href="#h1-1">Header 1</a></li>
</ul>
</nav>`, qt.Commentf(got))

got = toc.ToHTML(1, -1, false, &pre, &empty)
c.Assert(got, qt.Equals, `<nav id="TableOfContents"><h1>Woo</h1>
<ul>
<li><a href="#h1-1">Header 1</a></li>
</ul>
`, qt.Commentf(got))

got = toc.ToHTML(1, -1, false, &empty, &post)
c.Assert(got, qt.Equals, `
<ul>
<li><a href="#h1-1">Header 1</a></li>
</ul>
</nav>`, qt.Commentf(got))

}

func TestTocMissingParent(t *testing.T) {
c := qt.New(t)

Expand All @@ -103,7 +137,7 @@ func TestTocMissingParent(t *testing.T) {
toc.AddAt(Header{Text: "H3", ID: "h3"}, 1, 2)
toc.AddAt(Header{Text: "H3", ID: "h3"}, 1, 2)

got := toc.ToHTML(1, -1, false)
got := toc.ToHTML(1, -1, false, nil, nil)
c.Assert(got, qt.Equals, `<nav id="TableOfContents">
<ul>
<li>
Expand All @@ -124,15 +158,15 @@ func TestTocMissingParent(t *testing.T) {
</ul>
</nav>`, qt.Commentf(got))

got = toc.ToHTML(3, 3, false)
got = toc.ToHTML(3, 3, false, nil, nil)
c.Assert(got, qt.Equals, `<nav id="TableOfContents">
<ul>
<li><a href="#h3">H3</a></li>
<li><a href="#h3">H3</a></li>
</ul>
</nav>`, qt.Commentf(got))

got = toc.ToHTML(1, -1, true)
got = toc.ToHTML(1, -1, true, nil, nil)
c.Assert(got, qt.Equals, `<nav id="TableOfContents">
<ol>
<li>
Expand Down

0 comments on commit 0b8a19a

Please sign in to comment.