起因
最近在用霞鹜文楷(LXGW WenKai),配合上PaperMod这种强调文字的主题,在观感上很不错,于是决定转到Hugo。Hugo的文档和资料不太容易找,中文支持也少,中间遇到不少麻烦,记录下解决过程,给遇到同样问题的人一些参考。
安装
1> hugo version
2hugo v0.114.0-9df2ec7988e5a217a14901cc76c0b7e76b2e9f02+extended windows/amd64 BuildDate=2023-06-19T17:01:43Z VendorInfo=gohugoio
主题 PaperMod
执行hugo new site <blog>
创建博客目录,目录结构如下:
1 archetypes/ # 内容模板文件
2 assets/ # 静态资源
3 content/ # 博客内容
4 data/
5 layouts/ # 网站生成模版
6 public/ # 项目导出文件
7 resources/
8 static/ # 静态文件
9 themes/ # 主题
10 hugo.toml # 配置文件
默认不带主题,可以到Hugo官网主题淘一淘。本文使用PaperMod,将代码直接下载下来,放到themes/
下。当然也可以git clone
,但是themes/.git/
影响博客源码的提交,以及对应的源码部署方式,也可能导致文末出现的CSS加载失败问题。
使用 hugo server
启动本地调试服务,访问http://localhost:1313/
查看页面。使用hugo
生成网页文件至public目录。
配置 hugo.toml(必看)
站点目录的配置优先级高,会覆盖主题中的配置。所有在主题文件夹中的改动,都可以先将它复制到站点目录对应文件夹下,再作其他修改。这样主题更新的时候,自定义改动就不会丢失。
配置文件支持.toml
、.yaml
等。Hugo推荐.toml
,但是PaperMod推荐.yaml
。网上.toml
的比较少,本文使用这种。配置项的具体含义,请查阅Hugo的官方文档和PaperMod的配置wiki。
以下为/hugo.toml
配置参考:
1# baseURL = 'http://localhost:1313/'
2baseURL = 'https://blog.lordash.de'
3languageCode = 'zh-cn'
4title = '似水'
5theme = 'PaperMod'
6
7cleanDestinationDir = true
8enableEmoji = true # 允许使用Emoji表情
9enableInlineShortcodes = true # 允许内联短码
10enableRobotsTXT = true # 允许爬虫抓取到搜索引擎
11hasCJKLanguage = true # 自动检测是否包含中文日文韩文
12pagination.pagerSize = 15 # 每页文章数量
13
14# 语言设置
15defaultContentLanguage = "zh"
16
17# 单语言,必须在此处,以下设置之前
18[languages]
19[languages.zh]
20 languageName = "中文"
21
22[markup.goldmark.renderer]
23 unsafe = true # html标签
24[markup.highlight]
25 codeFences = true # 代码框
26 guessSyntax = true # 猜测代码类型
27 lineNos = true # 显示行号
28 lineNumbersInTable = false # table分隔行号与代码
29 noClasses = true # 代码块style而非class
30 style = "monokai" # 配色方案
31
32# 菜单设置
33[[menu.main]]
34 name = "搜索"
35 pre = "<i class='fa fa-search'></i>"
36 weight = 960
37 identifier = "search"
38 url = "/search"
39[[menu.main]]
40 name = "文章"
41 pre = "<i class='fa fa-list'></i>"
42 weight = 970
43 identifier = "posts"
44 url = "/posts"
45[[menu.main]]
46 name = "专栏"
47 pre = "<i class='fa fa-book'></i>"
48 weight = 980
49 identifier = "series"
50 url = "/series"
51[[menu.main]]
52 name = "友链"
53 pre = "<i class='fa fa-link'></i>"
54 weight = 990
55 identifier = "links"
56 url = "/links"
57[[menu.main]]
58 name = "关于"
59 pre = "<i class='fa fa-info-circle'></i>"
60 weight = 1000
61 identifier = "about"
62 url = "/about"
63
64[[permalinks]]
65 post = "/post/:section/:slug/" # 链接格式
66
67# 搜索功能
68[outputs]
69 home = ["HTML", "RSS", "JSON"]
70
71# 主题设置
72[params]
73 env = "production"
74 description = "Life is like a boat"
75 author = "Lordash"
76 keywords = ["中文博客","ACM竞赛题解","计算机代码编程","免费技术分享学习"]
77 DateFormat = "2006-01-02"
78 ShowCodeCopyButtons = true # 代码复制按钮
79 ShowFullTextinRSS = true # RSS展示全文
80 defaultTheme = "dark" # 默认主题颜色
81 hideSummary = false # 隐藏摘要
82 showtoc = true # 显示目录
83 tocopen = true # 目录默认展开
84 ShowPostNavLinks = true # 显示上一篇/下一篇
85 ShowBreadCrumbs = true # 文章顶部面包屑导航
86 # ShowRssButtonInSectionTermList = true
87 comments = true # 展示评论
88 hideFooter = false # 隐藏页脚信息
89 # ShowAllPagesInArchive = true #
90
91# 左上角标签
92[params.label]
93 text = "Lordash's blog"
94 # icon = "images/favicon-32x32-.png"
95 # iconHeight = 36
96
97[params.assets]
98 favicon = "images/favicon-16x16-.png" # 浏览器标签图标
99 favicon16x16 = "images/favicon-16x16-.png" # 浏览器标签图标
100 favicon32x32 = "images/favicon-32x32-.png" # 浏览器标签图标
101 disableHLJS = true # 不使用highlight.js
102
103[params.profileMode]
104 enabled = true # 个人主页模式
105 title = "似水"
106 subtitle = "Life is like a boat"
107 imageUrl = "https://s2.loli.net/2022/06/01/D6SzQ9Uc1dTFbKf.png"
108 imageWidth = "125"
109 imageHeight = "172"
110
111# 主页按钮
112[[params.profileMode.buttons]]
113 name = "技术"
114 url = "/post/tech"
115[[params.profileMode.buttons]]
116 name = "生活"
117 url = "/post/life"
118
119# 社交图标
120[[params.socialIcons]]
121 name = "github"
122 url = "https://github.com/GH1656409967"
123[[params.socialIcons]]
124 name = "QQ"
125 url = "http://wpa.qq.com/msgrd?v=3&uin=1656409967&site=qq&menu=yes"
126[[params.socialIcons]]
127 name = "neteasecloudmusic"
128 url = "https://music.163.com/#/user/home?id=270121274"
129[[params.socialIcons]]
130 name = "douban"
131 url = "https://www.douban.com/people/Lordash/"
132[[params.socialIcons]]
133 name = "email"
134 url = "mailto:1656409967@qq.com"
135[[params.socialIcons]]
136 name = "RSS"
137 url = "posts/index.xml"
138
139# fuse.js模糊搜索
140[params.fuseOpts]
141 isCaseSensitive = false # 不区分大小写
142 shouldSort = true # 搜索结果排序
143 location = 0
144 distance = 1000
145 threshold = 0.4
146 minMatchCharLength = 0
147 keys = ["title", "permalink", "summary", "content"]
148
149# 分类等级
150[taxonomies]
151 category = "categories"
152 tag = "tags"
153 series = "series"
154
155# 评论
156[params.twikoo]
157 version = "1.6.16"
158
159# 访客统计
160[params.busuanzi]
161 enable = true
默认中文 i18n
关键在于以下配置,且必须在其他配置上层,同理可以添加多语言。
1# 语言设置
2defaultContentLanguage = "zh"
3
4# 单语言,必须在此处,以下设置之前
5[languages]
6[languages.zh]
7 languageName = "中文"
自定义字体
正文采用霞鹜文楷(LXGW WenKai),代码采用Ubuntu Mono derivative Powerline。在/layouts/partials/extend_head.html
中引入
1<link rel="stylesheet" href="https://cdn.staticfile.org/lxgw-wenkai-screen-webfont/1.6.0/lxgwwenkaiscreen.css" media="print" onload="this.media='all'">
同时,在/assets/css/extended/blank.css
中配置
1body {
2 font-family: "LXGW WenKai Screen", sans-serif !important;
3}
4
5.post-content pre, code {
6 font-family: 'Ubuntu Mono derivative Powerline', sans-serif;
7 max-height: 40rem;
8}
菜单栏
菜单设置,根据权重weight排序。(折叠菜单和汉堡菜单,暂时没有好的适配方式,同时个人感觉也与主题不合,搁置。有了解的小伙伴烦请告知我一下)。
1# 菜单设置
2[[menu.main]]
3 name = "搜索"
4 pre = "<i class='fa fa-search'></i>"
5 weight = 960
6 identifier = "search"
7 url = "/search"
8[[menu.main]]
9 name = "文章"
10 pre = "<i class='fa fa-list'></i>"
11 weight = 970
12 identifier = "posts"
13 url = "/posts"
14[[menu.main]]
15 name = "专栏"
16 pre = "<i class='fa fa-book'></i>"
17 weight = 980
18 identifier = "series"
19 url = "/series"
20[[menu.main]]
21 name = "友链"
22 pre = "<i class='fa fa-link'></i>"
23 weight = 990
24 identifier = "links"
25 url = "/links"
26[[menu.main]]
27 name = "关于"
28 pre = "<i class='fa fa-info-circle'></i>"
29 weight = 1000
30 identifier = "about"
31 url = "/about"
图标 Font Awesome
在/layouts/partials/extend_head.html
中引入
1<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
搜索功能
执行hugo new search.md
创建搜索页面,修改front-matter标题
1---
2title: "搜索"
3---
同时必须配置以下内容,参考文档
1# 搜索功能
2[outputs]
3 home = ["HTML", "RSS", "JSON"]
侧边目录
参考Hugo博客目录放在侧边 | 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 {{- if (.Param "UseHugoToc") }}
13 {{- .TableOfContents -}}
14 {{- else }}
15 {{- $largest := 6 -}}
16 {{- range $headers -}}
17 {{- $headerLevel := index (findRE "[1-6]" . 1) 0 -}}
18 {{- $headerLevel := len (seq $headerLevel) -}}
19 {{- if lt $headerLevel $largest -}}
20 {{- $largest = $headerLevel -}}
21 {{- end -}}
22 {{- end -}}
23
24 {{- $firstHeaderLevel := len (seq (index (findRE "[1-6]" (index $headers 0) 1) 0)) -}}
25
26 {{- $.Scratch.Set "bareul" slice -}}
27 <ul>
28 {{- range seq (sub $firstHeaderLevel $largest) -}}
29 <ul>
30 {{- $.Scratch.Add "bareul" (sub (add $largest .) 1) -}}
31 {{- end -}}
32 {{- range $i, $header := $headers -}}
33 {{- $headerLevel := index (findRE "[1-6]" . 1) 0 -}}
34 {{- $headerLevel := len (seq $headerLevel) -}}
35
36 {{/* get id="xyz" */}}
37 {{- $id := index (findRE "(id=\"(.*?)\")" $header 9) 0 }}
38
39 {{- /* strip id="" to leave xyz, no way to get regex capturing groups in hugo */ -}}
40 {{- $cleanedID := replace (replace $id "id=\"" "") "\"" "" }}
41 {{- $header := replaceRE "<h[1-6].*?>((.|\n])+?)</h[1-6]>" "$1" $header -}}
42
43 {{- if ne $i 0 -}}
44 {{- $prevHeaderLevel := index (findRE "[1-6]" (index $headers (sub $i 1)) 1) 0 -}}
45 {{- $prevHeaderLevel := len (seq $prevHeaderLevel) -}}
46 {{- if gt $headerLevel $prevHeaderLevel -}}
47 {{- range seq $prevHeaderLevel (sub $headerLevel 1) -}}
48 <ul>
49 {{/* the first should not be recorded */}}
50 {{- if ne $prevHeaderLevel . -}}
51 {{- $.Scratch.Add "bareul" . -}}
52 {{- end -}}
53 {{- end -}}
54 {{- else -}}
55 </li>
56 {{- if lt $headerLevel $prevHeaderLevel -}}
57 {{- range seq (sub $prevHeaderLevel 1) -1 $headerLevel -}}
58 {{- if in ($.Scratch.Get "bareul") . -}}
59 </ul>
60 {{/* manually do pop item */}}
61 {{- $tmp := $.Scratch.Get "bareul" -}}
62 {{- $.Scratch.Delete "bareul" -}}
63 {{- $.Scratch.Set "bareul" slice}}
64 {{- range seq (sub (len $tmp) 1) -}}
65 {{- $.Scratch.Add "bareul" (index $tmp (sub . 1)) -}}
66 {{- end -}}
67 {{- else -}}
68 </ul>
69 </li>
70 {{- end -}}
71 {{- end -}}
72 {{- end -}}
73 {{- end }}
74 <li>
75 <a href="#{{- $cleanedID -}}" aria-label="{{- $header | plainify -}}">{{- $header | safeHTML -}}</a>
76 {{- else }}
77 <li>
78 <a href="#{{- $cleanedID -}}" aria-label="{{- $header | plainify -}}">{{- $header | safeHTML -}}</a>
79 {{- end -}}
80 {{- end -}}
81 <!-- {{- $firstHeaderLevel := len (seq (index (findRE "[1-6]" (index $headers 0) 1) 0)) -}} -->
82 {{- $firstHeaderLevel := $largest }}
83 {{- $lastHeaderLevel := len (seq (index (findRE "[1-6]" (index $headers (sub (len $headers) 1)) 1) 0)) }}
84 </li>
85 {{- range seq (sub $lastHeaderLevel $firstHeaderLevel) -}}
86 {{- if in ($.Scratch.Get "bareul") (add . $firstHeaderLevel) }}
87 </ul>
88 {{- else }}
89 </ul>
90 </li>
91 {{- end -}}
92 {{- end }}
93 </ul>
94 {{- end }}
95 </div>
96 </details>
97</div>
98</aside>
99<script>
100 let activeElement;
101 let elements;
102 window.addEventListener('DOMContentLoaded', function (event) {
103 checkTocPosition();
104
105 elements = document.querySelectorAll('h1[id],h2[id],h3[id],h4[id],h5[id],h6[id]');
106 // Make the first header active
107 activeElement = elements[0];
108 const id = encodeURI(activeElement.getAttribute('id')).toLowerCase();
109 document.querySelector(`.inner ul li a[href="#${id}"]`).classList.add('active');
110 }, false);
111
112 window.addEventListener('resize', function(event) {
113 checkTocPosition();
114 }, false);
115
116 window.addEventListener('scroll', () => {
117 // Check if there is an object in the top half of the screen or keep the last item active
118 activeElement = Array.from(elements).find((element) => {
119 if ((getOffsetTop(element) - window.pageYOffset) > 0 &&
120 (getOffsetTop(element) - window.pageYOffset) < window.innerHeight/2) {
121 return element;
122 }
123 }) || activeElement
124
125 elements.forEach(element => {
126 const id = encodeURI(element.getAttribute('id')).toLowerCase();
127 if (element === activeElement){
128 document.querySelector(`.inner ul li a[href="#${id}"]`).classList.add('active');
129 } else {
130 document.querySelector(`.inner ul li a[href="#${id}"]`).classList.remove('active');
131 }
132 })
133 }, false);
134
135 const main = parseInt(getComputedStyle(document.body).getPropertyValue('--article-width'), 10);
136 const toc = parseInt(getComputedStyle(document.body).getPropertyValue('--toc-width'), 10);
137 const gap = parseInt(getComputedStyle(document.body).getPropertyValue('--gap'), 10);
138
139 function checkTocPosition() {
140 const width = document.body.scrollWidth;
141
142 if (width - main - (toc * 2) - (gap * 4) > 0) {
143 document.getElementById("toc-container").classList.add("wide");
144 } else {
145 document.getElementById("toc-container").classList.remove("wide");
146 }
147 }
148
149 function getOffsetTop(element) {
150 if (!element.getClientRects().length) {
151 return 0;
152 }
153 let rect = element.getBoundingClientRect();
154 let win = element.ownerDocument.defaultView;
155 return rect.top + win.pageYOffset;
156 }
157</script>
158{{- end }}
在/assets/css/extended/blank.css
中配置
1:root {
2 --nav-width: 1380px;
3 --article-width: 720px;
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 /*滚动条*/
69 background: var(--border);
70 border: 7px solid var(--theme);
71 border-radius: var(--radius);
72}
73
74.toc li ul {
75 margin-inline-start: calc(var(--gap) * 0.5);
76 list-style-type: none;
77}
78
79.toc li {
80 list-style: none;
81 font-size: 0.95rem;
82 padding-bottom: 5px;
83}
84
85.toc li a:hover {
86 color: var(--secondary);
87}
Shortcode
缩写
新建/layouts/shortcodes/abbr.html
,内容如下:
1<abbr title="{{ .Get "title" }}">{{ .Get "text" }}</abbr>
使用方法(去掉'#'
):
1{#{< abbr title="达拉崩巴斑得贝迪卜多比鲁翁" text="达拉崩巴" >}}
折叠
新建/layouts/shortcodes/detail.html
,内容如下:
1<details>
2 <summary>{{ (.Get 0) | markdownify }}</summary>
3 {{ .Inner | markdownify }}
4</details>
使用方法(去掉'#'
):
1{#{< detail "点击展开" >}}
2 hello world!
3{#{< /detail >}}
点击展开
hello world!音乐
新建/layouts/shortcodes/music.html
,内容如下:
1<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/aplayer/dist/APlayer.min.css">
2<style type="text/css">.dark .aplayer .aplayer-body{background-color:#212121}.dark .aplayer .aplayer-info{border-top-color:#212121}.dark .aplayer.aplayer-withlist .aplayer-info{border-bottom-color:#5c5c5c}.dark .aplayer.aplayer-fixed .aplayer-list{border-color:#5c5c5c}.dark .aplayer .aplayer-info .aplayer-music .aplayer-author,.dark .aplayer .aplayer-info .aplayer-music .aplayer-title{color:#fff}.dark .aplayer .aplayer-info .aplayer-controller .aplayer-time{color:#eee}.dark .aplayer .aplayer-info .aplayer-controller .aplayer-time .aplayer-icon path{fill:#eee}.dark .aplayer .aplayer-list{background-color:#212121}.dark .aplayer .aplayer-list::-webkit-scrollbar-thumb{background-color:#999}.dark .aplayer .aplayer-list::-webkit-scrollbar-thumb:hover{background-color:#bbb}.dark .aplayer .aplayer-list li{color:#fff;border-top-color:#666}.dark .aplayer .aplayer-list li:hover{background:#4e4e4e}.dark .aplayer .aplayer-list li.aplayer-list-light{background:#6c6c6c}.dark .aplayer .aplayer-list li .aplayer-list-author,.dark .aplayer .aplayer-list li .aplayer-list-index{color:#ddd}.dark .aplayer .aplayer-lrc{text-shadow:-1px -1px 0 #666}.dark .aplayer .aplayer-lrc:before{background:-moz-linear-gradient(top,#212121 0,rgba(33,33,33,0) 100%);background:-webkit-linear-gradient(top,#212121,rgba(33,33,33,0));background:linear-gradient(180deg,#212121,rgba(33,33,33,0))}.dark .aplayer .aplayer-lrc:after{background:-moz-linear-gradient(top,rgba(33,33,33,0) 0,rgba(33,33,33,.8) 100%);background:-webkit-linear-gradient(top,rgba(33,33,33,0),rgba(33,33,33,.8));background:linear-gradient(180deg,rgba(33,33,33,0),rgba(33,33,33,.8))}.dark .aplayer .aplayer-lrc p{color:#fff}.dark .aplayer .aplayer-miniswitcher{background:#484848}.dark .aplayer .aplayer-miniswitcher .aplayer-icon path{fill:#eee}</style>
3<script src="https://cdn.jsdelivr.net/npm/aplayer/dist/APlayer.min.js"></script>
4<script src="https://cdn.jsdelivr.net/npm/meting@2.0.1/dist/Meting.min.js"></script>
5
6{{ if .IsNamedParams }}
7 <meting-js
8 id="{{ .Get "id" }}"
9 server="{{ .Get "server" }}"
10 type="{{ .Get "type" }}"
11 fixed="{{ if .Get "fixed" }}{{ .Get "fixed" }}{{ else }}false{{ end }}"
12 mini="{{ if .Get "mini" }}{{ .Get "mini" }}{{ else }}false{{ end }}"
13 autoplay="{{ if .Get "autoplay" }}{{ .Get "autoplay" }}{{ else }}false{{ end }}"
14 loop="{{ if .Get "loop" }}{{ .Get "loop" }}{{ else }}none{{ end }}"
15 theme="{{ if .Get "autoplay" }}{{ .Get "autoplay" }}{{ else }}#255579{{ end }}"
16 volume="{{ if .Get "volume" }}{{ .Get "volume" }}{{ else }}0.6{{ end }}"
17 prelosd="{{ if .Get "prelosd" }}{{ .Get "prelosd" }}{{ else }}auto{{ end }}"
18 mutex="{{ if .Get "mutex" }}{{ .Get "mutex" }}{{ else }}true{{ end }}"
19 list-folded="{{ if .Get "list-folded" }}{{ .Get "list-folded" }}{{ else }}true{{ end }}">
20 </meting-js>
21{{ end }}
使用方法(去掉'#'
):
1{#{< music id="560183743" type="song" server="netease" >}}
Bilibili
新建/layouts/shortcodes/bilibili.html
,内容如下:
1{{ $vid := (.Get 0) }}
2{{ $videopage := default 1 (.Get 1) }}
3{{ $basicQuery := querify "page" $videopage "high_quality" 1 "danmaku" 1 "as_wide" 1}}
4{{ $videoQuery := "" }}
5
6{{ if strings.HasPrefix (lower $vid) "av" }}
7 {{ $videoQuery = querify "aid" (strings.TrimPrefix "av" (lower $vid)) }}
8{{ else if strings.HasPrefix (lower $vid) "bv" }}
9 {{ $videoQuery = querify "bvid" $vid }}
10{{ else }}
11 <p>Bilibili 视频av号或BV号错误!</p>
12 <p>当前视频av或BV号:{{ $vid }}, 视频分P:{{ $videopage }}</p>
13{{ end }}
14
15<style type="text/css">
16 .video-wrapper {
17 position: relative;
18 overflow: hidden;
19 margin: auto;
20 padding-bottom: 66%;
21 width: 100%;
22 height: 0;
23 text-align: center
24 }
25
26 .video-wrapper iframe {
27 position: absolute;
28 top: 0;
29 left: 0;
30 width: 100%;
31 height: 100%
32 }
33</style>
34
35<div class="video-wrapper">
36 <iframe src="https://player.bilibili.com/player.html?{{ $basicQuery | safeURL }}&{{ $videoQuery | safeURL }}"
37 scrolling="no" frameborder="no" framespacing="0" allowfullscreen="true">
38 </iframe>
39</div>
使用方法(去掉'#'
)
1{#{< bilibili BV1pX4y1R7d6 >}}
代码块
代码高亮
Hugo使用Chroma
,PaperMod使用highlight.js
,本文采用Hugo自带方案。
1[markup.highlight]
2 codeFences = true # 代码框
3 guessSyntax = true # 猜测代码类型
4 lineNos = true # 显示行号
5 lineNumbersInTable = false # table分隔行号与代码
6 noClasses = true # 代码块style而非class
7 style = "monokai" # 配色方案
8
9[params.assets]
10 disableHLJS = true # 不使用highlight.js
代码复制
注意lineNumbersInTable
设置为true
时,长代码块的行号部分会出现多余的滚动条,并且不同步;设置为false
时,点击代码块的复制
按钮又会连行号一起复制。
本文处理后一种情况,关键在于通过F12
找出行号和代码部分的区别,然后修改复制
按钮的执行过程。
可以发现在配置中开启noClasses = true
之后(经过网友else提醒,还须关闭pygmentsUseClasses: false
),代码块中的<span>
标签都使用内嵌的style
样式,但是代码部分的顶层会有一个不包含样式的<span>
。
所以在/layouts/partials/footer.html
中,找到对于复制按钮点击事件的监听,修改if里面的内容如下:
1 copybutton.addEventListener('click', (cb) => {
2 if ('clipboard' in navigator) {
3 // 不包含样式的span的内容拼接起来,就是代码块的内容
4 let x = codeblock.getElementsByTagName("span");
5 let noLineNumContent = "";
6 for (i = 0; i < x.length; i++) {
7 if (!x[i].style.display && !x[i].style.color)
8 noLineNumContent += x[i].textContent;
9 }
10 navigator.clipboard.writeText(noLineNumContent);
11 copyingDone();
12 return;
13 }
14 ...
代码块折叠
目前方案比较简陋,抄一个和复制
相同样式的展开
按钮,用于控制代码块的最大高度,实现一种“不完全展开”的折叠。
在layouts\partials\footer.html
中添加代码块的折叠按钮:
1<script>
2 document.querySelectorAll('pre > code').forEach((codeblock) => {
3 const container = codeblock.parentNode.parentNode;
4
5 const unfoldbtn = document.createElement('button');
6 unfoldbtn.classList.add('unfoldbtn');
7 unfoldbtn.innerHTML = '展开';
8
9 unfoldbtn.addEventListener('click', (cb) => {
10 if (container.firstChild.firstChild.classList.contains('unfold')) {
11 container.firstChild.firstChild.classList.remove('unfold');
12 unfoldbtn.innerHTML = '展开';
13 } else {
14 container.firstChild.firstChild.classList.add('unfold');
15 unfoldbtn.innerHTML = '折叠';
16 }
17 });
18
19 if (container.classList.contains("highlight")) {
20 container.appendChild(unfoldbtn);
21 }
22 });
23</script>
在/assets/css/extended/blank.css
中配置:
1.highlight:hover {
2 .unfoldbtn {
3 display: block;
4 }
5}
6
7.unfoldbtn {
8 display: none;
9 position: absolute;
10 top: 4px;
11 right: 18px;
12 color: rgba(255, 255, 255, .8);
13 background: rgba(78, 78, 78, .8);
14 border-radius: var(--radius);
15 padding: 0 5px;
16 font-size: 14px;
17 user-select: none;
18}
19
20code {
21 max-height: 200px; /* 折叠后最大高度 */
22}
23
24.unfold {
25 max-height: none;
26}
27
28.copy-code {
29 right: 58px;
30}
数学公式 KaTeX
PaperMod未整合,但文档中提到了做法。
新建/layouts/partials/math.html
,复制粘贴KaTeX提供的自动渲染模板:
1<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.css" integrity="sha384-GvrOXuhMATgEsSwCs4smul74iXGOixntILdUW9XmUC6+HX0sLNAK3q71HotJqlAn" crossorigin="anonymous">
2<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.js" integrity="sha384-cpW21h6RZv/phavutF+AuVYrr+dA8xD9zs6FwLpaCct6O9ctzYFfFr4dgmgccOTx" crossorigin="anonymous"></script>
3<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/contrib/auto-render.min.js" integrity="sha384-+VBxd3r6XgURycqtZ117nYw44OOcIax56Z4dCRWbxyPt0Koah1uHoK0o4+/RRE05" crossorigin="anonymous"></script>
4<script>
5 document.addEventListener("DOMContentLoaded", function() {
6 renderMathInElement(document.body, {
7 // customised options
8 // • auto-render specific keys, e.g.:
9 delimiters: [
10 {left: '$$', right: '$$', display: true},
11 {left: '$', right: '$', display: false},
12 {left: '\\(', right: '\\)', display: false},
13 {left: '\\[', right: '\\]', display: true}
14 ],
15 // • rendering keys, e.g.:
16 throwOnError : false
17 });
18 });
19</script>
然后在/layouts/partials/extend_head.html
中添加:
1<!-- KaTeX -->
2{{ if or .Params.math .Site.Params.math }}
3{{ partial "math.html" . }}
4{{ end }}
在需要开启LaTeX渲染的文章front-matter中添加:
1math: true
评论系统 Twikoo
本文使用Twikoo作为评论系统,后台的部署参考Twikoo文档,或按照视频教程一步步完成即可。
前端部分参考Hugo博客添加Twikoo评论。新建/layouts/partials/comments.html
,添加以下内容:
1<div>
2 <div class="pagination__title">
3 <span class="pagination__title-h" style="font-size: 20px;">💬评论</span>
4 <hr />
5 </div>
6 <div id="tcomment"></div>
7 <script src="https://cdn.staticfile.org/twikoo/{{ .Site.Params.twikoo.version }}/twikoo.all.min.js"></script>
8 <script>
9 twikoo.init({
10 envId: "", // 这里填写自己的envId
11 el: "#tcomment",
12 lang: 'zh-CN',
13 region: 'ap-shanghai',
14 path: window.TWIKOO_MAGIC_PATH||window.location.pathname,
15 });
16 </script>
17</div>
添加配置
1# 评论
2[params.twikoo]
3 version = "1.6.16" # 这个版本号要自己手动修改,和twikoo的版本号要对得上
访客统计 busuanzi
访客统计采用不蒜子,参考Hugo添加不蒜子Busuanzi站点访问量与阅读量统计,在/layouts/partials/extend_head.html
中引入:
1<!-- busuanzi -->
2{{- if .Site.Params.busuanzi.enable -}}
3 <script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
4 <meta name="referrer" content="no-referrer-when-downgrade">
5{{- end -}}
站点底部显示总访问量与访客数,修改/layouts/partials/footer.html
,在footer标签中添加:
1<!-- busuanzi -->
2{{ if .Site.Params.busuanzi.enable -}}
3<span id="busuanzi_container_site_pv">
4 <i class="fa fa-eye"></i><span id="busuanzi_value_site_pv"></span>
5</span>
6<span id="busuanzi_container_site_uv">
7 <i class="fa fa-user"></i><span id="busuanzi_value_site_uv"></span>
8</span>
9{{- end -}}
每篇文章阅读量,在PaperMod主题中,修改/layouts/partials/post_meta.html
,在末尾添加:
1<!-- busuanzi -->
2{{ if .Site.Params.busuanzi.enable -}}
3 ·
4 <span id="busuanzi_container_page_pv">本文阅读量<span id="busuanzi_value_page_pv"></span>次</span>
5{{- end }}
最后在站点配置中添加:
1# 访问统计
2[params.busuanzi]
3 enable = true
部署
以Vercel为例,部署方式有至少两种。一种是上传博客源码,部署时选择Hugo,在线生成网页文件并部署;另一种则是本地生成网页文件,部署时选择Other,也就是GitHub Pages的方式。本文采用后者,具体过程不再赘述。
CSS加载失败
使用 hugo server
启动本地调试服务,访问http://localhost:1313/
时看起来很正常,推送到GitHub上,使用GitHub Pages或Vercel部署的页面却加载不出CSS。
按下F12
,切换到控制台,可以看到提示如下。大意是CSS文件的SHA-256校验失败,所以无法加载。
1Failed to find a valid digest in the 'integrity' attribute for resource 'https://blog-d1lytax8a-lordash.vercel.app/assets/css/stylesheet.94301bb9792e5b60c04e4187a47605d05c85a2062102b81ada42fe7d0cd0aec1.css' with computed SHA-256 integrity 'DtzRH2bXNjGH5kpyxdinsAaB3zwGHAorYyxEe0JoY9I='. The resource has been blocked.
简单搜索下,有说可能的原因是Cloudflare的速度/优化/Auto Minify
功能改动文件导致;也有直接修改/layouts/partials/head.html
生成过程,去掉integrity
的。
以上两种情况,在折腾 Hugo & PaperMod 主题找到了好的方法:
- Cloudflare 关闭的方法:速度 - 优化 - Auto Minify。
- 在 Hugo 中关闭的方法:
1[params.assets] 2 disableFingerprinting = true
本文未做以上修改。在对本地及GitHub上的CSS文件进行SHA-256校验时,发现提交GitHub后的文件就已经不一致了,可以猜测是Git提交时有改动,此时,很容易就联想到行尾序列(行结束符)的问题。
假设你在Windows上使用Git上传代码,Git会在你提交时自动的把行结束符CRLF
转化成LF
,而在拉取代码时把LF
转化成CRLF
。查看Git配置:
1git config --global -l
关闭自动转换行尾序列功能
1git config --global core.autocrlf false
重新提交网页文件(最好是先删除),再次部署即可。