PaperMod修改侧边目录
创建 <your_hugo_site>/layouts/partials/toc.html 覆盖,在其中粘贴如下代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 {{- $headers := findRE "<h[1-6].*?>(.|\n])+?</h[1-6]>" .Content -}} {{- $has_headers := ge (len $headers) 1 -}} {{if $has_headers -}} <aside id="toc-container" class="toc-container wide"> <div class="toc"> <details {{if (.Param "TocOpen") }} open{{ end }}> <summary accesskey="c" title="(Alt + C)"> <span class="details">{{i18n "toc" | default "Table of Contents" }}</span> </summary> <div class="inner"> {{- $largest := 6 -}} {{range $headers -}} {{- $headerLevel := index (findRE "[1-6]" . 1) 0 -}} {{- $headerLevel := len (seq $headerLevel) -}} {{if lt $headerLevel $largest -}} {{- $largest = $headerLevel -}} {{end -}} {{end -}} {{- $firstHeaderLevel := len (seq (index (findRE "[1-6]" (index $headers 0) 1) 0)) -}} {{- $.Scratch.Set "bareul" slice -}} <ul> {{range seq (sub $firstHeaderLevel $largest) -}} <ul> {{- $.Scratch.Add "bareul" (sub (add $largest .) 1) -}} {{end -}} {{range $i, $header := $headers -}} {{- $headerLevel := index (findRE "[1-6]" . 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-6].*?>((.|\n])+?)</h[1-6]>" "$1" $header -}} {{if ne $i 0 -}} {{- $prevHeaderLevel := index (findRE "[1-6]" (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 -}}" aria-label="{{- $header | plainify -}}">{{- $header | safeHTML -}}</a> {{else }} <li> <a href="#{{- $cleanedID -}}" aria-label="{{- $header | plainify -}}">{{- $header | safeHTML -}}</a> {{end -}} {{end -}} <!-- {{- $firstHeaderLevel := len (seq (index (findRE "[1-6]" (index $headers 0) 1) 0)) -}} --> {{- $firstHeaderLevel := $largest }} {{- $lastHeaderLevel := len (seq (index (findRE "[1-6]" (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> </details> </div> </aside> <script> let activeElement; let elements; document.addEventListener('DOMContentLoaded', function (event) { checkTocPosition(); elements = document.querySelectorAll('h1[id],h2[id],h3[id],h4[id],h5[id],h6[id]'); if (elements.length > 0) { // Make the first header active activeElement = elements[0]; const id = encodeURI(activeElement.getAttribute('id')).toLowerCase(); document.querySelector(`.inner ul li a[href="#${id}"]`).classList.add('active'); } // Add event listener for the "back to top" link const topLink = document.getElementById('top-link'); if (topLink) { topLink.addEventListener('click', (event) => { // Prevent the default action event.preventDefault(); // Smooth scroll to the top window.scrollTo({ top: 0, behavior: 'smooth' }); }); } }, false); window.addEventListener('resize', function(event) { checkTocPosition(); }, false); window.addEventListener('scroll', () => { // Get the current scroll position const scrollPosition = window.pageYOffset || document.documentElement.scrollTop; // Check if the scroll position is at the top of the page if (scrollPosition === 0) { return; } // Ensure elements is a valid NodeList if (elements && elements.length > 0) { // Check if there is an object in the top half of the screen or keep the last item active activeElement = Array.from(elements).find((element) => { if ((getOffsetTop(element) - scrollPosition) > 0 && (getOffsetTop(element) - scrollPosition) < window.innerHeight / 2) { return element; } }) || activeElement; elements.forEach(element => { const id = encodeURI(element.getAttribute('id')).toLowerCase(); const tocLink = document.querySelector(`.inner ul li a[href="#${id}"]`); if (element === activeElement){ tocLink.classList.add('active'); // Ensure the active element is in view within the .inner container const tocContainer = document.querySelector('.toc .inner'); const linkOffsetTop = tocLink.offsetTop; const containerHeight = tocContainer.clientHeight; const linkHeight = tocLink.clientHeight; // Calculate the scroll position to center the active link const scrollPosition = linkOffsetTop - (containerHeight / 2) + (linkHeight / 2); tocContainer.scrollTo({ top: scrollPosition, behavior: 'smooth' }); } else { tocLink.classList.remove('active'); } }); } }, false); const main = parseInt(getComputedStyle(document.body).getPropertyValue('--article-width'), 10); const toc = parseInt(getComputedStyle(document.body).getPropertyValue('--toc-width'), 10); const gap = parseInt(getComputedStyle(document.body).getPropertyValue('--gap'), 10); function checkTocPosition() { const width = document.body.scrollWidth; if (width - main - (toc * 2) - (gap * 4) > 0) { document.getElementById("toc-container").classList.add("wide"); } else { document.getElementById("toc-container").classList.remove("wide"); } } function getOffsetTop(element) { if (!element.getClientRects().length) { return 0; } let rect = element.getBoundingClientRect(); let win = element.ownerDocument.defaultView; return rect.top + win.pageYOffset; } </script> {{end }} 创建 <your_hugo_site>/assets/css/extended/toc.css 文件,并拷贝以下内容 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 :root { --nav-width: 1380px; --article-width: 650px; --toc-width: 300px; } .toc { margin: 0 2px 40px 2px; border: 1px solid var(--border); background: var(--entry); border-radius: var(--radius); padding: 0.4em; } .toc-container.wide { position: absolute; height: 100%; border-right: 1px solid var(--border); left: calc((var(--toc-width) + var(--gap)) * -1); top: calc(var(--gap) * 2); width: var(--toc-width); } .wide .toc { position: sticky; top: var(--gap); border: unset; background: unset; border-radius: unset; width: 100%; margin: 0 2px 40px 2px; } .toc details summary { cursor: zoom-in; margin-inline-start: 20px; padding: 12px 0; } .toc details[open] summary { font-weight: 500; } .toc-container.wide .toc .inner { margin: 0; } .active { font-size: 110%; font-weight: 600; } .toc ul { list-style-type: circle; } .toc .inner { margin: 0 0 0 20px; padding: 0px 15px 15px 20px; font-size: 16px; /*目录显示高度*/ max-height: 83vh; overflow-y: auto; } .toc .inner::-webkit-scrollbar-thumb { /*滚动条*/ background: var(--border); border: 7px solid var(--theme); border-radius: var(--radius); } .toc li ul { margin-inline-start: calc(var(--gap) * 0.5); list-style-type: none; } .toc li { list-style: none; font-size: 0.95rem; padding-bottom: 5px; } .toc li a:hover { color: var(--secondary); }