前言
Hugo
PaperMod 主题轻量且美观,参考网上的教程,并借助 AI
和自己的理解加以修改,记录下来供自己和他人参阅。
准备
PaperMod
提供了丰富的自定义入口,提供了 extended_head.html
和 extended_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 = '▼'; // 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') ? '▲' : '▼';
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
文件中引入 js
和 css
:
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-container
和 wide
等 CSS
类,在 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}
参考
- Hugo博客文章封面图片缩小并移到侧边 | PaperMod主题
- 使用Github Pages+Hugo+PaperMod搭建博客
- Hugo blog & PaperMod
- 折腾 Hugo & PaperMod 主题
- JannikArndt/jannikarndt.github.io@8b99f6c
- Sulv’s Blog | Hugo 博客目录放在侧边 | PaperMod 主题
最后修改于 2024-08-28