PaperMod主题最新修改记录
Hugo PaperMod 主题 7.0 版本最新修改记录。

前言

Hugo PaperMod 主题轻量且美观,参考网上的教程,并借助 AI 和自己的理解加以修改,记录下来供自己和他人参阅。

准备

PaperMod 提供了丰富的自定义入口,提供了 extended_head.htmlextended_footer.html 来修改,并且 assets/css/extended/ 下可以添加任意名称 CSS 文件,主题都会引入。

悬浮动画

CSS 的修改直接在 assets/css/extended/ 文件夹下新建文件写入,分开方便调试,后面都一样不再赘述。

 1/* 悬浮动画 */
 2/* 左上角logo悬浮动画 */
 3.logo a:hover {
 4    transition: 0.15s;
 5    color: grey;
 6  }
 7
 8/* 首页icon悬浮动画 */
 9svg:hover {
10    transition: 0.15s;
11    transform: scaleX(1.1) scaleY(1.1);
12}
13
14.social-icons a svg:hover{
15    color: #ffbb3d !important;
16
17}
18/* 模式切换按钮悬浮动画 */
19#moon:hover {
20    transition: 0.15s;
21    color: deepskyblue;
22}
23
24#sun:hover {
25    transition: 0.15s;
26    color: gold;
27}
28/* 菜单栏文字悬浮动画 */
29#menu a:hover {
30    transition: 0.15s;
31    color: grey;
32}

首页信息居中

 1/* 首页信息居中 */
 2.first-entry .entry-header {
 3    align-self: center;
 4}
 5.home-info .entry-content {
 6    display: flex;
 7    flex-direction: column;
 8    justify-content: center;
 9    align-items: center;
10}
11.first-entry .entry-footer {
12    display: flex;
13    justify-content: center;
14    align-items: center;
15}

字体修改

字体使用了两种,一个纯英文字体 Nunito,一个鸿蒙字体,字体设置中先使用英文字体,再使用中文,这样可以中英文字体分开,代码块字体也可以设置不一样的。

~/layouts/partials/extended_head.html 中引入字体,并且在 CSS 中添加样式:

1{{/*  字体引入  */}}
2<link rel="stylesheet" href="https://s1.hdslb.com/bfs/static/jinkela/long/font/regular.css" />
3<link rel="preconnect" href="https://fonts.googleapis.com">
4<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
5<link href="https://fonts.googleapis.com/css2?family=Nunito:ital,wght@0,200..1000;1,200..1000&display=swap" rel="stylesheet">
1body {
2    font-family: Nunito, HarmonyOS_Regular, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
3}
4
5.post-content pre, code {
6    font-family: consolas, sans-serif;
7    max-height: 40rem;
8}

首页文章封面图侧边显示

首页文章列表的封面图很高,导致一页放不下几篇文章,希望可以把封面图放到文章信息侧边,有两种方法可以实现。

一种只需要添加自定义 CSS 文件到目录下就可以实现,简单方便,只是文章没有封面图时,文章的标题和描述会两列显示,即标题占用到了封面的位置。

另一种方法是通过修改模板文件实现,可以通过站点设置控制显示在左侧、右侧或默认的顶部。参考了这篇文章:Hugo博客文章封面图片缩小并移到侧边 | PaperMod主题

方法一

可以直接到 GitHub 下载,这个项目还包含了其他的功能,具体访问仓库地址,或者直接在 assets/css/extended/ 目录下新建文件粘贴下面的内容:

 1/* Allocate a single column when the width of the page is small. */
 2.post-entry {
 3    display: grid;
 4    grid-template-columns: 1fr;
 5    grid-gap: 5px 0px;
 6}
 7
 8/* Allocate two columns when there is enough width. *
 9 * The thumbnail is placed in the first column, while the rest of
10 * the children are placed in the second column. */
11@media (min-width: 700px) {
12    .post-entry {
13        grid-template-columns: 2fr 3fr;
14        grid-gap: 0px 10px;
15    }
16}
17
18.post-entry .entry-cover {
19    max-width: fit-content;
20    margin: auto;
21    grid-row: span 3;
22}
23
24.post-entry .entry-header {
25    align-self: center;
26}
27
28.post-entry .entry-content {
29    align-self: center;
30}
31
32.post-entry .entry-footer {
33    align-self: end;
34}

上述代码封面图显示在左侧,如果想要显示在右侧,替换成下面的:

 1/* Allocate a single column when the width of the page is small. */
 2.post-entry {
 3    display: grid;
 4    grid-template-columns: 1fr;
 5    grid-gap: 5px 0px;
 6}
 7
 8/* Allocate two columns when there is enough width. */
 9/* The thumbnail is placed in the second column, while the rest of */
10/* the children are placed in the first column. */
11@media (min-width: 700px) {
12    .post-entry {
13        grid-template-columns: 3fr 2fr;
14        grid-gap: 0px 10px;
15    }
16    .post-entry .entry-cover {
17        grid-column: 2;
18        grid-row: 1 / span 3;
19    }
20}
21
22.post-entry .entry-cover {
23    max-width: fit-content;
24    margin: auto;
25}
26
27.post-entry .entry-header {
28    align-self: center;
29}
30
31.post-entry .entry-content {
32    align-self: center;
33}
34
35.post-entry .entry-footer {
36    align-self: end;
37}

方法二

这种方法较为复杂,好处是可以通过参数控制直接控制显示左侧或者右侧。

首先复制主题 layouts\_default\list.html 文件到根目录下,在其中修改,找到大概 66<article></article> 包裹的元素,将代码换成这部分:

 1<article class="{{ $class }}{{ if and .Site.Params.homeCoverPosition .Params.cover.image }} cover-{{ .Site.Params.homeCoverPosition }}{{ end }}">
 2  {{- $isHidden := (.Param "cover.hiddenInList") | default (.Param "cover.hidden") | default false }}
 3  {{- if and (not $isHidden) .Params.cover.image }}
 4  <div class="post-content-wrapper">
 5    <div class="post-cover">
 6      {{- partial "cover.html" (dict "cxt" . "IsSingle" false "isHidden" $isHidden) }}
 7    </div>
 8    <div class="post-info">
 9  {{- else }}
10  <div class="post-content-wrapper">
11    <div class="post-info">
12  {{- end }}
13      <header class="entry-header">
14        <h2 class="entry-hint-parent">
15          {{- .Title }}
16          {{- if .Draft }}
17          <span class="entry-hint" title="Draft">
18            <svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 -960 960 960" fill="currentColor">
19              <path d="M160-410v-60h300v60H160Zm0-165v-60h470v60H160Zm0-165v-60h470v60H160Zm360 580v-123l221-220q9-9 20-13t22-4q12 0 23 4.5t20 13.5l37 37q9 9 13 20t4 22q0 11-4.5 22.5T862.09-380L643-160H520Zm300-263-37-37 37 37ZM580-220h38l121-122-18-19-19-18-122 121v38Zm141-141-19-18 37 37-18-19Z" />
20            </svg>
21          </span>
22          {{- end }}
23        </h2>
24      </header>
25      {{- if (ne (.Param "hideSummary") true) }}
26      <div class="entry-content">
27        <p>{{ .Summary | plainify | htmlUnescape }}{{ if .Truncated }}...{{ end }}</p>
28      </div>
29      {{- end }}
30      {{- if not (.Param "hideMeta") }}
31      <footer class="entry-footer">
32        {{- partial "post_meta.html" . -}}
33      </footer>
34      {{- end }}
35    </div>
36  </div>
37  <a class="entry-link" aria-label="post link to {{ .Title | plainify }}" href="{{ .Permalink }}"></a>
38</article>

新建一个文件放置 CSS 代码:

 1.post-entry {
 2  overflow: hidden;
 3}
 4
 5.post-content-wrapper {
 6  display: flex;
 7  flex-direction: column;
 8  height: 100%;
 9}
10
11.cover-right .post-content-wrapper .post-cover .entry-cover,
12.cover-left .post-content-wrapper .post-cover .entry-cover 
13{
14  margin-bottom: unset;
15  margin-top: unset;
16}
17
18.entry-cover {
19  overflow: hidden;
20  display: flex;
21  justify-content: center;
22  align-items: center;
23  margin-top: var(--gap);
24}
25
26.cover-left .post-content-wrapper,
27.cover-right .post-content-wrapper {
28  flex-direction: row;
29  align-items: center;
30}
31
32.cover-left .post-cover,
33.cover-right .post-cover {
34  width: 40%;
35  margin-bottom: 0;
36  margin-right: 20px;
37}
38
39.cover-right .post-content-wrapper {
40  flex-direction: row-reverse;
41}
42
43.cover-right .post-cover {
44  margin-right: 0;
45  margin-left: 20px;
46}
47
48.cover-left .post-info,
49.cover-right .post-info {
50  width: 60%;
51}
52
53/* 修复文章详情页图片描述位置 */
54.post-single .entry-cover {
55  flex-direction: column;
56  margin-bottom: 10px;
57}
58
59
60/* 移动设备默认上方 */
61@media (max-width: 768px) {
62  .cover-left .post-content-wrapper,
63  .cover-right .post-content-wrapper {
64    flex-direction: column;
65  }
66
67  .cover-left .post-cover,
68  .cover-right .post-cover,
69  .cover-left .post-info,
70  .cover-right .post-info {
71    width: 100%;
72    margin-right: 0;
73    margin-left: 0;
74  }
75  .entry-cover {
76    margin-bottom: var(--gap) !important;
77  }
78}

最后在站点配置中添加配置来控制封面图位置:

1params:
2  homeCoverPosition: right # left/right/top

代码块功能

优化代码块,添加语言显示、Mac 风格色块以及代码块折叠展开功能,复制主题的 layouts\partials\footer.html 到根目录下,找到其中代码块复制功能部分,大概在 95 行左右,替换代码:

  1{{- if (and (eq .Kind "page") (ne .Layout "archives") (ne .Layout "search") (or (.Param "ShowCodeCopyButtons") (.Param "ShowMacDots") (.Param "ShowCodeLang") (.Param "ShowExpandButton"))) }}
  2<style>
  3    .code-toolbar {
  4        display: flex;
  5        justify-content: space-between;
  6        align-items: center;
  7        padding: 5px 10px;
  8        {{/*  background: var(--code-block-bg);  */}}
  9        background: #232323;
 10        border-top-left-radius: 5px;
 11        border-top-right-radius: 5px;
 12        font-size: 0.8em;
 13        position: relative;
 14    }
 15    .mac-dots {
 16        width: 12px;
 17        height: 12px;
 18        border-radius: 50%;
 19        background-color: #ff5f56;
 20        box-shadow: 20px 0 0 #ffbd2e, 40px 0 0 #27c93f;
 21        margin-right: 5px;
 22    }
 23    .lang-label {
 24        flex-grow: 1;
 25        text-align: center;
 26        margin: 0 5px;
 27        color: rgba(255,255,255,.8);
 28    }
 29    .toolbar-group {
 30        display: flex;
 31        align-items: center;
 32    }
 33    .expand-button, .copy-code {
 34        background: none;
 35        border: none;
 36        cursor: pointer;
 37        padding: 0 5px;
 38    }
 39    .highlight {
 40        position: relative;
 41    }
 42    .highlight.collapsible {
 43        max-height: {{ .Site.Params.codeMaxHeight | default "300px" }};
 44        overflow: hidden;
 45    }
 46    .highlight.expanded {
 47        max-height: none;
 48    }
 49    .highlight pre {
 50        margin-bottom: 0;
 51    }
 52    .expand-button {
 53        position: absolute;
 54        bottom: 0;
 55        left: 50%;
 56        transform: translateX(-50%);
 57        background-color: transparent;
 58        padding: 5px 10px;
 59        border-radius: 5px 5px 0 0;
 60        display: none;
 61        height: 30px;
 62        &:hover {
 63            background: rgba(255,255,255,.1);
 64            color: #fff;
 65        }
 66    }
 67    .highlight.collapsible .expand-button {
 68        display: block;
 69    }
 70    .highlight table {
 71        margin-bottom: 0;
 72    }
 73    .post-content pre code {
 74        overflow-x: auto;
 75        overflow-y: hidden;
 76    }
 77</style>
 78
 79<script>
 80    document.addEventListener('DOMContentLoaded', () => {
 81        const codeBlocks = document.querySelectorAll('.highlight');
 82        const maxHeight = parseInt('{{ .Site.Params.codeMaxHeight | default "300" }}');
 83        
 84        codeBlocks.forEach((block) => {
 85            const pre = block.querySelector('pre');
 86            const code = pre.querySelector('code');
 87
 88            // Determine if a toolbar is needed
 89            let toolbarNeeded = false;
 90            if ({{ .Param "ShowMacDots" }} || {{ .Param "ShowCodeLang" }}) {
 91                toolbarNeeded = true;
 92            }
 93
 94            if (toolbarNeeded) {
 95                const toolbar = document.createElement('div');
 96                toolbar.classList.add('code-toolbar');
 97                block.insertBefore(toolbar, block.firstChild);
 98
 99                const leftGroup = document.createElement('div');
100                leftGroup.classList.add('toolbar-group');
101                toolbar.appendChild(leftGroup);
102
103                const rightGroup = document.createElement('div');
104                rightGroup.classList.add('toolbar-group');
105                toolbar.appendChild(rightGroup);
106
107                if ({{ .Param "ShowMacDots" }}) {
108                    const macDots = document.createElement('div');
109                    macDots.classList.add('mac-dots');
110                    leftGroup.appendChild(macDots);
111                }
112
113                if ({{ .Param "ShowCodeLang" }}) {
114                    let language = '';
115                    const possibleElements = [
116                        block,
117                        block.querySelector('code'),
118                        block.querySelector('pre > code'),
119                        block.querySelector('pre'),
120                        block.querySelector('td:nth-child(2) code')
121                    ];
122
123                    for (const element of possibleElements) {
124                        if (element && element.className) {
125                            const elementLanguageClass = element.className.split(' ').find(cls => cls.startsWith('language-'));
126                            if (elementLanguageClass) {
127                                language = elementLanguageClass.replace('language-', '');
128                                break;
129                            }
130                        }
131                    }
132
133                    if (language) {
134                        const langLabel = document.createElement('div');
135                        langLabel.classList.add('lang-label');
136                        langLabel.textContent = language;
137                        toolbar.insertBefore(langLabel, rightGroup);
138                    }
139                }
140
141                if ({{ .Param "ShowCodeCopyButtons" }}) {
142                    const copyButton = document.createElement('button');
143                    copyButton.classList.add('copy-code');
144                    copyButton.innerHTML = '{{- i18n "code_copy" | default "copy" }}';
145                    rightGroup.appendChild(copyButton);
146
147                    copyButton.addEventListener('click', () => {
148                        let textToCopy = code.textContent;
149                        if (code.parentNode.parentNode.parentNode.parentNode.parentNode.nodeName == "TABLE") {
150                            textToCopy = Array.from(code.parentNode.parentNode.parentNode.querySelectorAll('td:nth-child(2)'))
151                                .map(td => td.textContent)
152                                .join('\n');
153                        }
154                        
155                        if ('clipboard' in navigator) {
156                            navigator.clipboard.writeText(textToCopy);
157                            copyingDone();
158                            return;
159                        }
160
161                        const textArea = document.createElement('textarea');
162                        textArea.value = textToCopy;
163                        document.body.appendChild(textArea);
164                        textArea.select();
165                        try {
166                            document.execCommand('copy');
167                            copyingDone();
168                        } catch (e) { };
169                        document.body.removeChild(textArea);
170                    });
171
172                    function copyingDone() {
173                        copyButton.innerHTML = '{{- i18n "code_copied" | default "copied!" }}';
174                        setTimeout(() => {
175                            copyButton.innerHTML = '{{- i18n "code_copy" | default "copy" }}';
176                        }, 2000);
177                    }
178                }
179            } else if ({{ .Param "ShowCodeCopyButtons" }}) {
180                const copyButton = document.createElement('button');
181                copyButton.classList.add('copy-code');
182                copyButton.innerHTML = '{{- i18n "code_copy" | default "copy" }}';
183                if (block.classList.contains("highlight")) {
184                    block.appendChild(copyButton);
185                } else if (block.parentNode.firstChild == block) {
186                    // td containing LineNos
187                } else if (code.parentNode.parentNode.parentNode.parentNode.parentNode.nodeName == "TABLE") {
188                    // table containing LineNos and code
189                    code.parentNode.parentNode.parentNode.parentNode.parentNode.appendChild(copyButton);
190                } else {
191                    // code blocks not having highlight as parent class
192                    code.parentNode.appendChild(copyButton);
193                }
194
195                copyButton.addEventListener('click', () => {
196                    let textToCopy = code.textContent;
197                    if (code.parentNode.parentNode.parentNode.parentNode.parentNode.nodeName == "TABLE") {
198                        textToCopy = Array.from(code.parentNode.parentNode.parentNode.querySelectorAll('td:nth-child(2)'))
199                            .map(td => td.textContent)
200                            .join('\n');
201                    }
202                    
203                    if ('clipboard' in navigator) {
204                        navigator.clipboard.writeText(textToCopy);
205                        copyingDone();
206                        return;
207                    }
208
209                    const textArea = document.createElement('textarea');
210                    textArea.value = textToCopy;
211                    document.body.appendChild(textArea);
212                    textArea.select();
213                    try {
214                        document.execCommand('copy');
215                        copyingDone();
216                    } catch (e) { };
217                    document.body.removeChild(textArea);
218                });
219
220                function copyingDone() {
221                    copyButton.innerHTML = '{{- i18n "code_copied" | default "copied!" }}';
222                    setTimeout(() => {
223                        copyButton.innerHTML = '{{- i18n "code_copy" | default "copy" }}';
224                    }, 2000);
225                }
226            }
227
228            if ({{ .Param "ShowExpandButton" }}) {
229                const expandButton = document.createElement('button');
230                expandButton.classList.add('expand-button');
231                expandButton.innerHTML = '&#9660;'; // Down arrow
232                block.appendChild(expandButton);
233
234                if (pre.offsetHeight > maxHeight) {
235                    block.classList.add('collapsible');
236                    expandButton.style.display = 'block';
237
238                    expandButton.addEventListener('click', () => {
239                        block.classList.toggle('expanded');
240                        expandButton.innerHTML = block.classList.contains('expanded') ? '&#9650;' : '&#9660;';
241                    });
242                }
243            }
244        });
245    });
246</script>
247{{- end }}

提示

这里直接将样式写入了模板中,工具栏的背景可以自己更换,可以设置成代码块背景色成为一个整体,也可以自己更改。

然后同样在站点配置文件中添加参数控制开关:

1params:
2# 代码块功能
3  ShowMacDots: true # Mac色块
4  ShowCodeLang: true # 语言显示
5  ShowExpandButton: true # 代码块折叠
6  ShowCodeCopyButtons: true # 代码块复制按钮
7  codeMaxHeight: "300px" # 代码块最大折叠高度

添加 Waline 评论

将主题目录下的 layouts\partials\comments.html 文件复制到站点根目录,写入代码:

 1{{ if .Site.Params.walineServer }}
 2<div id="waline"></div>
 3<script>
 4    Waline.init({
 5        el: '#waline',
 6        //path: location.pathname,
 7        dark: "body.dark",
 8        serverURL: "{{.Site.Params.walineServer}}",
 9    });
10    
11    </script>
12{{ end }}

然后在 extended_head.html 文件中引入 jscss

1{{/*  Waline评论引入  */}}
2{{ if and (.Site.Params.walineServer) (.IsPage) }}
3<script src="https://unpkg.com/@waline/client@v2/dist/waline.js"></script>
4  <link
5    rel="stylesheet"
6    href="https://unpkg.com/@waline/client@v2/dist/waline.css"
7  />
8{{ end }}

最后在站点配置中配置自己的地址:

1params:
2  comments: true
3  walineServer: https://waline.vercel.app

目录侧边显示

有三种方法更改目录到侧边,两种是直接添加自定义 CSS,一种需要修改模板文件。

方法一

直接添加自定义 CSS 样式,方法来自:Commit Make ToC float

方法比较简单,缺点是目录没有激活项高亮,不会随着页面滚动而滚动。

 1.toc {
 2    padding: 14px;
 3    border: solid 1px lightgray;
 4    font-size: 12px;
 5}
 6
 7
 8@media (min-width: 1280px) {
 9    .toc {
10        position: sticky;
11        float: left;
12        --toc-left: calc(100vw / 50);
13        left: var(--toc-left); /* _minimum_ distance from left screen border */
14        top: 100px;
15        margin-left: -1000px; /* overruled by left */
16
17        width: calc((100vw - var(--main-width) - 2 * var(--gap)) / 2 - 2 * var(--toc-left));
18        padding: 14px;
19        border: solid 1px lightgray;
20        font-size: 12px;
21    }
22
23    .toc .inner {
24        padding: 0;
25    }
26
27    .toc details summary {
28        margin-inline-start: 0;
29        margin-bottom: 10px;
30    }
31
32}
33
34
35
36summary {
37    cursor: pointer !important;
38}

方法二

同样使用 CSS 实现,访问项目地址

下载下来添加到自己的目录即可,还包含了文章缩略图。

重要

测试后发现,如果只添加目录样式,会有错位,需要添加他的自定义设置文件,但是文章总体布局会变宽,具体自行测试。

方法三

更改模板文件,使用 toc-containerwideCSS 类,在 JavaScript 中动态添加或移除这些类,以响应屏幕宽度的变化。动态样式控制通过 checkTocPosition () 函数来实现,确保目录在不同屏幕大小下的合适显示,最初来自一个外国博主的 PR,访问详细信息:Toc on the side #675 ,可以参考博客:Sulv’s Blog | Hugo 博客目录放在侧边 | PaperMod 主题

复制模板文件 ~/themes\PaperMod\layouts\partials\toc.html 到站点根目录下,替换内容并添加样式:

  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 }}
 1:root {
 2    --nav-width: 1380px;
 3    --article-width: 650px;
 4    --toc-width: 300px;
 5}
 6
 7.toc {
 8    margin: 0 2px 40px 2px;
 9    border: 1px solid var(--border);
10    background: var(--entry);
11    border-radius: var(--radius);
12    padding: 0.4em;
13}
14
15.toc-container.wide {
16    position: absolute;
17    height: 100%;
18    border-right: 1px solid var(--border);
19    left: calc((var(--toc-width) + var(--gap)) * -1);
20    top: calc(var(--gap) * 2);
21    width: var(--toc-width);
22}
23
24.wide .toc {
25    position: sticky;
26    top: var(--gap);
27    border: unset;
28    background: unset;
29    border-radius: unset;
30    width: 100%;
31    margin: 0 2px 40px 2px;
32}
33
34.toc details summary {
35    cursor: zoom-in;
36    margin-inline-start: 20px;
37    padding: 12px 0;
38}
39
40.toc details[open] summary {
41    font-weight: 500;
42}
43
44.toc-container.wide .toc .inner {
45    margin: 0;
46}
47
48.active {
49    font-size: 110%;
50    font-weight: 600;
51}
52
53.toc ul {
54    list-style-type: circle;
55}
56
57.toc .inner {
58    margin: 0 0 0 20px;
59    padding: 0px 15px 15px 20px;
60    font-size: 16px;
61
62    /*目录显示高度*/
63    max-height: 83vh;
64    overflow-y: auto;
65}
66
67.toc .inner::-webkit-scrollbar-thumb {  /*滚动条*/
68    background: var(--border);
69    border: 7px solid var(--theme);
70    border-radius: var(--radius);
71}
72
73.toc li ul {
74    margin-inline-start: calc(var(--gap) * 0.5);
75    list-style-type: none;
76}
77
78.toc li {
79    list-style: none;
80    font-size: 0.95rem;
81    padding-bottom: 5px;
82}
83
84.toc li a:hover {
85    color: var(--secondary);
86}

参考


最后修改于 2024-08-28