Hugo建站全指南 | 我的第一个博客网站
前言
建博客的初衷?
2020年八月的第一天,我还是像往常一样打开我的域名,本以为还是会像以前一样显示每日一图
的界面,结果出现的却是破图,当即开始打开服务器进行一番检查,发现是一直在使用的词霸API接口
挂掉了(结果两天后又恢复正常了囧),这样一来就意味着我的域名现在闲下来了😢 这时候,一个想法闪过我的脑海 ————
是时候开始做自己的个人博客网站了!
在这之前,由于平时比较忙,并且搭建博客需要投入一定的精力,加上已经有现成的博客园平台可以供自己记录博客,因此,考虑到时间成本
以及学习成本
,就一直搁置了博客网站这件事。
why hugo?
Hugo是由 Steve Francis 大神(http://spf13.com/)基于Go
语言开发的静态网站构建工具。通过 Hugo 你可以快速搭建你的静态网站,比如博客系统、文档介绍、公司主页、产品介绍等等。相对于其他静态网站生成器(例如hexo、jekyll)来说,Hugo 具备如下特点:
- 极快的页面编译生成速度。( ~1 ms 每页面)
- 完全跨平台支持,可以运行在 Mac OS X, Linux, Windows, 以及更多!
- 安装方便 Installation
- 本地调试 Usage 时通过 LiveReload 自动即时刷新页面。
- 完全的皮肤支持。
- 可以部署在任何的支持 HTTP 的服务器上。
hugo的基本使用
基本环境 | go、git、github |
安装方法 | 官网下载对应版本的源码,添加到环境变量 |
生成博客 | hugo new site 博客名 |
下载主题 | git clone … |
设置主题 | echo ’theme = “cactus”’ » config.toml |
启动博客 | hugo server -w |
新建文章 | hugo new post/文章名.md |
生成public 目录 |
hugo -D |
快速部署基本设置
博客的基本设置可以通过对应主题的GitHub帮助文档进行一步步设置,也可以走捷径,直接替换博客项目下的config.toml
为主题文件中exampleSite下的config.toml
文件,接着在config.toml中的对应位置修改自己的设置:
baseURL = "https://chenxuefan.cn"
languageCode = "en-us"
title = "人人都爱小雀斑's blogs"
theme = "cactus"
copyright = "billie" # cactus will use title if copyright is not set
disqusShortname = "example" # Used when comments is enabled. Cactus will use site title if not set
# googleAnalytics = "UA-1234-5"
文章添加标签和分类
以你现在看到的这篇文章为例,新建一篇文章之后,如果想加入标签,可以在每篇文章的页首这样设置:
title: "hugo建站 | 我的第一个博客网站"
date: 2020-08-04T18:42:17+08:00
categories:
- tec
tags:
- hugo
ps:注意-
后面有一个空格
页脚信息的编辑
以我现在使用的cactus主题为例,页脚配置文件位于:cactus/layouts/partials/footer.html
,编辑如下:
<footer id="footer">
<div class="footer-left">
Copyright
©
{{ now.Format "2006" }}
<span>❤️{{ if .Site.Copyright }} {{ print .Site.Copyright }} {{ else }} {{ print .Site.Title }} {{ end }} | </span>
<span>粤ICP备20025795号-1 | </span>
<span>Powered By Hugo | </span>
<span id="busuanzi_container_site_pv">
pv:<span id="busuanzi_value_site_pv"></span> |
</span>
<span id="busuanzi_container_site_uv">
uv: <span id="busuanzi_value_site_uv"></span>
</span>
</div>
</footer>
网站流量统计
工具:不蒜子,一个通过仅仅两行代码实现的网页流量计数器
1、在/cactus/layouts/partials/head.html
文件中引入不蒜子js文件
<!-- 不蒜子 -->
<script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
2、在页面添加统计代码,在/cactus/layouts/partials/footer.html
中添加如下代码
<span id="busuanzi_container_site_pv">
本站访问量:<span id="busuanzi_value_site_pv"></span>次
</span>
<span id="busuanzi_container_site_uv">
您是本站第 <span id="busuanzi_value_site_uv"></span> 位访问者
</span>
3、设置文章阅读数,在/cactus/layouts/article/single.html
中,大概35行的位置,添加如下代码
<div class="article-tag">
<i class="fa fa-eye"></i>
<span id="busuanzi_container_page_pv">
<span id="busuanzi_value_page_pv">0</span>
</span>
</div>
具体显示效果可移步此文章页首标题处查看
4、设置文章字数和阅读时间,在/cactus/layouts/_default/single.html
中添加以下代码
<h5 id="wc" style="font-size: 1rem;text-align: center;">{{ .FuzzyWordCount }} Words|Read in about {{ .ReadingTime }} Min|本文总阅读量<span id="busuanzi_value_page_pv"></span>次</h5>
首页添加每日一图
python+hugo+nginx | 实现博客主页每日一图
添加评论系统Valine
Valine - 一款快速、简洁且高效的无后端评论系统。
Valine 诞生于2017年8月7日,是一款基于Leancloud的快速、简洁且高效的无后端评论系统。
理论上支持但不限于静态博客,目前已有Hexo、Jekyll、Typecho等博客程序在使用Valine。
特性:
- 快速
- 安全
- Emoji 😉
- 无后端实现
- MarkDown 全语法支持
- 轻量易用(~15kb gzipped)
- 文章阅读量统计 v1.2.0-beta1+
Tips:
- 整个过程,是以cactus主题为例的,其它主题操作大同小异。
- 配置之前应该先阅读Valine快速开始
Leancloud相关配置:
评论系统依赖于leancloud,所以需要先在leancloud中进行相关的准备工作。
- 登录 或 注册 LeanCloud
- 登录成功后,进入后台点击左上角的创建应用
- 创建好应用,进入应用,左边栏找到 设置 ,然后点击 应用Key,此时记录出现的 App ID 和 App Key,后面配置文件中会用到
- 因为评论和文章阅读数统计依赖于存储,所以还需要建立两个新的存储
Class
,左边栏找到并点击 存储,点击 创建Class - 创建两个存储Class,分别命名为:
Counter
和Comment
- 为应用添加安全域名,左边栏点击 设置,找到 安全中心,点击后会看到 安全域名 设置框,输入博客使用的域名,点击保存即可
添加 Valine 参数项:
# Valine.
# You can get your appid and appkey from https://leancloud.cn
# more info please open https://valine.js.org
[params.valine]
enable = true
appId = '你的appId'
appKey = '你的appKey'
notify = false # mail notifier , https://github.com/xCss/Valine/wiki
verify = false # Verification code
avatar = 'mm'
placeholder = '说点什么吧...'
visitor = true
上面几项内容的含义,这里简单一说,具体还是要看 Valine官网中配置相关的内容:
参数 | 用途 |
---|---|
enable | 这是用于主题中配置的,不是官方Valine的参数,true时控制开启此评论系统 |
appId | 这是在 leancloud 后台应用中获取的,也就是上面提到的 App ID |
appKey | 这是在 leancloud 后台应用中获取的,也就是上面提到的 App Key |
notify | 用于控制是否开启邮件通知功能,具体参考邮件提醒配置 |
verify | 用于控制是否开启评论验证码功能 |
avatar | 用于配置评论项中用户头像样式,有多种选择:mm, identicon, monsterid, wavatar, retro, hide。详细参考:头像配置 |
placehoder | 评论框的提示符 |
visitor | 控制是否开启文章阅读数的统计功能i, 详情阅读文章阅读数统计 |
修改主题文件:
主要是修改主题中评论相关的布局文件 themes/even/layouts/partials/comments.html
,按照 Valine快速开始 添加 Valine 相关代码,找到以下位置,大概55~81行的位置:
<!-- gitment -->
{{- if .Site.Params.gitment.enable -}}
<div id="comments-gitment"></div>
<!--这里省略了部分代码-->
<noscript>Please enable JavaScript to view the <a href="https://github.com/imsun/gitment">comments powered by gitment.</a></noscript>
{{- end }}
<!--这个位置添加Valine相关代码-->
添加的 Valine 评论的代码如下:
<!-- valine -->
{{- if .Site.Params.valine.enable -}}
<!-- id 将作为查询条件 -->
<span id="{{ .URL | relURL }}" class="leancloud_visitors" data-flag-title="{{ .Title }}">
<span class="post-meta-item-text">文章阅读量 </span>
<span class="leancloud-visitors-count">1000000</span>
<p></p>
</span>
<div id="vcomments"></div>
<script src="//cdn1.lncld.net/static/js/3.0.4/av-min.js"></script>
<script src='//unpkg.com/valine/dist/Valine.min.js'></script>
<script type="text/javascript">
new Valine({
el: '#vcomments' ,
appId: '{{ .Site.Params.valine.appId }}',
appKey: '{{ .Site.Params.valine.appKey }}',
notify: {{ .Site.Params.valine.notify }},
verify: {{ .Site.Params.valine.verify }},
avatar:'{{ .Site.Params.valine.avatar }}',
placeholder: '{{ .Site.Params.valine.placeholder }}',
visitor: {{ .Site.Params.valine.visitor }}
});
</script>
{{- end }}
可以看到上述代码中引用了配置文件中的相关参数,这样以后修改配置就不用修改代码了,只需要改配置文件 config.toml
,另外注意到的是,我也添加了文章阅读数统计的显示内容。将配置文件中 valine 配置的 eanble
设置为 true
后台管理评论信息
难免会遇到有一些垃圾评论或自动程序产生的无效评论,这时候需要删除或编辑
访问leancloud:https://console.leancloud.cn/
数据存储、结构化数据、Comment
添加安全域名:
上传代码到GitHub
上传代码这部分我选择的方案是把相关git代码写进一个可执行文件下,然后后面更新博客的话运行这个可执行文件就行了,步骤如下:
- 在GitHub创建一个仓库,并命名为
your_github_id.github.io
,注意your_github_id
一定要小写 - 在博客项目目录下新建文件
deploy.sh
- 打开
deploy.sh
并编辑
hugo -D #打包成静态文件目录public
cd public #切换到public目录
git init
git remote rm origin
git remote add origin https://github.com/your_github_id/your_github_id.github.io.git #链接到你的GitHub博客项目
git add .
git commit -m "update"
echo "Pushing to github"
git pull origin master
git push -u origin +master
为你的博客添加一个域名(GithubPages)
上传代码到GitHub之后,在GitHub项目下新建一个’CNAME’文件,打开并编辑,在第一行写入你的域名
(所以你要先有一个域名,并备案),比如我的就是chenxuefan.cn
,这一步目的是做一个域名的关联,当你访问你的域名的时候,就会关联到你的博客项目来。
接着,以阿里云为例,在这之前,你的域名已经备案好了,登录阿里云,进入你的域名列表,在域名右侧点击解析
,点击添加安全组
,新手引导
,在记录值
输入你的博客项目GitHub pages的IP地址,IP地址可以通过终端输入ping your_github_id.github.io
获取。
添加字数统计
作为一个静态网页生成器,Hugo 为使用者提供了很多与网页相关的模板变量,而与文章字数相关的模板变量有两个:
.FuzzyWordCount
: 文章内容的大致单词数 (字数).WordCount
: 文章内容的单词数 (字数)
我们可以看到 .FuzzyWordCount
提供的是一个大概的值 (整 100),比如 1 个字算 100 字,2 个字还算 100 字,201 个字算 200 字。这样统计出来的字数可能会比实际情况更多一点,虽然更有牌面但全都是整数未免也太假了。所以我还是决定使用 .WordCount
。
参考用法,在html文件中任意位置添加:
{{ .FuzzyWordCount }} Words | Read in about {{ .ReadingTime }} Min
更多与模板变量相关信息请参考:Variables and Params
终极优化,部署博客到阿里云oss
部署博客有很多选择,国内外都有很多服务可以用,各有各的优缺点:
GithubPages | 码云Pages | Netlify | Heroku | 阿里云OSS | |
---|---|---|---|---|---|
纯静态托管 | 是 | 是 | 是 | 否👍 | 是 |
CDN加速 | 否 | 否 | 是👍 | 否 | 是👍 |
访问速度 | 慢 | 快👍 | 一般 | 一般 | 很快👍 |
支持404重定向 | 否 | 是👍 | 是👍 | 是👍 | 是👍 |
自定义重定向 | 否 | 否 | 是👍 | 是👍 | 否 |
具体选择哪个,根据个人对博客的需求进行选择。
- 访问速度快:优先选择阿里云(国内CDN加速)、其次是码云(国内服务器)
- 功能最强:Heroku(支持Node.js、PHP等后端)
鉴于本人较追求响应速度,因此毫不犹豫选择了部署到阿里云,下面是一些关于部署的简单过程:
部署思路
- 提交代码到GitHub
- 开通阿里云oss,创建bucket
- 通过GitHub Actions同步博客静态文件到阿里云oss
阿里云oss
1、对象存储oss
- 登录到阿里云之后点击控制台,选择对象存储oss,
2、创建Bucket
-
如果你还没有创建bucket,点击新建bucket。
-
进入新建Bucket页面,并填写Bucket信息。
3、绑定域名
-
点击进入你创建好的bucket,在左边一栏选择传输管理,点击域名管理,绑定你的域名
-
绑定填写时记得勾选添加CNAME
4、上传文件
- 点击红圈标注的上传文件,会弹出文件框列表,选择需要上传的文件进行上传。
- 将你的博客项目下public文件里的所有文件上传
5、获取accesskeyid和accesssecret
- 这相当于账号和密码,这样你就可以使用一些第三方工具进行上传
- 鼠标移到你的阿里云头像,进入AccessKey管理,使用子用户AccessKey,创建用户,创建时选择编程访问
- 记住提供的
AccessKeyd
和AccessSecret
,后面会用到
通过GitHub Actions同步博客静态文件到阿里云oss(2020-11-19已失效)
1、.github/workflows/main.yml
-
将拿到的 AccessKeys配置到GitHub项目的 Secrets
登录GitHub,进入博客项目,Setting,Secrets,new secret,新建两个secrets,命名Name分别为
ACCESS_KEY_ID
和ACCESS_KEY_ID
,填入相对应的值Value,即为上一步获取的两个值 -
在本地博客项目下的public文件夹下新建文件:
mkdir .github/workflows && cd .github/workflows
touch main.yml
- 编辑
main.yml
# This is a basic workflow to help you get started with Actions
name: main
# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the master branch
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/setup-node@v1
with:
node-version: "12.x"
- name: Build Blog
run: |
npm install
npm install -g hexo-cli
hexo generate
- uses: manyuanrong/setup-ossutil@v1.0
with:
# endpoint 可以去oss控制台上查看
endpoint: "oss-cn-beijing.aliyuncs.com"
# 使用我们之前配置在secrets里面的accesskeys来配置ossutil
access-key-id: ${{ secrets.ACCESS_KEY_ID }}
access-key-secret: ${{ secrets.ACCESS_KEY_SECRET }}
- name: Deply To OSS
# oss的路径参考你的阿里云对象存储的bucket名称
run: ossutil cp ./ oss://billie-s-blog/ -rf
2、提交代码到GitHub
提交成功之后可进入项目下的Actions选项,查看任务日志,如果不出错,则已成功同步到阿里云oss
PicGO+阿里云oss 图床搭建
考虑到自己写博客的需要,以及方便管理等原因,需要寻找一个方便的图床用来存储图片
创建对象存储OSS、bucket
在阿里云里面开通对象存储只是表明你开通了对象存储的功能。而bucket (桶)才是真正用于存东西的容器
- 创建对象存储OSS
- 在OSS控制台创建bucket,注意:在读写权限那里选用“公共读”,这样才能使用链接直接访问我们所存储的图片。
配置PicGo
-
切换到阿里云图床的配置页面,填写配置信息,keyid和keysecret就是上节提到的accesskey与secret。存储空间名指你用来当作图床的bucket名。 存储区域按照你创建时候的区域来填写。存储路径可以写
img/
,后面一定要加斜杠。 最后两项不用填写。 -
然后就可以使用picgo方便地上传图片了。上传完成之后会自动把格式化的图片链接放到你的粘贴板里~~这样就可以很快地向md里插入图片了
Typora配置图片自动上传
typora是首选的markdown编辑器,如果使用的是 Typora
作为 Markdown
编辑器,可以参考下面步骤配置图片自动上传到OSS中:
打开偏好设置 -> 图像 ,更改 插入图片时...
选择上传图片 。下面的上传服务设定,选择你的图床工具软件,点击验证图片上传选项
出现这个就代表成功了
在后续的使用中,截图粘贴或添加图片到typora中,就会直接上传图片到阿里云oss,yes!
从http到https
让Google和百度搜索到你的博客
Google Search Console
打开Google Search Console,点击 “SEARCH CONSOLE ” 进入,然后添加资源。
会要求下载一个html文件如google571325××××.html
做验证,将这个文件保存到hugo站点根目录下的static子目录,更新站点内容让google search console可以访问到进行验证即可。
进入资源页面,点”索引”下的”站点地图”,在”添加新的站点地图”处输入当前hugo站点的sitemap,这个文件hugo会默认生成,就在根路径下,如https://chenxuefan.cn/sitemap.xml
。
百度搜索资源平台
打开 百度搜索资源平台 ,点击 链接提交,然后点”添加站点”。同样可以用文件验证的方式来进行网站验证。
进入”资源提交”下的”普通收录”,再点“sitemap”,在这里可以提交hugo网站的sitemap文件。
btw,百度不容许以子目录的方式提交子站点,和google不一样,只能在提交sitemap文件时,提交多个sitemap文件。这样也能勉强让百度收录。
支持插入echarts图表
-
在目录 ./themes/cactus/layouts/shortcodes下创建文件echarts.html,并编辑
<div id="echarts{{ .Get `height` }}" style="width: 100%;height: {{.Get `height`}}px;margin: 0 auto"></div> <script src="https://cdn.bootcss.com/echarts/3.8.0/echarts.common.min.js"></script> <script type="text/javascript"> // 基于准备好的dom,初始化echarts实例 var myChart = echarts.init(document.getElementById('echarts{{ .Get `height` }}')); // 指定图表的配置项和数据 var option = JSON.parse({{ .Inner }}) // 使用刚指定的配置项和数据显示图表。 myChart.setOption(option); </script>
-
在你的文章(md文件)任意处插入echarts的配置数据,注意echarts标签与左侧右侧的大括号之间没有空格,下方的演示代码需修改
{{ <echarts height="500"> }} {"title":{"text":"ECharts 入门示例"},"tooltip":{},"legend":{"data":["销量"]},"xAxis":{"data":["衬衫","羊毛衫","雪纺衫","裤子","高跟鞋","袜子"]},"yAxis":{},"series":[{"name":"销量","type":"bar","data": [5, 20, 36, 10, 10, 20]}]} {{ </echarts> }}
示例1:
示例2:
示例3:
定制文章详情页的访问链接
方法1:编辑config.toml
参考官方文档:https://www.gohugo.org/doc/extras/permalinks/
在config.toml文件中编辑以下代码,如不编辑,则访问链接默认为文件路径+文章命名
permalinks:
post: /:year/:month/:title/
- :year the 4-digit year
- :month the 2-digit month
- :monthname the name of the month
- :day the 2-digit day
- :weekday the 1-digit day of the week (Sunday = 0)
- :weekdayname the name of the day of the week
- :yearday the 1- to 3-digit day of the year
- :section the content’s section
- :title the content’s title
- :slug the content’s slug (or title if no slug)
- :filename the content’s filename (without extension)
方法2:slug
还有一个办法是编辑文章头信息时加上slug: 路由
如下:
全新板块:NCP(新冠疫情实时数据)
文章页添加标签词云
贴一张效果图先
-
在
themes\cactus\layouts\partials
目录下新建词云文件tagcloud.html
,并编辑<div id="theme-tagcloud" class="tagcloud-wrap"> {{ $tags := $.Site.Taxonomies.tags.Alphabetical }} {{ $v1 := where $tags "Count" ">=" 1 }} {{ $v2 := where $v1 "Term" "not in" (slice "tags" "rss" "weekly") }} {{ range $v2 }} {{ if .Term }} {{ $tagURL := printf "tags/%s" .Term | relURL }} {{$tagSize := 12}} {{if gt .Count 5}} {{ $tagSize = 16 }} {{end}} {{if gt .Count 10}} {{ $tagSize = 22 }} {{end}} {{if gt .Count 20}} {{ $tagSize = 24 }} {{end}} {{if gt .Count 30}} {{ $tagSize = 26 }} {{end}} {{if gt .Count 45}} {{ $tagSize = 28 }} {{end}} {{if gt .Count 65}} {{ $tagSize = 30 }} {{end}} <style> a {text-decoration: none}</style> <a href="{{ $tagURL }}" style="font-size:{{ $tagSize }}px; text-transform:capitalize;text-decoration: none!important;"> {{ .Term }}<!-- <span class="badge">({{ .Count }})</span> --> </a> {{ end }} {{ end }} </div> <style> .tagcloud-wrap{ overflow: hidden; } .tagcloud-wrap a,.tagcloud-wrap a:hover { background-image: none; -webkit-text-size-adjust:none; white-space:nowrap; line-height: 23px; margin-right: 10px; margin-bottom: 4px; float: left; } </style>
-
在
themes\cactus\layouts\_default\list.html
文件中添加词云模块{{ partial "tagcloud.html" . }}
-
完事!上线🌹
插入B站视频
众所周知,B 站允许使用 <iframe>
标签将视频嵌入到别的网站上, Hugo 允许自定义 Shortcodes,我们可以利用 Shortcodes 将插入 B 站视频标准化,在保证安全的同时方便用户使用。
-
在
themes\cactus\layouts\partials
目录下新建文件bilibili.html
,编辑如下<style> /*// 嵌入 BiliBili 视频*/ #bilibili { width: 100%; height: 550px; } @media only screen and (min-device-width: 320px) and (max-device-width: 480px) { #bilibili { width: 100%; height: 250px; } } </style> {{ $videoID := index .Params 0 }} {{ $pageNum := index .Params 1 | default 1}} {{ if (findRE "^[bB][vV][0-9a-zA-Z]+$" $videoID) }} <div><iframe id="bilibili" src="//player.bilibili.com/player.html?bvid={{ $videoID }}&page={{ $pageNum }}" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true" loading="lazy" > </iframe></div> {{ else }} <div><iframe id="bilibili" src="//player.bilibili.com/player.html?aid={{ $videoID }}&page={{ $pageNum }}" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true" loading="lazy" > </iframe></div> {{ end }}
-
用法
插入网易云音乐组件
-
在
themes\cactus\layouts\partials
目录下新建文件neteasemusic.html
,编辑如下{{ $musicID := index .Params 0 }} <div class="neteasemusic"> <iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width=100% height=86 src="//music.163.com/outchain/player?type=2&id={{ $musicID }}&auto=0&height=66"></iframe> </div>
-
用法
在md文件正文中插入:
新模块"Most recent" - 记录今日新更新、有修改的的文章
技术栈 - python、css
相关业务代码
def write_index_page(self, titles, urls, times):
doc = open(config.Index_path, 'r').read()
# '<h1>Most recent</h1>'
html = '''
<section id="projects">
<span class="h1">Most recent</span>
<ul class="post-list">
'''
for i in range(len(titles)):
html += f'''
<li class="post-item">
<div class="meta">
<time datetime="{times[i]} 00:00:00 +0000 UTC" itemprop="datePublished">{times[i]}</time>
</div>
<span>
<a class="" href="{urls[i]}">{titles[i]}</a>
</span>
</li>
'''
html += '</ul></section>'
doc = re.sub('<section id="Most recent">', html, doc)
with open(config.Index_path, 'w') as f:
f.write(doc)
移动端,将list.html
文章列表的显示样式设为不换行显示:display:inline;
<style>
@media screen and (max-width: 500px) {
#archive .post-list .post-item .meta{
display: inline!important;
}
}
</style>
在文章底部插入文章的最后修改时间
相关业务代码:
def write_article_page(self, md_file_paths: list, article_times_ymd: list):
html_file_paths = [os.path.join(config.public_path,
re.search('content/(.*?).md', file).group(1),
'index.html')
for file in md_file_paths
if not file.endswith('index.md')]
html_file_paths += [os.path.join(config.public_path,
re.search('content/(.*?)/index.md', file).group(1),
'index.html')
for file in md_file_paths
if file.endswith('index.md')]
for ind, each in enumerate(html_file_paths):
doc = open(html_file_paths[ind], 'r').read()
doc = re.sub('<p id="last updated">', f'<p>last updated {article_times_ymd[ind]}</p>', doc)
with open(html_file_paths[ind], 'w') as f:
f.write(doc)
站内搜索
切换主题
theme/cactus/layouts/partials/themeswitcher.html
<!--<button id="theme-toggle" onclick="toggleTheme()"></button>-->
<button id="theme-toggle" onclick="toggleTheme()" accesskey="t" title="(Alt + T)">
<svg id="moon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
</svg>
<svg id="sun" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
</button>
<style>
#theme-toggle {
z-index: 100; /* 设置较高的 z-index 值,使元素位于较低值的元素之上 */
/*display: inline-block;*/
/*width: 20px;*/
/*height: 10px;*/
/*border: 2px solid #333;*/
/*border-radius: 5px;*/
/*background-color: #fff;*/
border: none;
background-color: transparent;
color: #333;
text-align: center;
line-height: 50px;
cursor: pointer;
position: absolute;
top: 20px;
left: 12px;
touch-action: manipulation; /* 控制触摸行为 */
-webkit-tap-highlight-color: transparent; /* 禁用移动设备上的点击高亮效果 */
}
/*#theme-toggle:hover {*/
/* background-color: #333;*/
/* color: #fff;*/
/*}*/
#moon {
background-color: transparent;
display: none;
}
#sun {
background-color: transparent;
color: #fff;
display: none;
}
</style>
<script>
var currentTheme = themeLink.getAttribute("href");
var sun = document.getElementById("sun");
var moon = document.getElementById("moon");
if (currentTheme === "/css/style-light.css") {
sun.style.display = "none";
moon.style.display = "block";
} else {
moon.style.display = "none";
sun.style.display = "block";
}
function toggleTheme() {
var themeLink = document.getElementById("themeLink");
var currentTheme = themeLink.getAttribute("href");
var sun = document.getElementById("sun");
var moon = document.getElementById("moon");
// console.log(22)
console.log(themeLink.getAttribute("href"))
if (currentTheme === "/css/style-light.css") {
// console.log(themeLink.getAttribute("href"))
themeLink.setAttribute("href", "/css/style-dark.css");
localStorage.setItem("selectedTheme", "dark");
sun.style.display = "block";
moon.style.display = "none";
// console.log(themeLink.getAttribute("href"))
} else {
themeLink.setAttribute("href", "/css/style-light.css");
localStorage.setItem("selectedTheme", "light");
moon.style.display = "block";
sun.style.display = "none";
}
}
</script>
<!--<script>-->
<!-- -->
<!-- function toggleTheme() {-->
<!-- var themeLink = document.getElementById("themeLink");-->
<!-- var currentTheme = themeLink.getAttribute("href");-->
<!-- if (currentTheme === "/css/style-light.css") {-->
<!-- themeLink.setAttribute("href", "/css/style-dark.css");-->
<!-- localStorage.setItem("selectedTheme", "dark");-->
<!-- } else {-->
<!-- themeLink.setAttribute("href", "/css/style-light.css");-->
<!-- localStorage.setItem("selectedTheme", "light");-->
<!-- }-->
<!-- }-->
<!-- //-->
<!-- // window.addEventListener("load", function () {-->
<!-- // var themeLink = document.getElementById("themeLink");-->
<!-- // var storedTheme = localStorage.getItem("selectedTheme");-->
<!-- //-->
<!-- // if (storedTheme === "dark") {-->
<!-- // themeLink.setAttribute("href", "/css/style-dark.css");-->
<!-- // } else {-->
<!-- // themeLink.setAttribute("href", "/css/style-light.css");-->
<!-- // }-->
<!-- // });-->
<!--</script>-->
代码折叠
// 限制代码显示长度
$(document).ready(function() {
$('code').each(function() {
var codeElement = $(this);
var codeHeight = codeElement.height();
if (codeHeight > 50) {
codeElement.css({
'max-height': '500px',
'overflow-y': 'scroll'
});
}
});
});
秘密文章
blog_site/layouts/shortcodes/secret.html
<div id="se_div">
<input id="se_password" type="password" placeholder="Enter password...">
<input id="se_submit" type="submit">
</div>
<style>
/* 隐藏内容 */
div .content > *:not(#se_div) {
display: none;
}
</style>
<script>
var modal = document.getElementById('se_div');
var passwordInput = document.getElementById('se_password');
var submitButton = document.getElementById('se_submit');
var article = document.querySelector('article');
var correctPassword = 'your_password'; // 将 'your_password' 替换为实际的密码
window.onload = function () {
modal.style.display = 'block'; // 页面加载时显示弹窗
};
submitButton.addEventListener('click', function () {
var password = passwordInput.value;
if (password !== correctPassword) {
alert('密码错误,返回上一页。');
history.back();;
} else {
modal.style.display = 'none'; // 隐藏弹窗
var contentDiv = document.querySelector('div .content');
var seDiv = document.getElementById('se_div');
// 遍历 .content 下的所有子元素
for (var i = 0; i < contentDiv.children.length; i++) {
var child = contentDiv.children[i];
if (child !== seDiv && child.tagName !== 'SCRIPT' && child.tagName !== 'STYLE') {
child.style.display = 'block'; // 隐藏除了 id 为 "se_div" 的 div 之外的其他标签
}
}
}
});
</script>
在需要秘密访问的文章md正文中添加:
效果如下,输入正确密码之后即可访问正文内容:
移动端表单输入自适应
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<!-- 其他头部信息 -->
</head>
在上述代码中,我们添加了一个 <meta>
标签,其中的 viewport
属性用于控制视口的行为。通过将 width
设置为 device-width
,可以使视口宽度与设备宽度相匹配。initial-scale
设置为 1.0
,确保页面初始缩放级别为 1。maximum-scale
和 user-scalable
设置为 1.0
和 no
,分别限制最大缩放级别为 1,以及禁用用户手动缩放。
这样设置后,应该能够防止移动设备在输入密码时自动放大页面。请注意,具体效果可能因设备和浏览器而异,因此您可能需要进行一些调整和测试,以确保在不同移动设备上获得一致的结果。
主页图片自适应窗口显示
blog_site/theme/cactus/layouts/partials/mainimg.html
<style type="text/css">
#mainimg img{
/*width: calc(100vw - 17px); !* 考虑滚动条宽度 *!*/
max-width: 100%;
height: auto;
}
/*@media screen and (max-width: 500px) {*/
/* !*主页图片移动端适应*!*/
/* #mainimg {*/
/* max-width: 100%;*/
/* height: auto*/
/* }*/
/*}*/
</style>
<span id="mainimg">
<img :src="image_url">
<span v-if="image_url"></span>
<span v-else v-text="info"></span>
</span>
<script>
var vm = new Vue({
// 挂载点
el: "#mainimg",
// 数据对象
data: {
msg: '获取图片文件链接',
api: 'https://api.chenxuefan.cn/api/travel',
info: 'Rage,rage against the dying of the light.',
image_url: ''
},
// 事件
methods: {
get_image_urls() {
var that = this;
axios.get(that.api)
.then(function (response) {
console.log(response.data);
that.image_url = response.data;
})
.catch(function (error) {
console.log(error);
});
}
},
mounted() {
this.get_image_urls();
}
})
function adjustImageWidth() {
var image = document.getElementById('mainimg');
var windowHeight = window.innerHeight || document.documentElement.clientHeight;
var bodyHeight = document.body.scrollHeight;
var hasScrollbar = bodyHeight > windowHeight;
var currentWidth = image.offsetWidth;
// console.log(document.body.scrollHeight ,window.innerHeight)
while (document.body.scrollHeight > window.innerHeight && currentWidth > 410) {
console.log(document.body.scrollHeight, window.innerHeight)
currentWidth -= 10; // 减小宽度的步长
image.style.width = currentWidth + 'px';
}
}
// 页面加载完成时调整图片宽度
window.addEventListener('load', adjustImageWidth);
// 窗口大小改变时调整图片宽度
window.addEventListener('resize', adjustImageWidth);
</script>
当主页的内容过长时,可以根据主页当前显示的界面是否有滚动条,调整主页图片的大小,以达到优化视觉体验、完整预览主页内容的效果。
图片点击放大
blog_site/theme/cactus/layouts/partials/imgopen.html
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
.modal {
display: none;
position: fixed;
z-index: 100;
/*padding-top: 100px;*/
left: 0;
top: 0;
width: 100% !important;
height: 100% !important;
overflow: auto;
background-color: rgba(23, 23, 23, 0.9);
}
.modal-content {
margin: auto;
display: block;
width: 75%;
}
/* 移动设备样式 */
@media (max-width: 768px) {
.modal-content {
display: flex;
justify-content: center;
align-items: center;
width: 95%;
height: 95%;
}
}
.modal-content img {
width: 100%;
height: auto;
}
.modal-content .prev-div{
padding: 30px;
position: absolute;
top: 50%;
left: 5%;
}
.modal-content .next-div{
padding: 30px;
position: absolute;
top: 50%;
right: 5%;
}
.btn-s {
border: none;
}
.modal-content .prev-btn {
width: 2rem;
height: 2rem;
transform: rotate(45deg);
border-left: 1px solid #fff0ff;
border-bottom: 1px solid #fff0ff;
background-color: transparent;
cursor: pointer;
outline: none; /* 禁用默认焦点样式 */
transition: background-color 0.3s;
touch-action: manipulation; /* 控制触摸行为 */
-webkit-tap-highlight-color: transparent; /* 禁用移动设备上的点击高亮效果 */
}
.modal-content .next-btn {
width: 2rem;
height: 2rem;
transform: rotate(-45deg);
background-color: transparent;
border-right: 1px solid #fff0ff;
border-bottom: 1px solid #fff0ff;
cursor: pointer;
outline: none; /* 禁用默认焦点样式 */
transition: background-color 0.3s;
touch-action: manipulation; /* 控制触摸行为 */
-webkit-tap-highlight-color: transparent; /* 禁用移动设备上的点击高亮效果 */
}
.prev-btn:hover {
border-color: #ddd;
}
.next-btn:hover {
border-color: #ddd;
}
/* 移动设备样式 */
@media (max-width: 768px) {
.modal-content .prev-div {
top: 80%;
left: 10%;
}
.modal-content .next-div {
top: 80%;
right: 10%;
}
}
</style>
<!-- 放大图片的模态框 -->
<div id="myModal" class="modal" onclick="closeModal()">
<span class="modal-content">
<img id="modalImg" src="" alt="Modal Image">
<div class="prev-div" onclick="prevImage()" ><button class="btn-s prev-btn"></button></div>
<div class="next-div" onclick="nextImage()" ><button class="btn-s next-btn"></button></div>
</span>
</div>
<script>
// 获取所有的图片标签
var images = document.getElementsByTagName("img");
var currentImageIndex = 0;
// 为每个图片标签添加点击事件
for (var i = 0; i < images.length; i++) {
images[i].onclick = function () {
openModal(this);
};
}
// 打开模态框并显示放大的图片
function openModal(img) {
var modal = document.getElementById("myModal");
var modalImg = document.getElementById("modalImg");
modal.style.display = "block";
modalImg.src = img.src;
}
// 关闭模态框
function closeModal() {
var modal = document.getElementById("myModal");
modal.style.display = "none";
}
// 上一张图片
function prevImage() {
event.stopPropagation(); // 阻止事件冒泡
currentImageIndex--;
if (currentImageIndex < 0) {
currentImageIndex = images.length - 1;
}
var modalImg = document.getElementById("modalImg");
modalImg.src = images[currentImageIndex].src;
}
// 下一张图片
function nextImage() {
event.stopPropagation(); // 阻止事件冒泡
currentImageIndex++;
if (currentImageIndex >= images.length) {
currentImageIndex = 0;
}
console.log(images[currentImageIndex].src);
var modalImg = document.getElementById("modalImg");
modalImg.src = images[currentImageIndex].src;
}
</script>
移动端点击图片发生:
明亮背景色号
色号 - #e8e6df
#e2e0de
为每一个图片标签都设置圆角
blog_site/theme/cactus/layouts/partials/imgradius.html
<script>
// 获取所有的图片标签
var images = document.getElementsByTagName("img");
// 为每个图片标签添加点击事件
for (var i = 0; i < images.length; i++) {
images[i].style.borderRadius = '5px';
}
</script>
blog_site/theme/cactus/layouts/partials/footer.html
{{ partial "imgradius.html" . }}
在个人页显示网站achievement
blog_site/layouts/shortcodes/achievement.html
<p id="ach">
🧑🏻💻 本站已统计共 <u v-text="ArticleNum"></u> 篇文章,共 <u v-text="formattedWordNum"></u> 字
</p>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var vm = new Vue({
el: "#ach", //挂载点
data: {
WordNum: '',
ArticleNum: ''
}, //数据对象
computed: {
formattedWordNum: function() {
return this.WordNum.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
},
methods: {
get_data() {
var that = this;
axios.get('https://api.chenxuefan.cn/api/blog')
.then(function (response) {
that.WordNum = response.data.WordNum;
that.ArticleNum = response.data.ArticleNum;
console.log(response, that.WordNum);
})
.catch(function (error) {
console.log(error);
});
}
},
mounted() {
this.get_data();
}
});
</script>
md文件中添加,效果如下:
页脚配图
blog_site/themes/catus/layouts/partials/footer.html
<img id='footerimg' src='{{ "images/footer.jpg" | relURL }}'>
<style>
#footer {
margin-top: 50px;
}
#footerimg {
z-index: -1;
height: 150px;
position: absolute;
bottom: -1px; /* 距离底部的距离 */
left: 10px; /* 距离左侧的距离 */
}
/* 移动设备样式 */
@media (max-width: 768px) {
#footerimg {
/*display: none;*/
height: 120px;
left: -1px; /* 距离左侧的距离 */
}
}
</style>
我的胶片笔记
开发了一个新页面,
https://chenxuefan.cn/album/gallery/
图片懒加载
blog_site/themes/catus/layouts/partials/imglazyload.html
<script src="https://cdn.jsdelivr.net/npm/lazyload@2.0.0-rc.2/lazyload.js"></script> <!-- 懒加载 -->
<script>
// 获取页面上所有的img元素
var imgElements = document.getElementsByTagName('img');
// 遍历每个img元素
for (var i = 0; i < imgElements.length; i++) {
// 将src属性的值替换为data-src属性的值
var srcValue = imgElements[i].getAttribute('src');
// 如果src属性有值,则将其设置为data-src属性的值,并将src属性置为空
if (srcValue) {
imgElements[i].setAttribute('data-src', srcValue);
imgElements[i].removeAttribute('src');
imgElements[i].classList.add('lazyload'); // 添加lazy类
}
}
lazyload();
</script>
blog_site/theme/cactus/layouts/partials/page/single.html
{{ partial "imglazyload.html" . }}
Meta keywords 自动获取值
在页面源代码中加入title标签、<meta name="description" content="">
、<meta name="keywords" content="">
,并正确填写其文本,这样会提高爬虫的收录。
菲林日记中存在固定的文本格式,因此可以按照正则取到关键信息,下面是hugo语法使用正则方法匹配的案例:
blog_site/theme/cactus/layouts/partials/head.html
<!-- keywords-->
{{ if strings.Contains .Title "菲林日记" }}
{{ $keywords := slice }} <!-- 初始化一个空的数组 -->
{{ $content := .Content | safeHTML }} <!-- 获取内容并将其转换为 HTML -->
{{ $lines := findRE `<p>\s*(.+?)\s*</p>` $content 4 }}
{{ range $lines }}
{{ $line := . }} <!-- 去除前面的<p>标签和前后空格 -->
{{ if ne $line "" }}
{{ if strings.Contains $line "🎞️" }}
{{ $match := findRE `<p>\s*🎞️ - (.*?)\s*</p>` $line 1 }} <!-- 提取匹配的内容 -->
{{ $keyword := index $match 0 }} <!-- 获取第一个匹配的内容 -->
{{ $keyword := replace $keyword "<p>🎞️ - " "" }} <!-- 去除前缀 -->
{{ $keyword := replace $keyword "</p>" "" }} <!-- 去除后缀 -->
{{ $keyword := trim $keyword "" }} <!-- 去除前后空格 -->
{{ $keywords = $keywords | append $keyword }} <!-- 将关键词添加到列表中 -->
{{ else if strings.Contains $line "📷" }}
{{ $match := findRE `<p>📷 - (.*?)</p>` $line 1 }} <!-- 提取匹配的内容 -->
{{ $keyword := index $match 0 }}
{{ $keyword := replace $keyword "<p>📷 - " "" }} <!-- 去除前缀 -->
{{ $keyword := replace $keyword "</p>" "" }} <!-- 去除后缀 -->
{{ $keyword := trim $keyword "" }} <!-- 去除前后空格 -->
{{ $keywords = $keywords | append $keyword }}
{{ else if strings.Contains $line "🖨" }}
{{ $match := findRE `<p>🖨 - (.*?)</p>` $line 1 }} <!-- 提取匹配的内容 -->
{{ $keyword := index $match 0 }}
{{ $keyword := replace $keyword "<p>🖨 - " "" }}
{{ $keyword := replace $keyword "</p>" "" }}
{{ $keyword := trim $keyword "" }} <!-- 去除前后空格 -->
{{ $keywords = $keywords | append $keyword }}
{{ else if strings.Contains $line "🏞" }}
{{ $match := findRE `<p>🏞 - (.*?)</p>` $line 1 }} <!-- 提取匹配的内容 -->
{{ $keyword := index $match 0 }}
{{ $keyword := replace $keyword "<p>🏞 - " "" }} <!-- 去除前缀 -->
{{ $keyword := replace $keyword "</p>" "" }} <!-- 去除后缀 -->
{{ $keyword := trim $keyword "" }} <!-- 去除前后空格 -->
{{ $keywords = $keywords | append $keyword }}
{{ end }}
{{ end }}
{{ end }}
<!-- 生成 meta keywords 标签 -->
{{ if gt (len $keywords) 0 }}
<meta name="keywords" content="{{ delimit $keywords ", " }}" />
{{ else }}
<meta name="keywords" content="{{ delimit .Site.Params.Keywords ", " }}" />
{{ end }}
{{ else if .Keywords }}
<meta name="keywords" content="{{ delimit .Keywords ", " }}" >
{{ else }}
<meta name="keywords" content="{{ delimit .Site.Params.Keywords ", " }}" >
{{ end }}
后记
更多功能正在持续更新中 🌹