|
| 1 | +{{- $headers := findRE "<h[1-6].*?>(.|\n])+?</h[1-6]>" .Content -}} |
| 2 | +{{- $has_headers := ge (len $headers) 1 -}} |
| 3 | +{{- if $has_headers -}} |
| 4 | +<aside id="toc-container" class="toc-container wide"> |
| 5 | + <div class="toc"> |
| 6 | + <details {{if (.Param "TocOpen") }} open{{ end }}> |
| 7 | + <summary accesskey="c" title="(Alt + C)"> |
| 8 | + <span class="details">{{- i18n "toc" | default "Table of Contents" }}</span> |
| 9 | + </summary> |
| 10 | + |
| 11 | + <div class="inner"> |
| 12 | + {{- $largest := 6 -}} |
| 13 | + {{- range $headers -}} |
| 14 | + {{- $headerLevel := index (findRE "[1-6]" . 1) 0 -}} |
| 15 | + {{- $headerLevel := len (seq $headerLevel) -}} |
| 16 | + {{- if lt $headerLevel $largest -}} |
| 17 | + {{- $largest = $headerLevel -}} |
| 18 | + {{- end -}} |
| 19 | + {{- end -}} |
| 20 | + |
| 21 | + {{- $firstHeaderLevel := len (seq (index (findRE "[1-6]" (index $headers 0) 1) 0)) -}} |
| 22 | + |
| 23 | + {{- $.Scratch.Set "bareul" slice -}} |
| 24 | + <ul> |
| 25 | + {{- range seq (sub $firstHeaderLevel $largest) -}} |
| 26 | + <ul> |
| 27 | + {{- $.Scratch.Add "bareul" (sub (add $largest .) 1) -}} |
| 28 | + {{- end -}} |
| 29 | + {{- range $i, $header := $headers -}} |
| 30 | + {{- $headerLevel := index (findRE "[1-6]" . 1) 0 -}} |
| 31 | + {{- $headerLevel := len (seq $headerLevel) -}} |
| 32 | + |
| 33 | + {{/* get id="xyz" */}} |
| 34 | + {{- $id := index (findRE "(id=\"(.*?)\")" $header 9) 0 }} |
| 35 | + |
| 36 | + {{- /* strip id="" to leave xyz, no way to get regex capturing groups in hugo */ -}} |
| 37 | + {{- $cleanedID := replace (replace $id "id=\"" "") "\"" "" }} |
| 38 | + {{- $header := replaceRE "<h[1-6].*?>((.|\n])+?)</h[1-6]>" "$1" $header -}} |
| 39 | + |
| 40 | + {{- if ne $i 0 -}} |
| 41 | + {{- $prevHeaderLevel := index (findRE "[1-6]" (index $headers (sub $i 1)) 1) 0 -}} |
| 42 | + {{- $prevHeaderLevel := len (seq $prevHeaderLevel) -}} |
| 43 | + {{- if gt $headerLevel $prevHeaderLevel -}} |
| 44 | + {{- range seq $prevHeaderLevel (sub $headerLevel 1) -}} |
| 45 | + <ul> |
| 46 | + {{/* the first should not be recorded */}} |
| 47 | + {{- if ne $prevHeaderLevel . -}} |
| 48 | + {{- $.Scratch.Add "bareul" . -}} |
| 49 | + {{- end -}} |
| 50 | + {{- end -}} |
| 51 | + {{- else -}} |
| 52 | + </li> |
| 53 | + {{- if lt $headerLevel $prevHeaderLevel -}} |
| 54 | + {{- range seq (sub $prevHeaderLevel 1) -1 $headerLevel -}} |
| 55 | + {{- if in ($.Scratch.Get "bareul") . -}} |
| 56 | + </ul> |
| 57 | + {{/* manually do pop item */}} |
| 58 | + {{- $tmp := $.Scratch.Get "bareul" -}} |
| 59 | + {{- $.Scratch.Delete "bareul" -}} |
| 60 | + {{- $.Scratch.Set "bareul" slice}} |
| 61 | + {{- range seq (sub (len $tmp) 1) -}} |
| 62 | + {{- $.Scratch.Add "bareul" (index $tmp (sub . 1)) -}} |
| 63 | + {{- end -}} |
| 64 | + {{- else -}} |
| 65 | + </ul> |
| 66 | + </li> |
| 67 | + {{- end -}} |
| 68 | + {{- end -}} |
| 69 | + {{- end -}} |
| 70 | + {{- end }} |
| 71 | + <li> |
| 72 | + <a href="#{{- $cleanedID -}}" aria-label="{{- $header | plainify -}}">{{- $header | safeHTML -}}</a> |
| 73 | + {{- else }} |
| 74 | + <li> |
| 75 | + <a href="#{{- $cleanedID -}}" aria-label="{{- $header | plainify -}}">{{- $header | safeHTML -}}</a> |
| 76 | + {{- end -}} |
| 77 | + {{- end -}} |
| 78 | + <!-- {{- $firstHeaderLevel := len (seq (index (findRE "[1-6]" (index $headers 0) 1) 0)) -}} --> |
| 79 | + {{- $firstHeaderLevel := $largest }} |
| 80 | + {{- $lastHeaderLevel := len (seq (index (findRE "[1-6]" (index $headers (sub (len $headers) 1)) 1) 0)) }} |
| 81 | + </li> |
| 82 | + {{- range seq (sub $lastHeaderLevel $firstHeaderLevel) -}} |
| 83 | + {{- if in ($.Scratch.Get "bareul") (add . $firstHeaderLevel) }} |
| 84 | + </ul> |
| 85 | + {{- else }} |
| 86 | + </ul> |
| 87 | + </li> |
| 88 | + {{- end -}} |
| 89 | + {{- end }} |
| 90 | + </ul> |
| 91 | + </div> |
| 92 | + </details> |
| 93 | + </div> |
| 94 | +</aside> |
| 95 | +<script> |
| 96 | + let activeElement; |
| 97 | + let elements; |
| 98 | + window.addEventListener('DOMContentLoaded', function (event) { |
| 99 | + checkTocPosition(); |
| 100 | + |
| 101 | + elements = document.querySelectorAll('h1[id],h2[id],h3[id],h4[id],h5[id],h6[id]'); |
| 102 | + // Make the first header active |
| 103 | + activeElement = elements[0]; |
| 104 | + const id = encodeURI(activeElement.getAttribute('id')).toLowerCase(); |
| 105 | + document.querySelector(`.inner ul li a[href="#${id}"]`).classList.add('active'); |
| 106 | + }, false); |
| 107 | + |
| 108 | + window.addEventListener('resize', function(event) { |
| 109 | + checkTocPosition(); |
| 110 | + }, false); |
| 111 | + |
| 112 | + window.addEventListener('scroll', () => { |
| 113 | + // Check if there is an object in the top half of the screen or keep the last item active |
| 114 | + activeElement = Array.from(elements).find((element) => { |
| 115 | + if ((getOffsetTop(element) - window.pageYOffset) > 0 && |
| 116 | + (getOffsetTop(element) - window.pageYOffset) < window.innerHeight/2) { |
| 117 | + return element; |
| 118 | + } |
| 119 | + }) || activeElement |
| 120 | + |
| 121 | + elements.forEach(element => { |
| 122 | + const id = encodeURI(element.getAttribute('id')).toLowerCase(); |
| 123 | + if (element === activeElement){ |
| 124 | + document.querySelector(`.inner ul li a[href="#${id}"]`).classList.add('active'); |
| 125 | + } else { |
| 126 | + document.querySelector(`.inner ul li a[href="#${id}"]`).classList.remove('active'); |
| 127 | + } |
| 128 | + }) |
| 129 | + }, false); |
| 130 | + |
| 131 | + const main = parseInt(getComputedStyle(document.body).getPropertyValue('--article-width'), 10); |
| 132 | + const toc = parseInt(getComputedStyle(document.body).getPropertyValue('--toc-width'), 10); |
| 133 | + const gap = parseInt(getComputedStyle(document.body).getPropertyValue('--gap'), 10); |
| 134 | + |
| 135 | + function checkTocPosition() { |
| 136 | + const width = document.body.scrollWidth; |
| 137 | + |
| 138 | + if (width - main - (toc * 2) - (gap * 4) > 0) { |
| 139 | + document.getElementById("toc-container").classList.add("wide"); |
| 140 | + } else { |
| 141 | + document.getElementById("toc-container").classList.remove("wide"); |
| 142 | + } |
| 143 | + } |
| 144 | + |
| 145 | + function getOffsetTop(element) { |
| 146 | + if (!element.getClientRects().length) { |
| 147 | + return 0; |
| 148 | + } |
| 149 | + let rect = element.getBoundingClientRect(); |
| 150 | + let win = element.ownerDocument.defaultView; |
| 151 | + return rect.top + win.pageYOffset; |
| 152 | + } |
| 153 | +</script> |
| 154 | +{{- end }} |
0 commit comments