Hugo Partial for Generating the Table of Contents
... | |
{{- partial "toc.html" . -}} | |
... |
{{/* https://github.com/gohugoio/hugo/issues/1778 */}} | |
{{/* ignore empty links with + */}} | |
{{- $headers := findRE "<h[2-4].*?>(.|\n])+?</h[2-4]>" .Content -}} | |
{{ .Scratch.Set "last_level" 1 }} | |
{{/* at least one header to link to */}} | |
{{- $has_headers := ge (len $headers) 1 -}} | |
{{- if $has_headers -}} | |
<aside class="table-of-contents"> | |
<details> | |
<summary> | |
<b>Table of Contents</b> | |
</summary> | |
{{- range $headers -}} | |
{{- $header := . -}} | |
{{- $base := ($.Page.File.LogicalName) -}} | |
{{- $anchorId := ($header | plainify | htmlUnescape | anchorize) -}} | |
{{- $href := delimit (slice $base $anchorId) "#" | string -}} | |
{{- range findRE "[2-4]" . 1 -}} | |
{{- $next_heading := (int .) -}} | |
{{- if gt $next_heading ($.Scratch.Get "last_level") -}} | |
<ul class="toc-h{{ . }}"> | |
{{- else if lt $next_heading ($.Scratch.Get "last_level") -}} | |
</ul> | |
{{- end -}} | |
<li><a href="{{ relref $.Page $href }}">{{- $header | plainify | htmlUnescape -}}</a></li> | |
{{ $.Scratch.Set "last_level" $next_heading }} | |
{{- end -}} | |
{{- end -}} | |
</details> | |
</aside> | |
{{- end -}} |
This comment has been minimized.
This comment has been minimized.
Neither of these work correctly for me - they both leave I've edited it so that it works with custom anchors which none of the others do, and removed all extra HTML so it just returns nested
{{- $headers := findRE "<h[2-4].*?>(.|\n])+?</h[2-4]>" .Content -}}
{{- $has_headers := ge (len $headers) 1 -}}
{{- if $has_headers -}}
<ul >
{{- range $i, $header := $headers -}}
{{- $headerLevel := index (findRE "[2-4]" . 1) 0 -}}
{{- $headerLevel := len (seq $headerLevel) -}}
{{/* get id="xyz" */}}
{{ $id := index (findRE "(id=\"([^\"]*?)\")" $header 9) 0 }}
{{/* strip id="" to leave xyz (no way to get regex capturing groups in hugo :( */}}
{{ $cleanedID := replace (replace $id "id=\"" "") "\"" "" }}
{{- if ne $i 0 -}}
{{- $prevHeaderLevel := index (findRE "[2-4]" (index $headers (sub $i 1)) 1) 0 -}}
{{- $prevHeaderLevel := len (seq $prevHeaderLevel) -}}
{{- if gt $headerLevel $prevHeaderLevel -}}
{{- range seq (sub $headerLevel $prevHeaderLevel) -}}
<ul>
{{- end}}
{{- end}}
{{- if lt $headerLevel $prevHeaderLevel -}}
{{- range seq (sub $prevHeaderLevel $headerLevel) -}}
</li></ul></li>
{{- end}}
{{- end}}
{{- if eq $headerLevel $prevHeaderLevel -}}
</li>
{{- end}}
<li>
<a href="#{{- $cleanedID -}}">{{- $header | plainify | htmlEscape | safeHTML -}}</a>
{{- if eq $i (sub (len $headers) 1) -}}
{{- range seq (sub $prevHeaderLevel $headerLevel) -}}
</li></ul></li>
{{- end}}
{{- end}}
{{- else}}
<li>
<a href="#{{- $cleanedID -}}">{{- $header | plainify | htmlEscape | safeHTML -}}</a>
{{- end -}}
{{- end -}}
{{- $firstHeaderLevel := len (seq (index (findRE "[2-4]" (index $headers 0) 1) 0)) -}}
{{- $lastHeaderLevel := len (seq (index (findRE "[2-4]" (index $headers (sub (len $headers) 1)) 1) 0)) -}}
{{- range seq (sub $lastHeaderLevel $firstHeaderLevel) -}}
</li></ul></li>
{{- end -}}
</ul>
{{- end -}} |
This comment has been minimized.
This comment has been minimized.
But @Iooeee 's code rendered too many end tags I have investigated into it and wrote a blog post If the blog is too abstract, here is the code {{- $headers := findRE "<h[1-4].*?>(.|\n])+?</h[1-4]>" .Content -}}
{{- $has_headers := ge (len $headers) 1 -}}
{{- if $has_headers -}}
{{- $largest := 6 -}}
{{- range $headers -}}
{{- $headerLevel := index (findRE "[1-4]" . 1) 0 -}}
{{- $headerLevel := len (seq $headerLevel) -}}
{{- if lt $headerLevel $largest -}}
{{- $largest = $headerLevel -}}
{{- end -}}
{{- end -}}
{{- $firstHeaderLevel := len (seq (index (findRE "[1-4]" (index $headers 0) 1) 0)) -}}
{{- $.Scratch.Set "bareul" slice -}}
<div id="TableOfContents">
<ul>
{{- range seq (sub $firstHeaderLevel $largest) -}}
<ul>
{{- $.Scratch.Add "bareul" (sub (add $largest .) 1) -}}
{{- end -}}
{{- range $i, $header := $headers -}}
{{- $headerLevel := index (findRE "[1-4]" . 1) 0 -}}
{{- $headerLevel := len (seq $headerLevel) -}}
{{/* get id="xyz" */}}
{{ $id := index (findRE "(id=\"(.*?)\")" $header 9) 0 }}
{{/* strip id="" to leave xyz (no way to get regex capturing groups in hugo :( */}}
{{ $cleanedID := replace (replace $id "id=\"" "") "\"" "" }}
{{- $header := replaceRE "<h[1-4].*?>((.|\n])+?)</h[1-4]>" "$1" $header -}}
{{- if ne $i 0 -}}
{{- $prevHeaderLevel := index (findRE "[1-4]" (index $headers (sub $i 1)) 1) 0 -}}
{{- $prevHeaderLevel := len (seq $prevHeaderLevel) -}}
{{- if gt $headerLevel $prevHeaderLevel -}}
{{- range seq $prevHeaderLevel (sub $headerLevel 1) -}}
<ul>
{{/* the first should not be recorded */}}
{{- if ne $prevHeaderLevel . -}}
{{- $.Scratch.Add "bareul" . -}}
{{- end -}}
{{- end -}}
{{- else -}}
</li>
{{- if lt $headerLevel $prevHeaderLevel -}}
{{- range seq (sub $prevHeaderLevel 1) -1 $headerLevel -}}
{{- if in ($.Scratch.Get "bareul") . -}}
</ul>
{{/* manually do pop item */}}
{{- $tmp := $.Scratch.Get "bareul" -}}
{{- $.Scratch.Delete "bareul" -}}
{{- $.Scratch.Set "bareul" slice}}
{{- range seq (sub (len $tmp) 1) -}}
{{- $.Scratch.Add "bareul" (index $tmp (sub . 1)) -}}
{{- end -}}
{{- else -}}
</ul></li>
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
<li>
<a href="#{{- $cleanedID -}}">{{- $header | safeHTML -}}</a>
{{- else -}}
<li>
<a href="#{{- $cleanedID -}}">{{- $header | safeHTML -}}</a>
{{- end -}}
{{- end -}}
<!-- {{- $firstHeaderLevel := len (seq (index (findRE "[1-4]" (index $headers 0) 1) 0)) -}} -->
{{ $firstHeaderLevel := $largest }}
{{- $lastHeaderLevel := len (seq (index (findRE "[1-4]" (index $headers (sub (len $headers) 1)) 1) 0)) -}}
</li>
{{- range seq (sub $lastHeaderLevel $firstHeaderLevel) -}}
{{- if in ($.Scratch.Get "bareul") (add . $firstHeaderLevel) -}}
</ul>
{{- else -}}
</ul></li>
{{- end -}}
{{- end -}}
</ul>
</div>
{{- end -}} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This comment has been minimized.
hi, the code snippet has a bug.
while my markdown content headers look like as follow :
your partial will output as follow:

You can modify it like the following: