Skip to content

Instantly share code, notes, and snippets.

@skyzyx
Last active February 27, 2023 04:21
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save skyzyx/a796d66f6a124f057f3374eff0b3f99a to your computer and use it in GitHub Desktop.
Save skyzyx/a796d66f6a124f057f3374eff0b3f99a to your computer and use it in GitHub Desktop.
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 -}}
@AllanChain
Copy link

AllanChain commented Dec 31, 2019

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