I. 主题文件#
Hugo PaperMod - GitHub
II. 主题安装#
2.1 首次安装(2选1)#
参考👉 Installation · hugo-PaperMod Wiki
1
| git submodule add --depth=1 https://github.com/adityatelange/hugo-PaperMod.git themes/PaperMod
|
2.2 更换旧主题(2选1)#
2.2.1 删除旧主题子模块#
先删除当前的主题子模块引用。假设当前主题是 old-theme,你可以执行以下命令:
1
2
3
4
| git submodule deinit -f themes/old-theme
rm -rf .git/modules/themes/old-theme
git rm -f themes/old-theme
git commit -m "Remove old theme"
|
上述命令将会移除 Git 子模块引用,并且在版本控制中提交该变动。
2.2.2 添加新主题子模块#
接下来,你可以添加新的主题子模块。如 PaperMod 主题,可以执行:
1
2
| git submodule add --depth=1 https://github.com/adityatelange/hugo-PaperMod.git themes/PaperMod
git submodule update --init --recursive
|
2.2.3 更新网站配置文件#
在你的 config.toml 或 config.yaml 中修改 theme 配置项为新的主题名称。例如:
2.3 主题更新#
在 Hugo 站点根目录下运行:
1
| git submodule update --remote --merge
|
📢 注意:
如果是从其他地方 copy | clone 过来的站点,有可能遇到(参考👉 从零开始搭建个人博客网站系列 - 知乎
):
1
| WARN found no layout file for "html" for kind "home": You should create a template file which matches Hugo Layouts Lookup Rules for this combination.
|
这个时候需要重新 clone 一下主题(参考👉 Installation · adityatelange/hugo-PaperMod Wiki
):
1
| git submodule update --init --recursive
|
2.4 查看版本#
1
2
| cd themes/PaperMod/
git describe --tags
|
III. 主题配置#
3.1 站点目录#
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
| .(site root)
├── assets
│ └── css
│ └── extended
│ └── blank.css
├── content
│ ├── posts
│ │ ├── _index.md
│ │ ├── emoji-support.md
│ │ ├── markdown-syntax.md
│ │ └── math-typesetting.md
│ ├── archives.md
│ └── search.md
├── i18n
│ └── en.yaml
├── layouts
│ ├── _default
│ │ ├── _markup
│ │ | └── render-link.html
│ │ ├── archives.html
│ │ └── terms.html
│ └── partials
│ └── toc.html
├── static
│ ├── android-chrome-192x192.png
│ ├── android-chrome-512x512.png
│ ├── apple-touch-icon.png
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── favicon.ico
│ └── papermod-cover.png
├── themes
| └── PaperMod
├── hugo.toml.bak
└── config.yml
|
3.2 站点配置#
3.2.1 备份旧配置#
3.2.2 配置新站点#
官方示例
配置 config.yml,内容如下:
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
| baseURL: "https://examplesite.com/"
title: ExampleSite
paginate: 5
theme: PaperMod
enableRobotsTXT: true
buildDrafts: false
buildFuture: false
buildExpired: false
minify:
disableXML: true
minifyOutput: true
params:
env: production # to enable google analytics, opengraph, twitter-cards and schema.
title: ExampleSite
description: "ExampleSite description"
keywords: [Blog, Portfolio, PaperMod]
author: Me
# author: ["Me", "You"] # multiple authors
images: ["<link or path of image for opengraph, twitter-cards>"]
DateFormat: "January 2, 2006"
defaultTheme: auto # dark, light
disableThemeToggle: false
ShowReadingTime: true
ShowShareButtons: true
ShowPostNavLinks: true
ShowBreadCrumbs: true
ShowCodeCopyButtons: false
ShowWordCount: true
ShowRssButtonInSectionTermList: true
UseHugoToc: true
disableSpecial1stPost: false
disableScrollToTop: false
comments: false
hidemeta: false
hideSummary: false
showtoc: false
tocopen: false
assets:
# disableHLJS: true # to disable highlight.js
# disableFingerprinting: true
favicon: "<link / abs url>"
favicon16x16: "<link / abs url>"
favicon32x32: "<link / abs url>"
apple_touch_icon: "<link / abs url>"
safari_pinned_tab: "<link / abs url>"
label:
text: "Home"
icon: /apple-touch-icon.png
iconHeight: 35
# profile-mode
profileMode:
enabled: false # needs to be explicitly set
title: ExampleSite
subtitle: "This is subtitle"
imageUrl: "<img location>"
imageWidth: 120
imageHeight: 120
imageTitle: my image
buttons:
- name: Posts
url: posts
- name: Tags
url: tags
# home-info mode
homeInfoParams:
Title: "Hi there \U0001F44B"
Content: Welcome to my blog
socialIcons:
- name: x
url: "https://x.com/"
- name: stackoverflow
url: "https://stackoverflow.com"
- name: github
url: "https://github.com/"
analytics:
google:
SiteVerificationTag: "XYZabc"
bing:
SiteVerificationTag: "XYZabc"
yandex:
SiteVerificationTag: "XYZabc"
cover:
hidden: true # hide everywhere but not in structured data
hiddenInList: true # hide on list pages and home
hiddenInSingle: true # hide on single page
editPost:
URL: "https://github.com/<path_to_repo>/content"
Text: "Suggest Changes" # edit text
appendFilePath: true # to append file path to Edit link
# for search
# https://fusejs.io/api/options.html
fuseOpts:
isCaseSensitive: false
shouldSort: true
location: 0
distance: 1000
threshold: 0.4
minMatchCharLength: 0
limit: 10 # refer: https://www.fusejs.io/api/methods.html#search
keys: ["title", "permalink", "summary", "content"]
menu:
main:
- identifier: categories
name: categories
url: /categories/
weight: 10
- identifier: tags
name: tags
url: /tags/
weight: 20
- identifier: example
name: example.org
url: https://example.org
weight: 30
# Read: https://github.com/adityatelange/hugo-PaperMod/wiki/FAQs#using-hugos-syntax-highlighter-chroma
pygmentsUseClasses: true
markup:
highlight:
noClasses: false
# anchorLineNos: true
# codeFences: true
# guessSyntax: true
# lineNos: true
# style: monokai
|
3.3 模式选择#
参考👉 Features · hugo-PaperMod Wiki
Hugo PaperMod 有:常规模式(Regular Mode)、首页信息模式(Home-Info Mode)、个人资料模式(Profile Mode) 三种模式可供选择。
这里选择“个人资料模式(Profile Mode)”模式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| params:
profileMode:
enabled: true
title: "<Title>" # optional default will be site title
subtitle: "This is subtitle"
imageUrl: "<image link>" # optional
imageTitle: "<title of image as alt>" # optional
imageWidth: 120 # custom size
imageHeight: 120 # custom size
buttons:
- name: Archive
url: "/archive"
- name: Github
url: "https://github.com/"
socialIcons: # optional
- name: "<platform>"
url: "<link>"
- name: "<platform 2>"
url: "<link2>"
|
💡 Tips:
请为 imageUrl 设定一个值,如:imageUrl: "apple-touch-icon.png" 这里 apple-touch-icon.png 须为实际存在的图像文件,文件默认存放在站点 ../static 目录下。
3.4 自定义导航#
3.4.1 增加导航菜单#
这里修改站点配置文件,增加 archives,search 导航菜单,并调整显示顺序。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| menu:
main:
- identifier: archives
name: archives
url: archives/
weight: 10
- identifier: categories
name: categories
url: categories/
weight: 20
- identifier: tags
name: tags
url: tags/
weight: 30
- identifier: search
name: search
url: search/
weight: 40
|
3.4.2 创建菜单页面#
为新增加的导航菜单创建页面。在站点 ../content/ 目录增加两个文件。
1
2
3
4
| .(site root)
└── content
├── archives.md
└── search.md
|
archives.md 文件内容如下:
1
2
3
4
5
6
| ---
title: "存档"
layout: "archives"
url: "/archives"
summary: "archives"
---
|
search.md 文件内容如下:
1
2
3
4
5
| ---
title: "搜索"
layout: "search"
placeholder: 输入关键字,然后回车 ...
---
|
3.5 中文翻译#
💡 Tips:
为了展示中文效果,建议在站点 ../content/posts/ 目录下至少发布三篇文章,每篇文章元数据包括文章的“分类”及“标签”,如下:
1
2
3
4
5
6
| .(site root)
└── content
└── posts
├── emoji-support.md
├── markdown-syntax.md
└── math-typesetting.md
|
3.5.1 导航菜单#
直接在站点配置文件 config.yml 中修改,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| menu:
main:
- identifier: archives
name: 归档
url: archives/
weight: 10
- identifier: categories
name: 分类
url: categories/
weight: 20
- identifier: tags
name: 标签
url: tags/
weight: 30
- identifier: search
name: 搜索
url: search/
weight: 40
|
3.5.2 页面元素#
参考👉 PaperMod 主题配置 - 修改 html 模板
1.首先汉化的是分类、标签页中文显示。创建 ../layouts/_default/terms.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
| {{- define "main" }}
{{- if .Title }}
<header class="page-header">
{{- if eq .Title "Categories" }}
<h1>{{ "分类" }}</h1>
{{- end }}
{{- if eq .Title "Tags" }}
<h1>{{ "标签" }}</h1>
<!-- <h1>🔖{{ .Title }}</h1> -->
{{- end }}
<!-- <h1>{{ .Title }}</h1> -->
{{- if .Description }}
<div class="post-description">
{{ .Description }}
</div>
{{- end }}
</header>
{{- end }}
<!-- 原始 -->
<ul class="terms-tags">
{{- $type := .Type }}
{{- range $key, $value := .Data.Terms.Alphabetical }}
{{- $name := .Name }}
{{- $count := .Count }}
{{- with $.Site.GetPage (printf "/%s/%s" $type $name) }}
<li>
<a href="{{ .Permalink }}">{{ .Name }} <sup><strong><sup>{{ $count }}</sup></strong></sup> </a>
</li>
{{- end }}
{{- end }}
</ul>
{{- end }}{{/* end main */ -}}
|
2.汉化文章页 <<PREV, NEXT>> 等页面元素,修改站点配置文件 config.yml,内容如下:
1
2
3
4
5
6
7
8
9
10
| # 语言设置
defaultContentLanguage: "zh"
# 单语言,必须在此处,以下设置之前
languages:
zh:
# RFC 5646 语言标记, Hugo 使用此值填充内置 RSS 模板中的语言元素和内置别名模板中 html 元素的 lang 属性
languageCode: "zh-CN"
# 语言名称,通常在渲染语言切换器时使用
languageName: "中文"
|
3.5.3 Posts 页#
汉化 exampleSite/posts/ 页面 Posts 显示为“文章”。在站点 ../content/posts/ 下创建 _index.md 文件,内容如下:
1
2
3
| ---
title: "文章"
---
|
3.6 文章目录(大纲)#
参考👉 Hugo侧边目录 | 3rd’s Blog
1.在站点配置文件 config.yml 中启用目录(大纲),如下:
1
2
3
4
| params:
# ...
showtoc: true # 显示目录
tocopen: true # 自动展开目录
|
2.PaperMod 主题默认将文章目录放在顶部,不方便阅读时跳转,修改为侧边目录。创建 toc.html ,toc.css 两个文件,目录结构如下:
1
2
3
4
5
6
7
8
| .(site root)
├── assets
│ └── css
│ └── extended
│ └── toc.css
└── layouts
└── partials
└── toc.html
|
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 }}
|
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);
}
|
3.7 新标签打开链接#
参考👉 设置以新标签打开链接 - Dvel’s Blog
创建 ../layouts/_default/_markup/render-link.html 文件,内容如下:
1
| <a href="{{ .Destination | safeURL }}"{{ with .Title}} title="{{ . }}"{{ end }}{{ if strings.HasPrefix .Destination "http" }} target="_blank" rel="noopener"{{ end }}>{{ .Text | safeHTML }}</a>
|
该方法自动为所有文章内的链接加上了 target="_blank" rel="noopener"。
但是站内链接也都加上了 target="_blank" rel="noopener"。
站内链接建议写 path:
1
2
3
| [title](/foo/bar/)
↓ 会被简单解析为: ↓
<a href="/foo/bar/">title</a>
|
3.8 Waline 评论系统#
参考👉 在Hugo-PaperMod中加入Waline评论区 | Drifting Boats
3.8.1 引入 Waline 客户端#
在站点目录创建评论页面 ../layouts/partials/comments.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
| {{- /* Comments area start */ -}}
{{- /* to add comments read => https://gohugo.io/content-management/comments/ */ -}}
<!-- layouts/partials/comments.html -->
{{- if .Site.Params.comments }}
<!-- 评论容器 -->
<div class="waline-container" data-path="{{ .Permalink | relURL }}"></div>
<link href="https://unpkg.com/@waline/client@v3/dist/waline.css" rel="stylesheet" />
<!-- 调整评论框宽度与文章宽度一致 -->
<style>
.waline-container {
max-width: 100% !important; /* 强制占满父容器宽度 */
margin-top: 40px; /* 保持与上方内容的间距 */
padding: 0 !important; /* 移除多余内边距 */
}
/* 针对 Waline 内部可能存在的最大宽度限制进行覆盖 */
.wl-panel {
margin: 0 !important;
width: 100% !important;
}
</style>
<!-- 初始化 Waline 的脚本 -->
<script>
document.addEventListener("DOMContentLoaded", () => {
// 初始化 Waline
const walineInit = () => {
import('https://unpkg.com/@waline/client@v3/dist/waline.js').then(({ init }) => {
const walineContainers = document.querySelectorAll('.waline-container[data-path]');
walineContainers.forEach(container => {
if (!container.__waline__) {
const path = container.getAttribute('data-path');
container.__waline__ = init({
el: container,
serverURL: '{{ .Site.Params.waline.serverURL }}',
lang: '{{ .Site.Params.waline.lang }}',
meta: [], // 隐藏昵称、邮箱、网址输入框
login: 'force', // 强制登录模式:未登录时只显示登录入口,登录后才允许提交
visitor: '{{ .Site.Params.waline.visitor | default "匿名者" }}',
emoji: [
{{- range .Site.Params.waline.emoji }}
'{{ . }}',
{{- end }}
],
requiredMeta: [
{{- range .Site.Params.waline.requiredMeta }}
'{{ . }}',
{{- end }}
],
locale: {
admin: '{{ .Site.Params.waline.locale.admin }}',
placeholder: '{{ .Site.Params.waline.locale.placeholder }}',
},
path: path,
dark: '{{ .Site.Params.waline.dark | default "body.dark" }}',
// 个性化显示
commentSorting: 'hottest', // 评论列表排序方式
wordLimit: 500, // 评论字数限制
search: false, // 自定义搜索功能
reaction: false, // 文章表情互动功能
imageUploader: false, // 自定义图片上传
});
}
});
}).catch(error => {
console.error("Waline 初始化失败:", error);
});
};
walineInit();
});
</script>
{{- end }}
{{- /* Comments area end */ -}}
|
3.8.2 修改站点配置#
修改 hugo.yaml 站点配置文件:
- comments 值改为 true
- 增加 Waline 配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| params:
# ......
comments: true # 启用评论功能
# ......
waline:
serverURL: "https://comments.example.com" # 绑定为你自己的 Waline 服务端地址
lang: "zh-CN"
# visitor: "访客"
# 适配 PaperMod 的深色模式
dark: "html[data-theme='dark']"
emoji:
- "https://unpkg.com/@waline/emojis@1.4.0/weibo"
# - "https://unpkg.com/@waline/emojis@1.4.0/qq"
requiredMeta: [] # 强制登录模式
# 选择必填项,包括 nick|mail|link
# - "nick"
locale:
# 作者标签文案
admin: "博主"
# 评论输入框占位文案内容
placeholder: "请留言(填写邮箱可在被回复时收到邮件提醒)"
|
3.9 版权声明#
参考👉 PaperMod 添加文章版权声明 | Tofuwine’s Blog
3.9.1 版权页面#
站点目录下创建 ../layouts/partials/copyright.html 文件,内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| <div class="pe-copyright">
<img src="/imgs/cc/cc.svg" width="75" height="75" align="right" />
<!-- <hr> -->
<blockquote>
{{ if .Param "reposted" }}
<p><strong>本文为转载内容,原文信息如下:</strong></p>
<p><strong>原文标题:</strong>{{- .Param "repostedTitle" -}}</p>
<p><strong>原文作者:</strong>{{- .Param "repostedAuthor" -}}</p>
<p><strong>原文链接:</strong><a href="{{- .Param "repostedLink" -}}" target="_blank">{{- .Param "repostedLink" -}}</a></p>
<p><strong>版权声明:</strong>如有侵权,请<a href="mailto://{{ .Param "contactEmail" }}">联系本站</a>删除。</p>
{{ else }}
<p><strong>文章标题:</strong>{{ .Title }}</p>
<p><strong>本文作者:</strong>{{ .Param "author" }}</p>
<p><strong>本文链接:</strong><a href="{{ .Permalink }}" target="_blank">{{ .Permalink }}</a></p>
<p><strong>版权声明:</strong>本网站所有文章除特别声明外,均采用 <a href="{{- .Param "licenseLink" -}}" target="_blank">{{- .Param "licenseName" -}}</a> 许可协议。转载请注明出处!</p>
{{ end }}
</blockquote>
</div>
|
📌 说明:
本示例版权水印文件存放路径 ../static/imgs/cc/cc.svg
3.9.2 添加样式#
在站点目录下创建 ../assets/css/extended/copyright.css 文件,内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| .pe-copyright {
margin-top: 50px; /* 上边距 */
font-size: 14px;
border: 3px solid #4A4A4A;
}
.pe-copyright hr {
border-style: dashed;
color: #e26c56;
}
.pe-copyright blockquote {
margin: 10px 0; /* 外边距:上下边距为 10px,左右为 0 */
padding: 10px 10px; /* 内边距:上下边距为 20px,左右为 10px */
}
.pe-copyright a {
box-shadow: 0 1px;
box-decoration-break: clone;
-webkit-box-decoration-break: clone;
}
|
3.9.3 加入文章页#
将 PaperMod 主题目录下 ../themes/PaperMod/layouts/_default/single.html 文件复制到站点目录 ../layouts/_default/single.html ,编辑 single.html 文件,在 footer 节点上添加如下内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| <article class="post-single">
{{- if .Content }}
<div class="post-content">
...
</div>
{{- end }}
<!-- 加入版权声明 -->
{{ if .Param "enableCopyright" }}
{{ partial "copyright.html" . }}
{{ end }}
<footer class="post-footer">
...
</footer>
</article>
|
3.9.4 启用版权声明#
在站点配置文件 config.yml 中加入以下配置:
1
2
3
| params:
# ...
enableCopyright: true # 启用版权声明
|
3.9.4.1 原创文章#
原创文章需在文章 frontmatter 中添加以下参数:(以下仅为示例,请根据实际自行修改)
1
2
3
4
5
| ---
author: <Author>
licenseLink: "https://creativecommons.org/licenses/by-nc/4.0/"
licenseName: "CC BY-NC 4.0"
---
|
也可直接在站点配置文件 config.yml 中加入以下配置:
1
2
3
4
5
6
| params:
# ...
author: <Author>
licenseLink: "https://creativecommons.org/licenses/by-nc/4.0/deed.zh-hans"
licenseName: "CC BY-NC-SA"
contactEmail: <userName@example.com>
|
其中 author 为 Hugo-PaperMod 已有参数。
3.9.4.2 转载文章#
转载文章需在文章 frontmatter 中添加以下参数:
1
2
3
4
5
6
7
| ---
reposted: true
repostedTitle: "修改为原文章标题"
repostedAuthor: "修改为原文章作者名"
repostedLink: "修改为原文章链接"
contactEmail: your email
---
|
其中 contactEmail 参数可在 hugo 配置中指定全局默认值:
1
2
3
| params:
# ...
contactEmail: <Your Email>
|
3.10 代码块优化#
3.10.1 代码块高亮#
修改站点配置文件 config.yml ,如下:
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
| params:
# ...
assets:
disableHLJS: true # to disable highlight.js
# Read: https://github.com/adityatelange/hugo-PaperMod/wiki/FAQs#using-hugos-syntax-highlighter-chroma
pygmentsUseClasses: false
markup:
goldmark:
renderer:
unsafe: true
highlight:
noClasses: false # 启用主题 CSS 样式
anchorLineNos: false
codeFences: true
guessSyntax: true
lineNos: true
lineNumbersInTable: true # 表格模式,物理分离行号和代码
# 参数说明:https://gohugo.io/functions/transform/highlight/
# lineNos:是否在每行开头显示数字。 默认为 false
# hl_Lines:以空格分隔的列表,用于强调高亮代码中的行。 要强调第 2、3、4 和 7 行,请将该值设为 2-4 7。 该选项独立于行无起始选项。
# lineNoStart=1:第一行开头要显示的数字。 如果 lineNos 为 false 则与此无关。 默认值为 1
# anchorLineNos:是否将每个行号渲染为 HTML 锚点元素,并将周围 span 元素的 id 属性设置为行号。 如果 lineNos 为 false,则与此无关。 默认为 false
# lineAnchors:在将行号作为 HTML 锚点元素呈现时,将此值预置到周围 span 元素的 id 属性中。 当页面包含两个或多个代码块时,这将提供唯一的 id 属性。 如果 lineNos 或 anchorLineNos 为 false,则与此无关。
# hl_inline:是否在没有包装容器的情况下渲染突出显示的代码。默认为 false
# codeFences:是否高亮显示有栅栏的代码块。 默认为 true
# guessSyntax:如果 LANG 参数为空或设置为没有相应词法的语言,是否自动检测语言。 如果无法自动检测语言,则退回到纯文本词法。 默认为 false
# lineNumbersInTable:是否在 HTML 表格的两个单元格中显示高亮显示的代码。 左侧表格单元格包含行号,右侧表格单元格包含代码。 如果 lineNos 为 false 则无关。 默认为 true
# noClasses:是否使用内联 CSS 样式而不是外部 CSS 文件。 要使用外部 CSS 文件,请将此值设为 false,并使用 hugo gen chromastyles 命令生成 CSS 文件。 默认值为 true
|
3.10.2 代码块展开/折叠#
1.修改站点配置文件 config.yml ,如下:
1
2
3
| params:
# ...
ShowCodeCopyButtons: true
|
2.复制主题目录 ../themes/PaperMod/layouts/partials/footer.html 文件,粘贴到站点根目录 ../layouts/partials/ 下。 编辑 footer.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
193
194
195
196
197
198
199
200
201
202
203
204
| {{- if not (.Param "hideFooter") }}
<footer class="footer">
{{- if not site.Params.footer.hideCopyright }}
{{- if site.Copyright }}
<span>{{ site.Copyright | markdownify }}</span>
{{- else }}
<span>© {{ now.Year }} <a href="{{ "" | absLangURL }}">{{ site.Title }}</a></span>
{{- end }}
{{- print " · "}}
{{- end }}
{{- with site.Params.footer.text }}
{{ . | markdownify }}
{{- print " · "}}
{{- end }}
<span>
Powered by
<a href="https://gohugo.io/" rel="noopener noreferrer" target="_blank">Hugo</a> &
<a href="https://github.com/adityatelange/hugo-PaperMod/" rel="noopener" target="_blank">PaperMod</a>
</span>
</footer>
{{- end }}
{{- if (not site.Params.disableScrollToTop) }}
<a href="#top" aria-label="go to top" title="Go to Top (Alt + G)" class="top-link" id="top-link" accesskey="g">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 6" fill="currentColor">
<path d="M12 6H0l6-6z" />
</svg>
</a>
{{- end }}
{{- partial "extend_footer.html" . }}
<script>
let menu = document.getElementById('menu');
if (menu) {
const scrollPosition = localStorage.getItem("menu-scroll-position");
if (scrollPosition) {
menu.scrollLeft = parseInt(scrollPosition, 10);
}
menu.onscroll = function () {
localStorage.setItem("menu-scroll-position", menu.scrollLeft);
}
}
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener("click", function (e) {
e.preventDefault();
var id = this.getAttribute("href").substr(1);
if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
document.querySelector(`[id='${decodeURIComponent(id)}']`).scrollIntoView({
behavior: "smooth"
});
} else {
document.querySelector(`[id='${decodeURIComponent(id)}']`).scrollIntoView();
}
if (id === "top") {
history.replaceState(null, null, " ");
} else {
history.pushState(null, null, `#${id}`);
}
});
});
</script>
{{- if (not site.Params.disableScrollToTop) }}
<script>
var mybutton = document.getElementById("top-link");
window.onscroll = function () {
if (document.body.scrollTop > 800 || document.documentElement.scrollTop > 800) {
mybutton.style.visibility = "visible";
mybutton.style.opacity = "1";
} else {
mybutton.style.visibility = "hidden";
mybutton.style.opacity = "0";
}
};
</script>
{{- end }}
{{- if (not site.Params.disableThemeToggle) }}
<script>
document.getElementById("theme-toggle").addEventListener("click", () => {
const html = document.querySelector("html");
if (html.dataset.theme === "dark") {
html.dataset.theme = 'light';
localStorage.setItem("pref-theme", 'light');
} else {
html.dataset.theme = 'dark';
localStorage.setItem("pref-theme", 'dark');
}
})
</script>
{{- end }}
{{- /* --- 核心优化区域:代码块增强功能 --- */ -}}
{{- if (and (eq .Kind "page") (ne .Layout "archives") (ne .Layout "search") (.Param "ShowCodeCopyButtons")) }}
<script>
document.querySelectorAll('pre > code').forEach((codeblock) => {
const container = codeblock.parentNode.parentNode;
// --- 1. 复制按钮逻辑 ---
const copybutton = document.createElement('button');
copybutton.classList.add('copy-code');
copybutton.innerHTML = '{{- i18n "code_copy" | default "copy" }}';
function copyingDone() {
copybutton.innerHTML = '{{- i18n "code_copied" | default "copied!" }}';
setTimeout(() => {
copybutton.innerHTML = '{{- i18n "code_copy" | default "copy" }}';
}, 2000);
}
copybutton.addEventListener('click', (cb) => {
if ('clipboard' in navigator) {
// 获取内容:依赖 lineNumbersInTable: true,此时 textContent 不含行号
let text = codeblock.textContent;
// 核心修复:移除末尾换行符
navigator.clipboard.writeText(text.replace(/\n$/, ""));
copyingDone();
return;
}
const range = document.createRange();
range.selectNodeContents(codeblock);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
try {
document.execCommand('copy');
copyingDone();
} catch (e) { };
selection.removeRange(range);
});
// 智能插入复制按钮
// 判断是否是行号区域 (在表格模式下,行号通常是 tr 的第一个子元素)
if (container.parentNode.firstChild == container) {
// 是行号,跳过,不加复制按钮
} else {
// 尝试找到外层的 .highlight 容器,以便将按钮绝对定位在右上角
const highlightDiv = codeblock.closest('.highlight');
if (highlightDiv) {
// 防止重复添加
if (!highlightDiv.querySelector('.copy-code')) {
highlightDiv.appendChild(copybutton);
}
} else {
// 回退:如果没找到 highlight 容器,加在当前代码块父级
codeblock.parentNode.appendChild(copybutton);
}
}
// --- 2. 折叠按钮逻辑 ---
// 检查代码高度是否超过 200px (需与 CSS 中的 max-height 一致)
if (codeblock.scrollHeight > 200) {
const buttonContainer = codeblock.closest('.highlight');
// 确保找到了容器且没有添加过折叠按钮
if (buttonContainer && !buttonContainer.querySelector('.unfoldbtn')) {
const unfoldbtn = document.createElement('button');
unfoldbtn.classList.add('unfoldbtn');
unfoldbtn.innerHTML = '展开';
// 【重要】添加此 class,CSS 会识别并自动将复制按钮左移,避免重叠
buttonContainer.classList.add('has-unfold');
unfoldbtn.addEventListener('click', () => {
// 表格模式下,需同时操作行号区和代码区的 code 元素
const codes = buttonContainer.querySelectorAll('code');
let isUnfolded = false;
codes.forEach(c => {
// 切换 CSS 类名实现展开/折叠动画
if (c.classList.contains('unfold')) {
c.classList.remove('unfold');
isUnfolded = false;
} else {
c.classList.add('unfold');
isUnfolded = true;
}
});
unfoldbtn.innerHTML = isUnfolded ? '折叠' : '展开';
});
buttonContainer.appendChild(unfoldbtn);
}
}
});
</script>
{{- end }}
{{if .Page.Site.Params.fancybox }}
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.css" />
<script src="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.js"></script>
{{ end }}
|
3.复制主题目录 ../themes/PaperMod/assets/css/extended/blank.css 文件,粘贴到站点根目录 ../assets/css/extended/ 下。 编辑 blank.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
87
88
| /*
This is just a placeholder blank stylesheet so as to support adding custom styles budled with theme's default styles
Read https://github.com/adityatelange/hugo-PaperMod/wiki/FAQs#bundling-custom-css-with-themes-assets for more info
*/
/* 1. 容器相对定位,为绝对定位的按钮提供锚点 */
.highlight {
position: relative;
}
/* 2. 按钮通用样式:右上角悬浮、默认透明 */
.copy-code, .unfoldbtn {
position: absolute;
top: 5px; /* 距离顶部 5px */
z-index: 20; /* 保证在最上层 */
/* 样式:使用主题变量,半透明背景防止文字看不清 */
background: var(--entry);
color: var(--primary);
border: 1px solid var(--border);
border-radius: 4px;
padding: 2px 8px;
font-size: 12px;
cursor: pointer;
user-select: none;
/* 核心交互:默认隐藏,平滑渐变 */
opacity: 0;
pointer-events: none; /* 隐藏时鼠标可穿透(方便选中文本) */
transition: opacity 0.3s ease, right 0.3s ease; /* 同时处理透明度和位置动画 */
}
/* 3. 交互逻辑:鼠标悬停在代码块时显示按钮 */
.highlight:hover .copy-code,
.highlight:hover .unfoldbtn {
opacity: 1;
pointer-events: auto; /* 显示时恢复点击 */
}
/* (可选) 移动端优化:触摸屏设备始终显示,或点击后显示 */
@media (hover: none) {
.copy-code, .unfoldbtn {
opacity: 1;
pointer-events: auto;
}
}
/* 4. 按钮位置控制 */
/* 展开按钮:始终固定在最右侧 */
.unfoldbtn {
right: 5px;
}
/* 复制按钮:默认在最右侧 (当没有展开按钮时) */
.copy-code {
right: 5px;
}
/* 智能避让:当存在展开按钮时(JS添加了 .has-unfold 类),复制按钮向左移动 */
/* 计算:5px(右边距) + 45px(按钮宽度) + 5px(间隙) ≈ 55px */
.has-unfold .copy-code {
right: 55px;
}
/* 5. 按钮悬停反馈:鼠标放在按钮上时加深背景 */
.copy-code:hover, .unfoldbtn:hover {
background: var(--tertiary);
}
/* 6. 折叠/展开功能区 (严格限定在代码块内,不影响行内代码) */
.highlight pre > code {
display: block;
overflow-y: hidden;
width: 100%;
/* 高度切换动画 */
transition: max-height 0.3s ease-in-out;
}
/* 默认折叠状态高度 */
.highlight pre > code {
max-height: 200px;
}
/* 展开状态 (由 JS 切换类名控制) */
.unfold {
max-height: 5000px !important;
}
|
3.11 查看原图#
参考👉 Hugo 主题配置 | 夜云泊
1.修改站点配置文件 config.yml ,如下:
1
2
3
4
| params:
# ...
# User-defined parameters
fancybox: true # 启用图片放大功能
|
2.创建 ../layouts/_default/_markup/render-image.html 文件,内容如下:
1
2
3
4
5
6
7
| {{if .Page.Site.Params.fancybox }}
<div class="post-img-view">
<a data-fancybox="gallery" href="{{ .Destination | safeURL }}">
<img src="{{ .Destination | safeURL }}" alt="{{ .Text }}" {{ with .Title}} title="{{ . }}"{{ end }} />
</a>
</div>
{{ end }}
|
3.编辑 ../layouts/partials/footer.html 文件,加入以下内容:
1
2
3
4
5
| {{if .Page.Site.Params.fancybox }}
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.css" />
<script src="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.js"></script>
{{ end }}
|
3.12 数学公式#
参考👉 在Hugo PaperMod主题中加入数学支持的最简方式 - 微控圈(MCU Loop)
1.在站点目录下创建 ../layouts/partials/math.html 文件,内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.2/dist/katex.min.css" integrity="sha384-bYdxxUwYipFNohQlHt0bjN/LCpueqWz13HufFEV1SUatKs1cm4L6fFgCi1jT643X" crossorigin="anonymous">
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.2/dist/katex.min.js" integrity="sha384-Qsn9KnoKISj6dI8g7p1HBlNpVx0I8p1SvlwOldgi3IorMle61nQy4zEahWYtljaz" crossorigin="anonymous"></script>
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.2/dist/contrib/auto-render.min.js" integrity="sha384-+VBxd3r6XgURycqtZ117nYw44OOcIax56Z4dCRWbxyPt0Koah1uHoK0o4+/RRE05" crossorigin="anonymous"></script>
<script>
document.addEventListener("DOMContentLoaded", function() {
renderMathInElement(document.body, {
// customised options
// • auto-render specific keys, e.g.:
delimiters: [
{left: '$$', right: '$$', display: true},
{left: '$', right: '$', display: false}
],
// • rendering keys, e.g.:
throwOnError : false
});
});
</script>
|
2.复制主题目录 ../themes/PaperMod/layouts/partials/extend_head.html 文件,粘贴到站点根目录 ../layouts/partials/ 下。 编辑 extend_head.html,内容如下:
1
2
3
4
5
6
7
| {{- /* Head custom content area start */ -}}
{{- /* Insert any custom code (web-analytics, resources, etc.) - it will appear in the <head></head> section of every page. */ -}}
{{- /* Can be overwritten by partial with the same name in the global layouts. */ -}}
{{ if or .Params.math .Site.Params.math }}
{{ partial "math.html" . }}
{{ end }}
{{- /* Head custom content area end */ -}}
|
3.通过在文章 front matter 中设置 math 属性 true/false 来按需加载数学公式资源。
1
2
3
4
5
6
| ---
title: 文章标题
date:
tags:
math: true
---
|
3.13 其它配置#
3.13.1 不显示面包屑#
在站点配置文件 config.yml 中关闭,如下:
1
2
3
| params:
# ...
ShowBreadCrumbs: false
|
3.13.2 日期格式化#
修改站点配置文件 config.yml ,如下:
1
2
3
4
| params:
# ...
DateFormat: "2006-01-02" # 日期格式化
ShowFullTextinRSS: true # RSS 输出全文
|
3.13.3 修改文章作者#
修改站点配置文件 config.yml ,如下:
1
2
3
| params:
# ...
author: <YourName>
|
3.13.4 表格优化#
参考👉 折腾 Hugo & PaperMod 主题 - Dvel’s Blog
编辑 ../assets/css/extended/blank.css 文件,添加以下内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| /* GitHub 样式的表格 */
.post-content table tr {
border: 1px solid #979da3 !important;
}
.post-content table tr:nth-child(2n),
.post-content thead {
background-color: var(--code-bg);
}
.post-content table th {
border: 1px solid #979da3 !important;
}
.post-content table td {
border: 1px solid #979da3 !important;
}
|
3.14 搜索+分类+标签集成(可选)#
参考 👉 PaperMod 搜索页展示标签列表 | loyayz
将 PaperMod 主题目录下 ../themes/PaperMod/layouts/_default/search.html 文件复制到站点目录 ../layouts/_default/search.html ,编辑 search.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
| {{- define "main" }}
<header class="page-header">
<h1>{{- (printf "%s " .Title ) | htmlUnescape -}}
<!-- 取消放大镜图标 -->
<!-- <svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg> -->
</h1>
{{- if .Description }}
<div class="post-description">
{{ .Description }}
</div>
{{- end }}
{{- if not (.Param "hideMeta") }}
<div class="post-meta">
{{- partial "translation_list.html" . -}}
</div>
{{- end }}
</header>
<div id="searchbox">
<input id="searchInput" autofocus placeholder="{{ .Params.placeholder | default (printf "%s ↵" .Title) }}"
aria-label="search" type="search" autocomplete="off" maxlength="64">
<ul id="searchResults" aria-label="search results"></ul>
</div>
<!-- 显示分类: -->
{{- if not (.Param "hideCategories")}}
{{- $taxonomies := .Site.Taxonomies.categories }}
{{- if gt (len $taxonomies) 0 }}
<h2 style="margin-top: 32px">{{- (.Param "categoriesTitle") | default "🧩分类" }}</h2>
<ul class="terms-tags">
{{- range $name, $value := $taxonomies }}
{{- $count := .Count }}
{{- with site.GetPage (printf "/categories/%s" $name) }}
<li>
<a href="{{ .Permalink }}">{{ .Name }} <sup><strong><sup>{{ $count }}</sup></strong></sup> </a>
</li>
{{- end }}
{{- end }}
</ul>
{{- end }}
{{- end }}
<!-- 显示标签: -->
{{- if not (.Param "hideTags") }}
{{- $taxonomies := .Site.Taxonomies.tags }}
{{- if gt (len $taxonomies) 0 }}
<h2 style="margin-top: 32px">{{- (.Param "tagsTitle") | default "🏷️标签" }}</h2>
<ul class="terms-tags">
{{- range $name, $value := $taxonomies }}
{{- $count := .Count }}
{{- with site.GetPage (printf "/tags/%s" $name) }}
<li>
<a href="{{ .Permalink }}">{{ .Name }} <sup><strong><sup>{{ $count }}</sup></strong></sup> </a>
</li>
{{- end }}
{{- end }}
</ul>
{{- end }}
{{- end }}
{{- end }}{{/* end main */}}
|
💡 Tips :
将”分类“和”标签“移至搜索页后,就可以取消独立展示入口,以保持菜单整洁。
3.15 增加“说说”页面(可选)#
参考1👉 hugo-Papermod添加瞬间Moments页面 | Drifting Boats
参考2👉 Hugo 添加瞬间页 | Tofuwine’s Blog
3.15.1 创建页面模板#
在站点目录创建页面模板 ../layouts/moments/list.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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
| {{ define "main" }}
<!-- 如果需要引入 moments.css,请保持路径一致或根据自己项目结构调整 -->
{{ $css := resources.Get "css/extended/moments.css" | minify | fingerprint }}
<link
crossorigin="anonymous"
href="{{ $css.RelPermalink }}"
integrity="{{ $css.Data.Integrity }}"
rel="stylesheet"
/>
<!-- 引入 Waline 的 CSS -->
<link
rel="stylesheet"
href="https://unpkg.com/@waline/client@v3/dist/waline.css"
/>
{{ $dateformat := .Params.DateFormat }}
<article class="post-single">
<header class="page-header">
<!-- 可根据需求添加页面标题、描述等 -->
<!-- <h1>{{ .Title }}</h1> -->
</header>
<div class="tags-filter">
<ul>
<li><a href="#" class="tag-filter all-tags">全部</a></li> <!-- "全部"选项 -->
{{ $tags := slice }} <!-- 用于存储所有标签 -->
{{ range .Pages }}
{{ range .Params.tags }}
{{ if not (in $tags .) }}
{{ $tags = $tags | append . }}
{{ end }}
{{ end }}
{{ end }}
<!-- 按字母顺序排序标签 -->
{{ $tags = $tags | sort }}
{{ range $tags }}
<li><a href="#" class="tag-filter">{{ . }}</a></li> <!-- 标签项 -->
{{ end }}
</ul>
</div>
<div class="post-content">
<div class="moments-list">
{{ range .Pages }}
{{ if .Content }}
<!-- 卡片容器 -->
<div class="moment-card">
<!-- 头部:头像 + 作者名 -->
<div class="moment-header">
<div class="left-content">
<img
src="{{ site.Params.label.avatar }}"
alt="{{ site.Params.author }}"
class="moment-avatar"
>
<span class="moment-author">
{{ site.Params.author }}
</span>
</div>
</div>
<!-- 动态主体内容(Hugo 渲染后的 .Content) -->
<div class="moment-body">
<div class="moment-content-wrapper">
{{ .Content | safeHTML }}
</div>
<div class="moment-loading">
<div class="loading-spinner"></div>
<div class="skeleton-content">
<div class="skeleton-line" style="width: 90%"></div>
<div class="skeleton-line" style="width: 75%"></div>
<div class="skeleton-line" style="width: 60%"></div>
</div>
</div>
<div class="moment-error" style="display: none;">
<span>图片加载失败</span>
<button onclick="retryLoad(this)">重试</button>
</div>
</div>
<!-- 标签(如果有) -->
{{ if .Params.tags }}
<div class="moment-tags">
{{ range $tag := .Params.tags }}
<span class="moment-tag">{{ $tag }}</span>
{{ end }}
</div>
{{ end }}
<!-- 底部:时间 + 评论按钮 -->
<div class="moment-bottom">
<div class="moment-time">
<span>
{{ .Param "date" | time.Format (default site.Params.DateFormat $dateformat) }}
</span>
</div>
<!-- 如果没有 hideComment 参数,则显示评论按钮 -->
{{ if not (.Param "hideComment") }}
<button
class="moment-comment-btn"
onclick="showComment(this)"
data-slug="{{ .Param "slug" }}"
data-path="{{ .Param "slug" }}"
>
<!-- 评论图标 SVG -->
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M281.535354 387.361616c-31.806061 0-57.664646 26.763636-57.664647 59.733333 0 32.969697 25.858586 59.733333 57.664647 59.733334s57.664646-26.763636 57.664646-59.733334c0-33.09899-25.858586-59.733333-57.664646-59.733333z m230.529292 0c-31.806061 0-57.664646 26.763636-57.664646 59.733333 0 32.969697 25.729293 59.733333 57.664646 59.733334 31.806061 0 57.535354-26.763636 57.535354-59.733334 0-33.09899-25.858586-59.733333-57.535354-59.733333z m230.4 0c-31.806061 0-57.664646 26.763636-57.664646 59.733333 0 32.969697 25.858586 59.733333 57.664646 59.733334s57.664646-26.763636 57.664647-59.733334c-0.129293-33.09899-25.858586-59.733333-57.664647-59.733333z m115.2-270.222222H166.335354c-63.612121 0-115.2 53.527273-115.2 119.59596v390.981818c0 65.939394 52.751515 126.836364 117.785858 126.836363h175.579798c30.513131 32.581818 157.220202 149.979798 157.220202 149.979798 5.559596 5.818182 14.739394 5.818182 20.29899 0 0 0 92.832323-91.410101 153.212121-149.979798h179.717172c65.034343 0 117.785859-60.89697 117.785859-126.836363V236.606061c0.129293-65.939394-51.458586-119.466667-115.070708-119.466667z m57.535354 510.577778c0 32.969697-27.668687 67.620202-60.250505 67.620202H678.335354c-21.462626 0-40.727273 21.979798-40.727273 21.979798l-124.121212 114.941414-124.121212-114.941414s-23.660606-21.979798-43.830303-21.979798H168.921212c-32.581818 0-60.250505-34.650505-60.250505-67.620202V236.606061c0-32.969697 25.729293-59.733333 57.664647-59.733334h691.329292c31.806061 0 57.535354 26.763636 57.535354 59.733334v391.111111z m0 0"></path></svg>
<!-- 评论按钮文字 -->
<span class="comment-text">评论 ({{ .Params.comments_count | default "0" }})</span>
</button>
{{ end }}
</div>
<!-- 评论容器:点击按钮后会在这里渲染 Waline 评论 -->
<div
class="waline-container"
id="waline-{{ .Param "slug" }}"
data-path="{{ .Param "slug" }}"
></div>
</div>
{{ end }}
{{ end }}
</div>
</div>
</article>
<!-- JavaScript 代码 -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// 图片懒加载和预加载处理
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
const wrapper = img.closest('.moment-content-wrapper');
const loadingEl = wrapper.nextElementSibling;
const errorEl = loadingEl.nextElementSibling;
// 显示加载状态
loadingEl.style.display = 'flex';
errorEl.style.display = 'none';
// 创建新的Image对象用于预加载
const tempImg = new Image();
tempImg.onload = function() {
img.src = img.dataset.src;
img.classList.add('loaded');
loadingEl.style.display = 'none';
observer.unobserve(img);
};
tempImg.onerror = function() {
loadingEl.style.display = 'none';
errorEl.style.display = 'block';
};
tempImg.src = img.dataset.src;
}
});
}, {
rootMargin: '50px 0px',
threshold: 0.1
});
// 处理所有图片元素
document.querySelectorAll('.moment-content-wrapper').forEach(wrapper => {
const loadingEl = wrapper.nextElementSibling;
const images = wrapper.querySelectorAll('img');
if (images.length === 0) {
loadingEl.style.display = 'none';
return;
}
let loadedImages = 0;
images.forEach(img => {
if (img.src) {
img.dataset.src = img.src;
img.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'; // 透明占位图
imageObserver.observe(img);
// 监听图片加载完成
img.onload = () => {
loadedImages++;
if (loadedImages === images.length) {
loadingEl.style.display = 'none';
}
};
}
});
});
// 重试加载功能
window.retryLoad = function(button) {
const errorEl = button.closest('.moment-error');
const loadingEl = errorEl.previousElementSibling;
const wrapper = loadingEl.previousElementSibling;
const img = wrapper.querySelector('img');
errorEl.style.display = 'none';
loadingEl.style.display = 'flex';
const tempImg = new Image();
tempImg.onload = function() {
img.src = img.dataset.src;
img.classList.add('loaded');
loadingEl.style.display = 'none';
};
tempImg.onerror = function() {
loadingEl.style.display = 'none';
errorEl.style.display = 'block';
};
tempImg.src = img.dataset.src;
};
// 新增分页相关变量
let currentPage = 1;
const pageSize = {{ .Site.Params.moments.pageSize | default 8 }};
let filteredMoments = [];
let isLoading = false;
let isAllLoaded = false;
let loadingTimeout = null;
const loadingHint = document.createElement('div');
loadingHint.className = 'scroll-hint-container';
loadingHint.innerHTML = '<div class="loading-hint">加载更多...</div>';
document.querySelector('.moments-list').after(loadingHint);
// 显示分页内容的方法
function displayMoments() {
const end = currentPage * pageSize;
const totalItems = filteredMoments.length;
// 优化显示逻辑,只处理新增的内容
const start = (currentPage - 1) * pageSize;
filteredMoments.slice(start, end).forEach(moment => {
moment.style.display = 'block';
// 触发图片懒加载重新检查
moment.querySelectorAll('img[data-src]').forEach(img => {
imageObserver.observe(img);
});
});
// 更新底部提示
const hasMore = end < totalItems;
isAllLoaded = !hasMore;
if (totalItems === 0) {
loadingHint.innerHTML = '<div class="end-divider">暂无内容</div>';
} else if (isAllLoaded) {
loadingHint.innerHTML = '<div class="end-divider">———— · 已到底部 · ————</div>';
} else {
loadingHint.innerHTML = '<div class="loading-hint">加载更多...</div>';
}
loadingHint.style.display = 'block';
}
// 优化的滚动事件处理
function checkScroll() {
if (isLoading || isAllLoaded || filteredMoments.length === 0) return;
const { scrollTop, scrollHeight, clientHeight } = document.documentElement;
const threshold = 200; // 增加阈值,提前开始加载
const end = currentPage * pageSize;
if (end < filteredMoments.length && scrollTop + clientHeight >= scrollHeight - threshold) {
isLoading = true;
loadingHint.innerHTML = '<div class="loading-hint"><div class="loading-spinner"></div>加载中...</div>';
// 清除之前的超时
if (loadingTimeout) {
clearTimeout(loadingTimeout);
}
// 使用 requestAnimationFrame 和防抖优化性能
requestAnimationFrame(() => {
loadingTimeout = setTimeout(() => {
currentPage++;
displayMoments();
isLoading = false;
loadingTimeout = null;
}, 300);
});
}
}
// 点击标签时筛选逻辑
const tags = document.querySelectorAll('.tag-filter'); // 获取所有标签
const moments = document.querySelectorAll('.moment-card'); // 获取所有 moment 卡片
const allTags = document.querySelector('.all-tags'); // 获取"全部"按钮
const momentTags = document.querySelectorAll('.moment-tag'); // 获取所有卡片内的标签
// 默认选中"全部"标签
if (allTags) {
allTags.classList.add('selected');
}
// 点击标签时进行筛选
tags.forEach(tag => {
tag.addEventListener('click', function(e) {
e.preventDefault();
const selectedTag = tag.textContent.trim();
filteredMoments = Array.from(moments).filter(moment => {
const momentTags = moment.querySelectorAll('.moment-tag');
return selectedTag === '全部' ||
Array.from(momentTags).some(t => t.textContent === selectedTag);
});
currentPage = 1;
displayMoments();
window.scrollTo(0, 0); // 筛选后回到顶部
tags.forEach(t => t.classList.remove('selected'));
tag.classList.add('selected');
});
});
// 为卡片内的标签添加点击事件
momentTags.forEach(tag => {
tag.style.cursor = 'pointer';
tag.addEventListener('click', function() {
const tagText = this.textContent.trim();
// 找到对应的顶部标签并触发点击
tags.forEach(headerTag => {
if (headerTag.textContent.trim() === tagText) {
headerTag.click();
}
});
});
});
// 初始加载
filteredMoments = Array.from(moments);
displayMoments();
// 确保提示容器正确插入
const existingHint = document.querySelector('.scroll-hint-container');
if (!existingHint) {
const hintContainer = document.createElement('div');
hintContainer.className = 'scroll-hint-container';
document.querySelector('.moments-list').after(hintContainer);
}
window.addEventListener('scroll', checkScroll);
});
</script>
<!-- 在页面底部引入 Waline 评论脚本并初始化 -->
<script type="module">
import { init } from 'https://unpkg.com/@waline/client@v3/dist/waline.js';
const walineParams = {
/* 这里根据你自己的 Waline 配置进行调整 */
serverURL: '{{ .Site.Params.waline.serverURL }}',
lang: '{{ .Site.Params.waline.lang | default "zh-CN" }}',
visitor: '{{ .Site.Params.waline.visitor | default "匿名者" }}',
emoji: [
{{- range .Site.Params.waline.emoji }}
'{{ . }}',
{{- end }}
],
requiredMeta: [
{{- range .Site.Params.waline.requiredMeta }}
'{{ . }}',
{{- end }}
],
locale: {
admin: '{{ .Site.Params.waline.locale.admin | default "作者本人" }}',
placeholder: '{{ .Site.Params.waline.locale.placeholder | default "🍗所以我配有一条评论吗!" }}',
},
dark: '{{ .Site.Params.waline.dark | default "html.dark" }}',
};
// 点击"添加评论"按钮时,显示对应卡片下的评论区
window.showComment = function(element) {
const slug = element.getAttribute('data-slug');
const path = element.getAttribute('data-path');
const commentElement = document.getElementById('waline-' + slug);
// 如果已激活则清空
if (commentElement.classList.contains('active')) {
commentElement.classList.remove('active');
commentElement.innerHTML = '';
return;
}
// 移除其它所有已激活评论区
const allComments = document.querySelectorAll('.waline-container');
allComments.forEach(el => {
el.classList.remove('active');
el.innerHTML = '';
});
// 激活当前评论区
commentElement.classList.add('active');
// 初始化 Waline
init({
el: commentElement,
serverURL: walineParams.serverURL,
lang: walineParams.lang,
visitor: walineParams.visitor,
emoji: walineParams.emoji,
requiredMeta: walineParams.requiredMeta,
locale: walineParams.locale,
path: path,
dark: walineParams.dark,
// 个性化显示
commentSorting: 'hottest', // 评论列表排序方式
wordLimit: 500, // 评论字数限制
search: false, // 自定义搜索功能
reaction: false, // 文章表情互动功能
imageUploader: false, // 自定义图片上传
});
}
</script>
<!-- 获取评论数 -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// 获取所有评论按钮
const commentBtns = document.querySelectorAll('.moment-comment-btn');
// 确保有找到评论按钮
if (commentBtns.length > 0) {
commentBtns.forEach(button => {
const slug = button.getAttribute('data-slug'); // 获取按钮对应的 slug
const commentText = button.querySelector('.comment-text'); // 获取按钮中的评论文本
const serverURL = '{{ .Site.Params.waline.serverURL }}'; // 获取 Waline 服务器地址
// 输出调试信息,查看是否有多个按钮
console.log(`Processing button with slug: ${slug}`);
if (slug && commentText) {
// 假设你有一个获取评论数量的 API 或接口
fetch(`${serverURL}/api/comment?type=count&url=${slug}`)
.then(response => response.json())
.then(data => {
if (commentText) {
// 从 API 返回的数据中提取评论数
const commentCount = data.data && data.data[0] ? data.data[0] : 0; // 如果没有数据或评论数,默认为 0
// 输出调试信息,查看评论数
console.log(`Fetched comment count: ${commentCount}`);
// 更新评论按钮上的评论数量
commentText.textContent = `评论 (${commentCount})`; // 更新评论数
}
})
.catch(error => {
console.error('Error fetching comment count:', error);
if (commentText) {
// 如果 API 请求失败,保持评论数为 0
commentText.textContent = `评论 (0)`;
}
});
} else {
console.error('Slug or commentText missing for button:', button);
}
});
} else {
console.error('No comment buttons found');
}
});
</script>
{{ end }}
|
3.15.2 Build options#
创建构建选项文件 ../content/moments/_index.md ,内容如下:
1
2
3
4
5
6
7
8
9
10
11
| ---
title: "说说"
DateFormat: 2006-01-02 15:04
build:
render: always
cascade:
- build:
list: local
publishResources: false
render: never
---
|
3.15.3 定义页面样式#
自定义页面样式 ../assets/css/extended/moments.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
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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
| /*
全局:亮色模式下的默认值
说明:PaperMod 会在 <html> 或 <body> 上添加 .dark 类切换暗色,
所以这里只需定义默认(亮色)和暗色时的变量即可。
*/
:root {
--card-bg: #fff;
--card-text: #333;
--tag-bg: #f2f4f5;
--tag-text: #333;
--comment-btn-color: #999;
--time-color: #999;
--tag-filter-bg: #fff;
--tag-filter-text: #333;
--tag-filter-hover-bg: #55ac68;
--gradient-mask: linear-gradient(180deg, rgba(255, 255, 255, 0), #ffffff 100%);
}
/* 暗色模式下的变量 */
.dark {
--card-bg: rgb(46, 46, 51);
--card-text: rgb(196, 196, 197); /* 对比度够高的浅色文字 */
--tag-bg: rgb(65, 66, 68); /* 比卡片再浅一些,或自己喜欢的颜色 */
--tag-text: #eee;
--comment-btn-color: #aaa;
--time-color: #ccc;
--tag-filter-bg: #555;
--tag-filter-text: #ddd;
--tag-filter-hover-bg: #55ac68;
--gradient-mask: linear-gradient(180deg, rgba(46, 46, 51, 0), rgb(46, 46, 51) 100%);
--tag-filter-shadow: 0 2px 6px rgba(0, 0, 0, 0.15); /* 新增暗色阴影变量 */
}
/* 单列容器:依旧保持 vertical 布局 */
.moments-list {
display: flex;
flex-direction: column;
gap: 1.5rem;
margin-top: 1.5rem;
}
/* 卡片:使用CSS变量 */
.moment-card {
width: 100%;
max-width: 800px; /* 每张卡片最多800px */
margin: 0 auto; /* 居中 */
position: relative;
background: var(--card-bg);
color: var(--card-text);
border-radius: 8px;
padding: 1rem;
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
display: none; /* 初始隐藏,由JS控制显示 */
flex-direction: column;
justify-content: space-between;
}
.moment-card.hidden {
display: none; /* 隐藏元素并且不占据空间 */
}
/* 头像 & 作者 */
.moment-header {
position: relative;
display: flex; /* 水平排列头像、昵称和按钮 */
align-items: center; /* 垂直居中 */
justify-content: space-between; /* 头像和昵称左对齐,按钮右对齐 */
}
.moment-header .left-content {
display: flex;
align-items: center;
gap: 1rem; /* 固定间距,控制头像和昵称之间的距离 */
}
.moment-avatar{
width: 40px;
height: 40px;
object-fit: cover; /* 保证图片不变形 */
border-radius: 4px;
transition: transform 1s ease; /* 平滑的旋转过渡效果 */
cursor: pointer; /* 鼠标悬停时显示为可点击 */
}
.moment-avatar:active {
transform: rotate(360deg); /* 点击时旋转一圈 */
}
.moment-author {
font-weight: 600;
font-size: 16px;
/* 跟随卡片文字颜色 */
color: var(--card-text);
}
/* 主体内容 */
.moment-body {
font-size: 16px;
color: var(--card-text);
padding: 0;
line-height: 1.5;
}
.moment-body p {
margin-bottom: 0.6rem; /* 默认值可能是 1rem,改为 0.75rem 或更小 */
line-height: 1.6; /* 确保行间距仍然易读 */
}
.moment-body ol {
padding-left: 1rem; /* 调整左侧内边距,避免序号超出范围 */
margin-left: 0; /* 确保整体左对齐 */
list-style-position: inside; /* 确保序号在内容外部显示 */
}
.image-row {
display: grid;
gap: 0.4rem; /* 默认图片之间的间距 */
margin: 1rem 0; /* 上下与其他内容的间距 */
}
.image-row-1col {
grid-template-columns: repeat(1, 1fr); /* 单列布局 */
}
.image-row-2col {
grid-template-columns: repeat(2, 1fr); /* 双列布局 */
gap: 0.4rem; /* 自定义两列间距 */
}
.image-row-3col {
grid-template-columns: repeat(3, 1fr); /* 三列布局 */
gap: 0.2rem; /* 自定义三列间距 */
}
.image-row img {
width: 100%; /* 图片宽度占满格子 */
aspect-ratio: 1 / 1; /* 强制为正方形 */
object-fit: cover; /* 裁切图片内容,居中显示 */
display: block; /* 避免周围多余间隙 */
border-radius: 2px; /* 可选:增加圆角 */
}
/* 标签 */
.moment-tags {
margin-bottom: 1rem;
}
.moment-tag {
display: inline-block;
padding: 0.3em 0.6em;
margin-right: 0.5em;
margin-top: 1.2em;
background: var(--tag-bg);
color: var(--tag-text);
font-size: 14px;
border-radius: 4px;
}
/* 底部:时间 + 评论按钮 */
.moment-bottom {
display: flex;
align-items: center;
justify-content: space-between;
}
.moment-time span {
font-size: 14px;
color: var(--time-color); /* 比主体更淡一点 */
}
/* 评论按钮 */
.moment-comment-btn {
background: none;
border: none;
cursor: pointer;
padding: 0;
display: inline-flex;
align-items: center;
/* 使用变量 color */
color: var(--comment-btn-color);
transition: color 0.2s;
font-size: 14px;
}
.moment-comment-btn:hover {
/* 可在暗色下也进行不同程度的 hover 颜色变化 */
color: var(--card-text);
}
.moment-comment-btn svg {
width: 18px;
height: 18px;
fill: currentColor;
}
.moment-comment-btn .comment-text {
margin-left: 0.3em;
}
/* 顶部筛选标签样式 */
.tags-filter {
margin-bottom: 20px;
}
.tags-filter ul {
list-style: none;
padding: 0;
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.tags-filter li {
margin: 0;
}
.tags-filter .tag-filter {
text-decoration: none;
color: var(--tag-filter-text);
cursor: pointer;
padding: 8px 16px;
background-color: var(--tag-filter-bg);
border-radius: 20px;
font-size: 14px;
transition: all 0.3s ease;
font-weight: normal;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); /* 缩小垂直偏移,降低透明度 */
}
.tag-filter.selected {
background-color: var(--tag-filter-hover-bg);
color: #fff;
font-weight: bold;
box-shadow: 0 2px 4px rgba(85, 172, 104, 0.1); /* 减少扩散半径和透明度 */
}
.tag-filter:hover {
background-color: var(--tag-filter-hover-bg);
color: #fff;
transform: translateY(-1px);
box-shadow: 0 2px 6px rgba(85, 172, 104, 0.15); /* 保持微移效果但降低阴影强度 */
}
go.moment-loading {
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 1rem;
gap: 0.5rem;
}
.loading-spinner {
width: 24px;
height: 24px;
border: 2px solid var(--card-text);
border-top-color: transparent;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.loading-hint {
text-align: center;
padding: 0.5rem 0;
color: var(--card-text);
margin: 0 auto;
max-width: 600px;
opacity: 0.7;
font-size: 0.9em;
transition: opacity 0.3s ease;
}
.loading-hint[style*="block"],
.end-divider {
display: block !important;
}
.end-hint {
text-align: center;
padding: 2rem 0;
position: relative;
}
.end-divider {
color: #dcdcdc;
font-size: 0.9em;
text-align: center;
width: 100%;
padding: 2rem 0;
margin: 0 auto;
}
/* 修复提示容器样式(新增) */
.scroll-hint-container {
width: 100%;
max-width: 800px;
margin: 0 auto;
padding: 1rem;
font-size: 0.95em;
}
.skeleton-content {
width: 100%;
padding: 0.5rem 0;
}
.skeleton-line {
height: 16px;
background: linear-gradient(90deg, var(--card-bg) 25%, rgba(128, 128, 128, 0.1) 50%, var(--card-bg) 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
border-radius: 4px;
margin-bottom: 0.5rem;
}
@keyframes loading {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
|
3.15.4 新增一条说说#
在 moments 目录下创建一个 md 文档,frontmatter 参考如下:
1
2
3
4
5
6
7
8
| ---
date: 2024-03-13T09:05:00+08:00
slug: "change to your moment slug"
tags:
- Apple
draft: false
---
enter your moment content.
|
参数说明:
date 是可选的。在瞬间左下角显示的时间。(建议显示指定该值,如果你未配置此项,也可能显示时间,因为赋值方式为 .Param "date")slug 是必须的。它涉及到与评论绑定,建议使用 UUID 或随机数来保证不重复。tags 是可选的。标记这条瞬间的标签,可以为多个。(注意,此标签与文章标签无关)hideComment 是可选的。如果为 true 则不会在这个瞬间的右下角显示评论按钮。
3.15.5 头像问题#
头像如果不能正常显示,需在站点配置文件中添加一项参数,如下:
1
2
3
4
5
| params:
# ......
label:
# ......
avatar: "/imgs/avatar.png" # 修改为你的实际图像路径
|
IV. 参考文档#
田少晗的个人博客
周鑫的个人博客
似水
夜云泊
3rd’s Blog
Dvel’s Blog
Tofuwine’s Blog
loyayz
Drifting Boats
微控圈(MCU Loop)