Hugo-PaperMod主题自定义
Hugo
PaperMod 主题轻量且美观,参考网上的教程,并借助 AI
和自己的理解加以修改,记录下来供自己和他人参阅。
Note更换了主题, PaperMod 站点访问链接:PaperMod Blog
准备
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}
首页文章封面图侧边显示
首页文章列表的封面图很高,导致一页放不下几篇文章,希望可以把封面图放到文章信息侧边,有两种方法可以实现。
一种只需要添加自定义 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 }}
Tip这里直接将样式写入了模板中,工具栏的背景可以自己更换,可以设置成代码块背景色成为一个整体,也可以自己更改。
然后同样在站点配置文件中添加参数控制开关:
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
实现,访问项目地址
下载下来添加到自己的目录即可,还包含了文章缩略图。
Important测试后发现,如果只添加目录样式,会有错位,需要添加他的自定义设置文件,但是文章总体布局会变宽,具体自行测试。
方法三
更改模板文件,使用 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-Diary 主题修改记录
添加 GitHub 风格的 Alert 块引用
参考之前的文章:Hugo博客添加GitHub风格的Alert块引用
Important需要
Hugo
版本在0.132
以上
新建 ~/layouts/_default/_markup/render-blockquote-alert.html
,写入模板内容:
1{{ $alertTypes := dict
2 "note" "<path d=\"M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z\"></path>"
3 "tip" "<path d=\"M8 1.5c-2.363 0-4 1.69-4 3.75 0 .984.424 1.625.984 2.304l.214.253c.223.264.47.556.673.848.284.411.537.896.621 1.49a.75.75 0 0 1-1.484.211c-.04-.282-.163-.547-.37-.847a8.456 8.456 0 0 0-.542-.68c-.084-.1-.173-.205-.268-.32C3.201 7.75 2.5 6.766 2.5 5.25 2.5 2.31 4.863 0 8 0s5.5 2.31 5.5 5.25c0 1.516-.701 2.5-1.328 3.259-.095.115-.184.22-.268.319-.207.245-.383.453-.541.681-.208.3-.33.565-.37.847a.751.751 0 0 1-1.485-.212c.084-.593.337-1.078.621-1.489.203-.292.45-.584.673-.848.075-.088.147-.173.213-.253.561-.679.985-1.32.985-2.304 0-2.06-1.637-3.75-4-3.75ZM5.75 12h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1 0-1.5ZM6 15.25a.75.75 0 0 1 .75-.75h2.5a.75.75 0 0 1 0 1.5h-2.5a.75.75 0 0 1-.75-.75Z\"></path>"
4 "important" "<path d=\"M0 1.75C0 .784.784 0 1.75 0h12.5C15.216 0 16 .784 16 1.75v9.5A1.75 1.75 0 0 1 14.25 13H8.06l-2.573 2.573A1.458 1.458 0 0 1 3 14.543V13H1.75A1.75 1.75 0 0 1 0 11.25Zm1.75-.25a.25.25 0 0 0-.25.25v9.5c0 .138.112.25.25.25h2a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h6.5a.25.25 0 0 0 .25-.25v-9.5a.25.25 0 0 0-.25-.25Zm7 2.25v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 9a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z\"></path>"
5 "warning" "<path d=\"M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z\"></path>"
6 "caution" "<path d=\"M4.47.22A.749.749 0 0 1 5 0h6c.199 0 .389.079.53.22l4.25 4.25c.141.14.22.331.22.53v6a.749.749 0 0 1-.22.53l-4.25 4.25A.749.749 0 0 1 11 16H5a.749.749 0 0 1-.53-.22L.22 11.53A.749.749 0 0 1 0 11V5c0-.199.079-.389.22-.53Zm.84 1.28L1.5 5.31v5.38l3.81 3.81h5.38l3.81-3.81V5.31L10.69 1.5ZM8 4a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 8 4Zm0 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z\"></path>"
7 }}
8 {{ if eq .Type "alert" }}
9 <blockquote class="alert-blockquote alert-{{ .AlertType }}">
10 <p class="alert-heading">
11 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16">
12 {{ index $alertTypes .AlertType | safeHTML }}
13 </svg>
14 <span>{{ or (i18n .AlertType) (title .AlertType) }}</span>
15 </p>
16 {{ .Text | safeHTML }}
17 </blockquote>
18 {{ else }}
19 <blockquote>
20 {{ .Text | safeHTML }}
21 </blockquote>
22 {{ end }}
23
24
25<style>
26 :root {
27 --alert-title-color: #fff;
28 --alert-content-color: inherit;
29 }
30
31 .post-content .alert-blockquote {
32 --title-background-color: #166dd0;
33 --content-background-color: #e7f2fa;
34 padding: 0;
35 margin: 1rem 0;
36 border-radius: 4px;
37 color: var(--alert-content-color);
38 border-inline-start: none;
39 }
40
41 .alert-blockquote * {
42 color: inherit;
43 }
44
45 .alert-blockquote .alert-heading {
46 padding: 4px 18px;
47 border-radius: 4px 4px 0 0;
48 font-weight: 600;
49 color: var(--alert-title-color);
50 display: flex;
51 align-items: center;
52 background: var(--title-background-color);
53 margin-bottom: 0;
54 }
55
56 .alert-heading svg {
57 width: 1em;
58 height: 1em;
59 margin-right: 0.5rem;
60 fill: currentColor;
61 }
62
63 .alert-blockquote > :not(.alert-heading) {
64 padding: 18px;
65 background-color: var(--content-background-color);
66 }
67
68 .alert-blockquote p:last-child {
69 text-align: justify;
70 margin-bottom: 0;
71 }
72
73 .alert-blockquote.alert-tip { --title-background-color: #1a7f37; --content-background-color: #efe; }
74 .alert-blockquote.alert-important { --title-background-color: #8250df; --content-background-color: #f5f0ff; }
75 .alert-blockquote.alert-warning { --title-background-color: #9a6700; --content-background-color: #fff8c5; }
76 .alert-blockquote.alert-caution { --title-background-color: #cf222e; --content-background-color: #ffebe9; }
77
78 body.dark {
79 :root {
80 --alert-content-color: #d0d7dd;
81 }
82
83 .post-content .alert-blockquote {
84 --title-background-color: #58a6ff;
85 --content-background-color: #0d1d30;
86 }
87
88 .alert-blockquote.alert-tip { --title-background-color: #3fb950; --content-background-color: #0f2a1b; }
89 .alert-blockquote.alert-important { --title-background-color: #a371f7; --content-background-color: #2a1d3f; }
90 .alert-blockquote.alert-warning { --title-background-color: #d29922; --content-background-color: #3b2300; }
91 .alert-blockquote.alert-caution { --title-background-color: #f85149; --content-background-color: #3d0c0c; }
92 }
93</style>
Important下面这是
GitHub
风格
1<style>
2 .post-content .alert-blockquote {
3 border-left: 4px solid;
4 border-radius: 5px;
5 }
6
7 .alert-blockquote .alert-heading {
8 display: flex;
9 align-items: center;
10 font-weight: 600;
11 margin-bottom: 0.5rem;
12 }
13
14 .alert-blockquote .alert-heading svg {
15 margin-right: 0.5rem;
16 fill: currentColor;
17 }
18
19 /* 左侧border颜色 */
20 .post-content .alert-note { border-left-color: #0969da; }
21 .post-content .alert-tip { border-left-color: #1a7f37; }
22 .post-content .alert-important { border-left-color: #8250df; }
23 .post-content .alert-warning { border-left-color: #9a6700; }
24 .post-content .alert-caution { border-left-color: #cf222e; }
25
26 /* 标题颜色 */
27 .alert-note .alert-heading { color: #0969da; }
28 .alert-tip .alert-heading { color: #1a7f37; }
29 .alert-important .alert-heading { color: #8250df; }
30 .alert-warning .alert-heading { color: #9a6700; }
31 .alert-caution .alert-heading { color: #cf222e; }
32
33 body.dark .alert-note .alert-heading { color: #58a6ff; }
34 body.dark .alert-tip .alert-heading { color: #3fb950; }
35 body.dark .alert-important .alert-heading { color: #a371f7; }
36 body.dark .alert-warning .alert-heading { color: #d29922; }
37 body.dark .alert-caution .alert-heading { color: #f85149; }
38</style>
热力图
代码直接复制的原博主代码,访问原文,也可以参考下面两篇文章:
新建一个短代码 ~/layouts/shortcodes/heatmap.html
1<p style="text-align: center;">文章统计</p>
2<div class="heatmap_container"> <!-- 全部用 Flex 排版 -->
3 <div class="heatmap_content">
4 <div class="heatmap_week">
5 <span>Mon</span>
6 <span> </span> <!-- 不需要显示的星期用空格表示 -->
7 <span>Wed</span>
8 <span> </span>
9 <span>Fri</span>
10 <span> </span>
11 <span>Sun</span>
12 </div>
13 <div class="heatmap_main">
14 <div class="month heatmap_month">
15 <!-- js 检测屏幕宽度动态生成月份 -->
16 </div>
17 <div id="heatmap" class="heatmap">
18 <!-- js 检测屏幕宽度动态生成年度日历小方块 -->
19 </div>
20 </div>
21 </div>
22 <div class="heatmap_footer">
23 <div class="heatmap_less">Less</div>
24 <div class="heatmap_level">
25 <span class="heatmap_level_item heatmap_level_0"></span>
26 <span class="heatmap_level_item heatmap_level_1"></span>
27 <span class="heatmap_level_item heatmap_level_2"></span>
28 <span class="heatmap_level_item heatmap_level_3"></span>
29 <span class="heatmap_level_item heatmap_level_4"></span>
30 </div>
31 <div class="heatmap_more">More</div>
32 </div>
33</div>
34
35
36<style>
37 :root {
38 /* GitHub Light Color */
39 --ht-main: #334155;
40 --ht-day-bg: #ebedf0;
41 --ht-tooltip: #24292f;
42 --ht-tooltip-bg: #fff;
43 --ht-lv-0: #ebedf0;
44 --ht-lv-1: #9be9a8;
45 --ht-lv-2: #40c463;
46 --ht-lv-3: #30a14e;
47 --ht-lv-4: #216e39;
48 }
49
50 body.dark {
51 /* GitHub Dark Dimmed Color */
52 --ht-main: #94a3b8;
53 --ht-day-bg: #161b22;
54 --ht-tooltip: #24292f;
55 --ht-tooltip-bg: #fff;
56 --ht-lv-0: #161b22;
57 --ht-lv-1: #0e4429;
58 --ht-lv-2: #006d32;
59 --ht-lv-3: #26a641;
60 --ht-lv-4: #39d353;
61 }
62
63 .heatmap_container {
64 display: flex;
65 flex-direction: column;
66 align-items: center;
67 font-size: 10px;
68 line-height: 12px;
69 color: var(--ht-main);
70 }
71
72 .heatmap_content {
73 display: flex;
74 flex-direction: row;
75 align-items: flex-end
76 }
77
78 .heatmap_week {
79 display: flex;
80 margin-top: 0.25rem;
81 margin-right: 2px;
82 flex-direction: column;
83 justify-content: flex-end;
84 align-items: flex-end;
85 text-align: right
86 }
87
88 .heatmap_main {
89 display: flex;
90 flex-direction: column
91 }
92
93 .heatmap_month {
94 display: flex;
95 margin-top: 0.25rem;
96 margin-right: 0.25rem;
97 justify-content: space-around;
98 align-items: flex-end;
99 text-align: right;
100 }
101
102 .heatmap {
103 display: flex;
104 flex-direction: row;
105 height: 84px;
106 }
107
108 .heatmap_footer {
109 display: flex;
110 margin-top: 0.5rem;
111 align-items: center
112 }
113
114 .heatmap_level {
115 display: flex;
116 gap: 2px;
117 margin: 0 0.25rem;
118 flex-direction: row;
119 align-items: center;
120 width: max-content;
121 height: 10px
122 }
123
124 .heatmap_level_item {
125 display: block;
126 border-radius: 0.125rem;
127 width: 10px;
128 height: 10px;
129 }
130
131 .heatmap_level_0 {
132 background: var(--ht-lv-0);
133 }
134
135 .heatmap_level_1 {
136 background: var(--ht-lv-1);
137 }
138
139 .heatmap_level_2 {
140 background: var(--ht-lv-2);
141 }
142
143 .heatmap_level_3 {
144 background: var(--ht-lv-3);
145 }
146
147 .heatmap_level_4 {
148 background: var(--ht-lv-4);
149 }
150
151 .heatmap_week {
152 display: flex;
153 flex-direction: column;
154 }
155
156 .heatmap_day {
157 width: 10px;
158 height: 10px;
159 background-color: var(--ht-day-bg);
160 margin: 1px;
161 border-radius: 2px;
162 display: inline-block;
163 position: relative;
164 }
165
166 .heatmap_tooltip {
167 position: absolute;
168 bottom: 12px;
169 left: 50%;
170 width: max-content;
171 color: var(--ht-tooltip);
172 background-color: var(--ht-tooltip-bg);
173 font-size: 12px;
174 line-height: 16px;
175 padding: 8px;
176 border-radius: 3px;
177 white-space: pre-wrap;
178 opacity: 1;
179 transition: 0.3s;
180 z-index: 1000;
181 text-align: right;
182 transform: translateX(-50%);
183 }
184
185 .heatmap_tooltip_count,
186 .heatmap_tooltip_post {
187 display: inline-block;
188 }
189
190 .heatmap_tooltip_title,
191 .heatmap_tooltip_date {
192 display: block;
193 }
194
195 .heatmap_tooltip_date {
196 margin: 0 0.25rem;
197 }
198
199 .heatmap_day_level_0 {
200 background-color: var(--ht-lv-0);
201 }
202
203 .heatmap_day_level_1 {
204 background-color: var(--ht-lv-1);
205 }
206
207 .heatmap_day_level_2 {
208 background-color: var(--ht-lv-2);
209 }
210
211 .heatmap_day_level_3 {
212 background-color: var(--ht-lv-3);
213 }
214
215 .heatmap_day_level_4 {
216 background-color: var(--ht-lv-4);
217 }
218</style>
219
220
221<script>
222 // 获取最近一年的文章数据
223{{ $pages := where .Site.RegularPages "Date" ">" (now.AddDate -1 0 0) }}
224{{ $pages := $pages.Reverse }}
225var blogInfo = {
226 "pages": [
227 {{ range $index, $element := $pages }}
228 {
229 "title": "{{ replace (replace .Title "《" "〈") "》" "〉" }}",
230 "date": "{{ .Date.Format "2006-01-02" }}",
231 "year": "{{ .Date.Format "2006" }}",
232 "month": "{{ .Date.Format "01" }}",
233 "day": "{{ .Date.Format "02" }}",
234 "word_count": "{{ .WordCount }}"
235 }{{ if ne (add $index 1) (len $pages) }},{{ end }}
236 {{ end }}
237 ]
238};
239// console.log(blogInfo)
240
241let currentDate = new Date();
242currentDate.setFullYear(currentDate.getFullYear() - 1);
243
244let startDate;
245
246let monthDiv = document.querySelector('.month');
247let monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
248
249if (window.innerWidth < 768) {
250 numMonths = 6;
251} else {
252 numMonths = 12;
253}
254
255let startMonthIndex = (currentDate.getMonth() - (numMonths - 1) + 12) % 12;
256for (let i = startMonthIndex; i < startMonthIndex + numMonths; i++) {
257 let monthSpan = document.createElement('span');
258 let monthIndex = i % 12;
259 monthSpan.textContent = monthNames[monthIndex];
260 monthDiv.appendChild(monthSpan);
261}
262
263function getStartDate() {
264 const today = new Date();
265
266 if (window.innerWidth < 768) {
267 numMonths = 6;
268 } else {
269 numMonths = 12;
270 }
271
272 const startDate = new Date(today.getFullYear(), today.getMonth() - numMonths + 1, 1, today.getHours(), today.getMinutes(), today.getSeconds());
273
274 while (startDate.getDay() !== 1) {
275 startDate.setDate(startDate.getDate() + 1);
276 }
277
278 return startDate;
279}
280
281function getWeekDay(date) {
282 const day = date.getDay();
283 return day === 0 ? 6 : day - 1;
284}
285
286function createDay(date, title, count, post) {
287 const day = document.createElement("div");
288
289 day.className = "heatmap_day";
290
291 day.setAttribute("data-title", title);
292 day.setAttribute("data-count", count);
293 day.setAttribute("data-post", post);
294 day.setAttribute("data-date", date);
295
296 day.addEventListener("mouseenter", function () {
297 const tooltip = document.createElement("div");
298 tooltip.className = "heatmap_tooltip";
299
300 let tooltipContent = "";
301
302 if (post && parseInt(post, 10) !== 0) {
303 tooltipContent += '<span class="heatmap_tooltip_post">' + '共 ' + post + ' 篇' + '</span>';
304 }
305
306 if (count && parseInt(count, 10) !== 0) {
307 tooltipContent += '<span class="heatmap_tooltip_count">' + ' ' + count + ' 字;' + '</span>';
308 }
309
310 if (title && parseInt(title, 10) !== 0) {
311 tooltipContent += '<span class="heatmap_tooltip_title">' + title + '</span>';
312 }
313
314 if (date) {
315 tooltipContent += '<span class="heatmap_tooltip_date">' + date + '</span>';
316 }
317
318 tooltip.innerHTML = tooltipContent;
319 day.appendChild(tooltip);
320 });
321
322 day.addEventListener("mouseleave", function () {
323 const tooltip = day.querySelector(".heatmap_tooltip");
324 if (tooltip) {
325 day.removeChild(tooltip);
326 }
327 });
328
329 if (count == 0 ) {
330 day.classList.add("heatmap_day_level_0");
331 } else if (count > 0 && count < 1000) {
332 day.classList.add("heatmap_day_level_1");
333 } else if (count >= 1000 && count < 2000) {
334 day.classList.add("heatmap_day_level_2");
335 } else if (count >= 2000 && count < 3000) {
336 day.classList.add("heatmap_day_level_3");
337 } else {
338 day.classList.add("heatmap_day_level_4");
339 }
340
341 return day;
342}
343
344function createWeek() {
345 const week = document.createElement('div');
346 week.className = 'heatmap_week';
347 return week;
348}
349
350function createHeatmap() {
351 const container = document.getElementById('heatmap');
352 const startDate = getStartDate();
353 const endDate = new Date();
354 const weekDay = getWeekDay(startDate);
355
356 let currentWeek = createWeek();
357 container.appendChild(currentWeek);
358
359 let currentDate = startDate;
360 let i = 0;
361
362 while (currentDate <= endDate) {
363 if (i % 7 === 0 && i !== 0) {
364 currentWeek = createWeek();
365 container.appendChild(currentWeek);
366 }
367
368 const dateString = `${currentDate.getFullYear()}-${("0" + (currentDate.getMonth()+1)).slice(-2)}-${("0" + (currentDate.getDate())).slice(-2)}`;
369
370 const articleDataList = blogInfo.pages.filter(page => page.date === dateString);
371
372 if (articleDataList.length > 0) {
373 const titles = articleDataList.map(data => data.title);
374 const title = titles.map(t => `《${t}》`).join('<br />');
375
376 let count = 0;
377 let post = articleDataList.length;
378
379 articleDataList.forEach(data => {
380 count += parseInt(data.word_count, 10);
381 });
382
383 const formattedDate = formatDate(currentDate);
384 const day = createDay(formattedDate, title, count, post);
385 currentWeek.appendChild(day);
386 } else {
387 const formattedDate = formatDate(currentDate);
388 const day = createDay(formattedDate, '', '0', '0');
389 currentWeek.appendChild(day);
390 }
391
392 i++;
393 currentDate.setDate(currentDate.getDate() + 1);
394 }
395}
396
397function formatDate(date) {
398 const options = { month: 'short', day: 'numeric', year: 'numeric' };
399 return date.toLocaleDateString('en-US', options);
400}
401
402createHeatmap();
403</script>
可以修改其中的颜色适应自己的博客,使用方法如下:
1{\{< heatmap >}\}
图片展示
图片排版优化
代码来自:木木木木木博客
正文中的图片排版优化,直接通过 CSS
来,在自定义样式目录新建文件添加下面的样式:
1.post-content p:has(> img:nth-child(2)){column-count:2;column-gap:8px;margin:6px 0;}
2.post-content p:has(> img:nth-child(3)){column-count:3;}
3.post-content p:has(> img:nth-child(4)){column-count:4;}
4.post-content p:has(> img:nth-child(5)){column-count:5;}
5.post-content p:has(> img:nth-child(6)){column-count:4;}
6.post-content p:has(> img:nth-child(2)) img{display:inherit;}
7.post-content p:has(> img:nth-child(6)) img{margin-bottom:8px;}
使用时直接以 markdown
书写即可,中间不要有空行。
图片瀑布流
代码来自:木木木木木博客
在 extend_footer.html
中添加 js
:
1{{/* gallery瀑布流 */}}
2<script src="https://immmmm.com/waterfall.min.js"></script>
3<script src="https://immmmm.com/imgStatus.min.js"></script>
4<script>
5 document.addEventListener('DOMContentLoaded', () => {
6 //外链 gallery 标签相册瀑布流
7 var photosAll = document.getElementsByTagName('gallery') || '';
8 if(photosAll){
9 for(var i=0;i < photosAll.length;i++){
10 photosAll[i].innerHTML = '<div class="gallery-photos">'+photosAll[i].innerHTML+'</div>'
11 var photosIMG = photosAll[i].getElementsByTagName('img')
12 for(var j=0;j < photosIMG.length;j++){
13 wrap(photosIMG[j], document.createElement('div'));
14 }
15 }
16 }
17 function wrap(el, wrapper) {
18 wrapper.className = "gallery-photo";
19 el.parentNode.insertBefore(wrapper, el);
20 wrapper.appendChild(el);
21 }
22 //相册瀑布流
23 let galleryPhotos = document.querySelectorAll('.gallery-photos') || ''
24 if(galleryPhotos){
25 imgStatus.watch('.gallery-photo img', function(imgs) {
26 if(imgs.isDone()){
27 for(var i=0;i < galleryPhotos.length;i++){
28 waterfall(galleryPhotos[i]);
29 let pagePhoto = galleryPhotos[i].querySelectorAll('.gallery-photo');
30 for(var j=0;j < pagePhoto.length;j++){pagePhoto[j].className += " visible"};
31 }
32 }
33 });
34 window.addEventListener('resize', function () {
35 for(var i=0;i < galleryPhotos.length;i++){
36 waterfall(galleryPhotos[i]);
37 }
38 });
39 }
40 });
41</script>
42<script type="text/javascript" src="https://immmmm.com/view-image.js"></script>
43<script>
44 window.ViewImage && ViewImage.init('.gallery-photo img')
45</script>
Tip建议将代码中的
JS
文件放入自己博客根目录static
文件夹下引入。
引入样式:
1/* gallery瀑布流 */
2.gallery-photos{width:100%;}
3.gallery-photo{width:24.9%;position: relative;visibility: hidden;overflow: hidden;}
4.gallery-photo.visible{visibility: visible;animation: fadeIn 2s;}
5.gallery-photo img{display: block;width:100%;border-radius:0;padding:4px;animation: fadeIn 1s;cursor: pointer;transition: all .4s ease-in-out;}
6.gallery-photo span.photo-title,.gallery-photo span.photo-time{background: rgba(0, 0, 0, 0.3);padding:0px 8px;font-size:0.9rem;color: #fff;display:none;animation: fadeIn 1s;}
7.gallery-photo span.photo-title{position:absolute;bottom:4px;left:4px;}
8.gallery-photo span.photo-time{position:absolute;top:4px;left:4px;font-size:0.8rem;}
9.gallery-photo:hover span.photo-title{display:block;}
10.gallery-photo:hover img{transform: scale(1.1);}
11@media screen and (max-width: 1280px) {
12 .gallery-photo{width:33.3%;}
13}
14@media screen and (max-width: 860px) {
15 .gallery-photo{width:49.9%;}
16}
17@media (max-width: 683px){
18 .photo-time{display: none;}
19}
20@keyframes fadeIn{
21 0% {opacity: 0;}
22 100% {opacity: 1;}
23}
使用如下:
1<gallery>
2 <img src="https://xxxxx.jpg">
3 <img src="https://xxxxx.jpg">
4 <img src="https://xxxxx.jpg">
5</gallery>
Tip可以引入外链图片,也可以引入本地图片,默认路径为
~/static/
,如果使用markdown
直接将图片放置到一行,不要换行。
图片橱窗
通过橱窗样式展示图片,代码来自:codepen
新建 ~/layouts/shortcodes/image-showcase.html
,直接将所有内容放入其中:
1<div class="app">
2 <form class="plate">
3 {{ range $index, $src := .Params }}
4 <label class="item">
5 <input type="radio" name="item" {{ if eq $index 0 }}checked="checked"{{ end }}/>
6 <img src="{{ $src }}"/>
7 </label>
8 {{ end }}
9 </form>
10 </div>
11
12
13<style>
14/** side by side **/
15.app:nth-of-type(1) {
16 place-self: center right;
17 }
18 .app:nth-of-type(2) {
19 place-self: center left;
20 }
21
22 .app {
23 --preview-item-width: calc(100% / (var(--item-count) - 1));
24 --preview-item-height: 10%;
25 width: 50vmin;
26 height: 80vmin;
27 margin: auto;
28 }
29
30 .app [type="radio"] {
31 display: none;
32 }
33
34 .app .plate {
35 width: 100%;
36 height: 100%;
37 position: relative;
38 }
39
40 .app .plate .item {
41 display: block;
42 width: var(--preview-item-width);
43 height: var(--preview-item-height);
44 position: absolute;
45 bottom: 0;
46 left: var(--left);
47 transform-origin: top left;
48 transition: transform 0.5s, bottom 0.6s, left 0.6s, width 0.3s,
49 box-shadow 0.6s;
50 }
51
52 .app .plate .item img {
53 display: block;
54 width: 100%;
55 height: 100%;
56 object-fit: cover;
57 }
58
59 .app .plate .item.active {
60 --preview-item-width: 100%;
61 bottom: var(--preview-item-height); /* bubble up */
62 left: 0 !important;
63 height: calc(100% - var(--preview-item-height));
64 box-shadow: 0 0 0 transparent;
65 animation: anim 2s 1;
66 transform: translate3d(0, 0, -10px);
67 transition: transform 0.5s, bottom 0.6s, left 0.6s, width 0.3s, box-shadow 0s;
68 }
69
70 .app .plate .item.active img {
71 object-fit: contain;
72 }
73
74 /*
75 optional
76 */
77
78 .app .plate {
79 perspective: 100px;
80 perspective-origin: center center;
81 transform-style: preserve-3d;
82 pointer-events: none;
83 }
84
85 .app .plate::after {
86 content: "";
87 display: block;
88 width: 100%;
89 height: 15px;
90 position: absolute;
91 bottom: 0;
92 background: linear-gradient(transparent, rgba(0, 0, 0, 0.1));
93 transform: rotateX(90deg);
94 transform-origin: bottom center;
95 }
96
97 .app .plate .item:not(.active) {
98 transform-origin: center;
99 transform: scale(0.8) translate3d(0, 0, -5px);
100 pointer-events: auto;
101 }
102
103 .app .plate .item:not(.active):hover {
104 transform-origin: center;
105 transform: scale(0.8) translate3d(0, -1px, -5px);
106 box-shadow: 0 20px 10px -10px rgba(0, 0, 0, 0.3);
107 cursor: pointer;
108 }
109
110 /*
111 animation
112 */
113
114 @keyframes anim {
115 from {
116 transform: rotateY(6deg) rotateX(3deg);
117 }
118 }
119</style>
120
121
122<script>
123document.querySelectorAll('.app').forEach(init)
124
125function init(app) {
126 const items = app.querySelectorAll('.item')
127 app.style.setProperty('--item-count', items.length)
128 const form = app.querySelector('.plate')
129 form.addEventListener('input', () => update(app))
130 update(app)
131}
132
133function update(app) {
134 const items = Array.from(app.querySelectorAll('.item'))
135 const active = items.filter(x => x.querySelector(':checked'))[0]
136 const inactives = items.filter(x => x != active)
137 // toggle class
138 items.forEach(x => x.classList.toggle('active', x === active))
139 // re-calc anim props
140 inactives.forEach((x, i, xs) => x.style.setProperty('--left', `${i / xs.length}e2%`))
141}
142</script>
使用短代码如下:
1{\{< image-showcase "/images/pic1.jpg" "/images/pic2.jpg" "/images/pic3.jpg" >}\}
Tip图片地址默认仍然是博客根目录下
static
文件夹,或者引入外链图片也可以
图片轮播 | Carousel
原始代码来自:一种在 MemE 主题中实现轮播图功能的思路
修改代码来自:Hugo | 在文章中插入轮播图片
新建 ~/lauouts/shortcodes/loop.html
1{{ if .Site.Params.enableimgloop }}
2 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/3.4.2/css/swiper.min.css">
3 <!-- Swiper -->
4 <div class="swiper-container">
5 <div class="swiper-wrapper">
6 {{$itItems := split (.Get 0) ","}}
7 {{range $itItems }}
8 <div class="swiper-slide">
9 <img src="{{.}}" alt="">
10 </div>
11 {{end}}
12 </div>
13 <!-- Add Pagination -->
14 <div class="swiper-pagination"></div>
15 </div>
16
17 <script src="https://cdnjs.cloudflare.com/ajax/libs/Swiper/3.4.2/js/swiper.min.js"></script>
18 <!-- Initialize Swiper -->
19 <script>
20 var swiper = new Swiper('.swiper-container', {
21 pagination: '.swiper-pagination',
22 paginationClickable: true,
23 //自动调节高度
24 autoHeight: true,
25 //键盘左右方向键控制
26 keyboardControl : true,
27 //鼠标滑轮控制
28 mousewheelControl : true,
29 //自动切换
30 //autoplay : 5000,
31 //懒加载
32 lazyLoading : true,
33 lazyLoadingInPrevNext : true,
34 //无限循环
35 loop : true,
36 });
37 </script>
38{{ end }}
39
40<style>
41 .swiper-container {
42 width: 100%;
43 margin: 2em auto;
44 height: 330px;
45 border-radius: 10px;
46
47 }
48 .swiper-slide {
49 text-align: center;
50 font-size: 18px;
51 background-color: #fff;
52 /* Center slide text vertically */
53 display: flex;
54 justify-content: center;
55 align-items: center;
56 img {
57 margin: 0 !important;
58 }
59 }
60
61</style>
使用方法: 在站点配置文件中写入参数:
1params:
2 enableimgloop: true
1{\{< loop "/images/1.jpg,/images/2.jpg,/images/3.jpg" >}\}
参考
- Hugo博客文章封面图片缩小并移到侧边 | PaperMod主题
- 使用Github Pages+Hugo+PaperMod搭建博客
- Hugo blog & PaperMod
- 折腾 Hugo & PaperMod 主题
- JannikArndt/jannikarndt.github.io@8b99f6c
- Sulv’s Blog | Hugo 博客目录放在侧边 | PaperMod 主题
- 如何给 Hugo 博客添加热力图
- 给博客添加heatmap
- CSS 和 JS 实现博客热力图
- Hugo 相册短代码
- Hugo 多图排版这样来
- 图片瀑布流折腾记
- Simplicity {multi}
- Hugo | 在文章中插入轮播图片
- 一种在 MemE 主题中实现轮播图功能的思路