使用Hugo框架搭建博客的过程 - 功能拓展
前言
本文介绍一些拓展功能,如文章页面功能增加二级菜单,相关文章推荐和赞赏。另外,使用脚本会大大简化写作后的上传流程。
文章页面功能
这部分功能的拓展主要是用前端的JS和CSS,如果对前端不了解,可以参考放在Github上的网站源码。
二级菜单
导航栏放都太多链接不分主次,不够简洁。尝试多种方案后,做出了现在的二级菜单。
- 使用bootstrap框架
引入了太多样式,界面被扰乱,不够友好。 - Select标签
样式单一,不能修改。 - Hover属性
多个二级菜单不能使用相同参数的绝对定位,拓展太麻烦。
最终方案是:引入Jquery,使用Jquery的slideToggle()方法。可拓展,不影响原有界面。步骤如下。
- 调整下拉菜单的样式
/* dropdown menus css*/
.dropdown {
position: relative;
display: inline;
margin: 0px;
}
.dropdown-menu {
position: absolute;
left: 0;
z-index: 1000;
float: left;
min-width: auto;
padding: 2px 1px;
font-size: 14px;
text-align: left;
list-style: none;
background-color: #fff;
-webkit-background-clip: padding-box;
background-clip: padding-box;
border: 1px solid #ccc;
border-radius: 4px;
-webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175);
box-shadow: 0 6px 12px rgba(0, 0, 0, .175);
}
.dropdown-menu-mobile {
width: 100%;
position: relative;
background: transparent;
text-align: center;
}
.dropdown-menu.show {
display: block;
}
.dropdown-item {
display: block;
margin: .4rem 0.5rem;
clear: both;
font-weight: 400;
color: #212529;
text-align: inherit;
white-space: nowrap;
background-color: transparent;
line-height: 1.5rem;
}
.btn {
vertical-align: inherit;
font-weight: 400;
color: #212529;
text-align: center;
-webkit-user-select: none;
user-select: none;
background-color: transparent;
font-size: 1rem;
border-radius: .25rem;
}
/* dropdown menus css*/
- 调用JS
$(".dropdown").each(function() {
$(this).on("click", function(e) {
// 收起其他菜单
if (isMobile()) {
$(".menu").find(".dropdown-menu").not($(this).children("div")).slideUp("fast");
} else {
$(".menu-inner").find(".dropdown-menu").not($(this).children("div")).slideUp("fast");
}
e.stopPropagation();
var downmenu = $(this).children("div");
// 展开菜单
downmenu.slideToggle("fast");
// 点击其他地方,隐藏菜单
if (downmenu.is(":visible")) {
$(document).one("click", function() {
downmenu.slideUp("fast");
});
}
});
});
# 移动端
function isMobile(){
return window.matchMedia("only screen and (max-width: 680px)").matches;
}
- 修改菜单模板,详细代码参考header.html
<div class="menu" style="overflow: visible">
<div class="menu-inner">
{{- range .Site.Menus.main -}}
{{- /* MultiMenus */ -}}
{{ if .HasChildren }}
<div class="dropdown menu-item" style="display: inline;">
<a class="btn" href="javascript:void(0);" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">{{- .Pre | safeHTML }}{{ .Name }}{{ .Post | safeHTML -}}</a>
<div class="dropdown-menu" style="display: none;">
{{ range .Children }}
{{- $url := .URL | relLangURL -}}
{{- with .Page -}}
{{- $url = .RelPermalink -}}
{{- end -}}
<a class="dropdown-item" href="{{ $url }}" {{ if (urls.Parse $url).Host }} rel="noopener noreffer" target="_blank"{{ end }}>{{- .Pre | safeHTML }} {{ .Name }} {{ .Post | safeHTML -}}</a>
{{ end }}
</div>
</div>
{{ else }}
{{- $url := .URL | relLangURL -}}
{{- with .Page -}}
{{- $url = .RelPermalink -}}
{{- end -}}
<a class="menu-item{{ if $.IsMenuCurrent `main` . | or ($.HasMenuCurrent `main` .) | or (eq $.RelPermalink $url) }} active {{ end }}" href="{{ $url }}"{{ with .Title }} title="{{ . }}"{{ end }}{{ if (urls.Parse $url).Host }} rel="noopener noreffer" target="_blank"{{ end }}> {{- .Pre | safeHTML }}{{ .Name }}{{ .Post | safeHTML -}}</a>
{{ end }}
{{- end -}}
{{- /* Mobile header */ -}}
{{- range .Site.Menus.main -}}
{{- /* MultiMenus */ -}}
{{ if .HasChildren }}
<div class="dropdown menu-item">
<a class="btn" href="javascript:void(0);" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">{{- .Pre | safeHTML }}{{ .Name }}{{ .Post | safeHTML -}}</a>
<div class="dropdown-menu dropdown-menu-mobile" style="display: none">
{{ range .Children }}
{{- $url := .URL | relLangURL -}}
{{- with .Page -}}
{{- $url = .RelPermalink -}}
{{- end -}}
<a class="dropdown-item" href="{{ $url }}" {{ if (urls.Parse $url).Host }} rel="noopener noreffer" target="_blank"{{ end }}>{{- .Pre | safeHTML }} {{ .Name }} {{ .Post | safeHTML -}}</a>
{{ end }}
</div>
</div>
{{ else }}
{{- $url := .URL | relLangURL -}}
{{- with .Page -}}
{{- $url = .RelPermalink -}}
{{- end -}}
<a class="menu-item" href="{{ $url }}" title="{{ .Title }}"{{ if (urls.Parse $url).Host }} rel="noopener noreffer" target="_blank"{{ end }}>
{{- .Pre | safeHTML }}{{ .Name }}{{ .Post | safeHTML -}}
</a>
{{ end }}
{{- end -}}
- 菜单配置,子菜单设置
parent
属性指向父菜单。
[languages.en.menu]
[[languages.en.menu.main]]
identifier = "home"
pre = "<i class='fas fa-home fa-fw'></i>"
post = ""
name = "Home"
url = ""
title = "Home"
weight = 1
[[languages.en.menu.main]]
identifier = "posts"
pre = "<i class='fas fa-archive fa-fw'></i>"
post = ""
name = "Posts"
url = "posts/"
title = "all articles"
weight = 2
[[languages.en.menu.main]]
identifier = "memory"
pre = "<i class='fas fa-database fa-fw'></i>"
post = ""
name = "Memory"
url = "memories/"
title = "left it"
weight = 3
[[languages.en.menu.main]]
identifier = "tags"
pre = "<i class='fas fa-tags fa-fw'></i>"
post = ""
name = " Tags"
url = "tags/"
title = ""
weight = 4
[[languages.en.menu.main]]
identifier = "categories"
pre = "<i class='fas fa-th-list fa-fw'></i>"
post = ""
name = "Categories"
url = "categories/"
title = ""
weight = 5
[[languages.en.menu.main]]
identifier = "share"
pre = "<i class='fas fa-fan fa-fw'></i>"
post = ""
name = "Share"
title = ""
weight = 7
[[languages.en.menu.main]]
identifier = "books"
pre = "<i class='fas fa-file-alt fa-fw'></i>"
post = ""
name = "Book list"
url = "booklist/"
title = ""
weight = 8
parent = "share"
[[languages.en.menu.main]]
identifier = "websites"
pre = "<i class='fas fa-globe fa-fw'></i>"
post = ""
name = "Websites"
url = "websites/"
title = ""
weight = 8
parent = "share"
[[languages.en.menu.main]]
identifier = "internal"
pre = "<i class='fas fa-door-closed fa-fw'></i>"
post = ""
name = "Doors"
title = ""
weight = 8
[[languages.en.menu.main]]
identifier = "message-board"
pre = "<i class='fas fa-comments fa-fw'></i>"
post = ""
name = "Message Board"
url = "message-board/"
title = ""
weight = 8
parent = "internal"
[[languages.en.menu.main]]
identifier = "favorites"
pre = "<i class='fas fa-star fa-fw'></i>"
post = ""
name = "Favorites"
url = "favorites/"
title = ""
weight = 8
parent = "internal"
[[languages.en.menu.main]]
identifier = "milestone"
pre = "<i class='fas fa-monument fa-fw'></i>"
post = ""
name = "Milestone"
url = "milestone/"
title = ""
weight = 8
parent = "internal"
[[languages.en.menu.main]]
identifier = "links"
pre = "<i class='fas fa-user-friends fa-fw'></i>"
post = ""
name = "Links"
url = "links/"
title = ""
weight = 9
parent = "internal"
[[languages.en.menu.main]]
identifier = "about"
pre = "<i class='fas fa-user-secret fa-fw'></i>"
post = ""
name = "About"
url = "about/"
title = ""
weight = 10
parent = "internal"
相关文章推荐
参考Related Content
themes\LoveIt\layouts\posts\single.html
添加以下代码:
{{- /*see also*/ -}}
# 显示tag分类相关的前5篇文章
{{ $related := .Site.RegularPages.RelatedIndices . "tags" | first 5 }}
{{ with $related }}
<div id="related-article">
<p>{{- T "seeAlso" -}}</p>
<ul>
{{ range . }}
<li><a href="{{ .RelPermalink }}">{{ .Title }}</a></li>
{{ end }}
</ul>
</div>
{{ end }}
赞赏
赞赏功能需要在data\imgURL.toml
中添加图片的URL。
alipay = "https://img.xiaodejiyi.com/img/alipay.jpg"
weichatPay = "https://img.xiaodejiyi.com/img/wechat%20pay.jpg"
然后在模板文件themes\LoveIt\layouts\posts\single.html
添加以下代码:
{{- /* sponsor */ -}}
<div style="text-align:center;margin-bottom:30px;">
<h5 style="font-weight:600;margin-bottom:10px;">「 {{- T "thanksSponsor" -}} 」</h5>
<button id="rewardButton"><span>{{- T "tips" -}}</span></button>
<div id="QR" style="display: none;">
<div id="wechat" style="display:inline-block">
<a class="fancybox" rel="group">
<img id="wechat_qr" src="{{ .Site.Data.imgURL.weichatPay }}" alt="WeChat Pay"></a>
<h5 style="font-weight:600;margin-top:5px;">{{- T "weichatTip" -}}</h5>
</div>
<div id="alipay" style="display: inline-block">
<a class="fancybox" rel="group">
<img id="alipay_qr" src="{{ .Site.Data.imgURL.alipay }}" alt="Alipay"></a>
<h5 style="font-weight:600;margin-top:5px;">{{- T "aliTip" -}}</h5>
</div>
</div>
</div>
T和i18n函数是翻译函数,按照不同的语言,使用对应语言的字符串。参考i18n
版权声明
themes\LoveIt\layouts\posts\single.html
添加以下代码:
{{- /*copyright*/ -}}
<div id="copyright-container">
<ul class="post-copyright">
<li class="post-copyright-author">
<strong>{{- T "articleAuthor" -}}:</strong><a href="{{ $.Site.Author.link | default .Site.Home.RelPermalink }}" target="_blank">{{ T "penname" }}</a>
</li>
<li class="post-copyright-link">
<strong>{{- T "articleLink" -}}:</strong><a href="#" target="_blank" title="{{ .Title }}">{{- .Permalink | safeHTML -}}</a>
</li>
<li class="post-copyright-license">
<strong>{{- T "copyRight" -}}:</strong>
{{- $prestr := printf `<a href="%v" target="_blank" title="CC BY-NC-ND 4.0">%v</a>` .Site.Params.footer.license ( T "license" ) -}}
{{- $laststr := printf `<a href="%v" target="_blank">%v</a>` ($.Site.Author.link | default .Site.Home.RelPermalink) ( T "penname" ) -}}
{{- dict "preCopyRight" $prestr "afterCopyRight" $laststr | T "copyRightMsg" | safeHTML }}
</li>
</ul>
</div>
需要使用T函数翻译并且根据不同语言传递参数。
[copyRightMsg]
other = "本站所有文章除特别声明外,均采用 {{ .preCopyRight }} 转载请注明来自 {{ .afterCopyRight }} "
.Site.Params.footer.license
在config.toml中设置:license = 'https://creativecommons.org/licenses/by-nc-sa/4.0/'
同步文章到服务器
如果需要了解同步文章到对象存储平台,请参考Hugo 静态网站部署及免费 HTTPS 证书配置和OSS常用工具汇总。
文章最后的“阅读全文”可以绕过,方法是在Chrome控制台的Network中找到readmore.js
,右键Block request URL
。
文章同步到服务器的步骤如下。
- 本地创建博客文章
- 配置SSH免密登陆
- 使用Python脚本生成网站静态资源,提交变更
- Github Action使用rsync同步网站静态资源到服务器,并使用atomic-algolia同步
index.json
到Algolia。
创建文章
在archetypes
目录下的创建模板文件,使用hugo new --kind post-bundle posts/my-post
生成指定模板的样例文章,不需要每次复制文章头部参数。
archetypes\post-bundle\index.md
文件内容。
SSH免密登陆
注意!服务器端要配置authorized_keys。参考设置 SSH 通过密钥登录
注册Algolia
使用Algolia搜索引擎为博客提供搜索功能。参考搜索配置
本地同步与备份脚本
在博客目录下执行该脚本,先推送blog到Github的blog
仓库,再本地备份。(相信看到这里的读者都有Github帐号了)
import time
import os, sys
def main(msg):
# 备份blog/
print("*"*10+"push blog/"+"*"*10, end="\n\n")
# 生成静态页面
os.system('hugo')
# 需要安装Git
os.system('git add .')
os.system('git commit -m "{}"'.format(msg))
os.system('git push')
local_back = input('\n本地备份?提示: y\n')
if local_back == 'y':
#7z、winrar压缩参数是一样的,只有winrar的话,7z换成winrar也可以运行
os.system(r'7z a D:\src\xxxxx.rar D:\src\xxxxx')
os.system(r'move D:\src\code\xxxxx.rar D:\backup\xxxxx')
print('本地备份完成!')
print('over...')
if __name__ == '__main__':
msg = input('commit: ')
main(msg)
如果需要安装Git,试试Scoop,它用来安装应用程序非常方便。
Github Action
blog/文件根目录下创建package.json
文件,并在scripts模块中加入"algolia": "atomic-algolia"
。
{
"name": "algolia",
"version": "0.2.0",
"description": "atomic-algolia package",
"private": true,
"scripts": {
"algolia": "atomic-algolia"
}
}
在Github的Blog
仓库中添加Secrets
变量
同步index.json
到Algolia需要配置如下变量:
- ALGOLIA_ADMIN_KEY
- ALGOLIA_APP_ID
- ALGOLIA_INDEX_NAME
rsync同步需要配置如下变量
- HOST
- PORT
- USER
- REMOTE_PATH
Nginx中配置的网站根目录 - SSH_KEY
使用Github Action,创建workflow,复制以下代码。
name: deploy_blog
on:
push:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Check Out
uses: actions/checkout@v2
#同步blog搜索
- name: Use Node.js
uses: actions/setup-node@v1
with:
node-version: '12.x'
- name: Install automic-algolia
env:
ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }}
ALGOLIA_ADMIN_KEY: ${{ secrets.ALGOLIA_ADMIN_KEY }}
ALGOLIA_INDEX_NAME: zh-cn
ALGOLIA_INDEX_FILE: "./public/index.json"
run: |
npm install atomic-algolia
npm run algolia
# 同步英语文章的json
- name: en json
env:
ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }}
ALGOLIA_ADMIN_KEY: ${{ secrets.ALGOLIA_ADMIN_KEY }}
ALGOLIA_INDEX_NAME: en
ALGOLIA_INDEX_FILE: "./public/en/index.json"
run: |
npm run algolia
- name: deploy to server
uses: AEnterprise/rsync-deploy@v1.0
env:
DEPLOY_KEY: ${{ secrets.SSH_KEY }}
# avzr参数,增量备份本地文件。-delete 删除目标地址中本地没有的文件
ARGS: "-avzr --delete"
SERVER_PORT: ${{ secrets.PORT }}
FOLDER: "./public/"
SERVER_IP: ${{ secrets.HOST }}
USERNAME: ${{ secrets.USER }}
SERVER_DESTINATION: ${{ secrets.REMOTE_PATH }}
如果出现问题,先在本地创建linux虚拟机,测试rsync
命令,确定服务器同步是否正常。
代码改变世界,现在,写作多么简单。