🔝重要内容置顶

注意每次备份前,都需要commit一下~(回忆前面讲的内容,如果不commit就只是在本地有缓存,没有上传到缓存区

➜  dlog git:(master)git add themes/butterfly41
➜  dlog git:(master)git add .
➜  dlog git:(master)git commit -m '211023版本提交'
➜  dlog git:(master)git push backup_github master

后面的hexo是远程仓库名,masterhexo这个远程仓库的默认分支名,这些名字都可以自己个性化命名,它们都不是代码中的关键字

将备份下载到本地的代码:(要预先给电脑配置好SSH哦~)

➜  dlog git:(master) ✗git clone https://gitee.com/dhndzwxj/hexo-backup.git hexo-backup

如何将仓库拷贝到本地?(以Windows版本为例)

cd Desktop
git clone git@github.com:dhndzwxj/hexo-backup.git juncker

当然,进行这一切的前提是提前在使用的电脑上设置好SSH key,方法详见本文章3.1节。

下载主题:

git clone -b master https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly

【完美解决】Hexo博客出现“Cannot GET/xxx”错误

npm audit fix

1. 搭建环境

首先搭建nodejsgit环境。这两个环境相当于博客搭建的基础设施,没有这两个东西的支持,就没法对自己的博客实现结构化管理。虽然这一步看起来非常枯燥无聊,却是实现后续所有功能的关键步骤!经过实验,下载这两个环境的时候,一定要关闭电脑的安全中心当中的“病毒和威胁防护”,否则直接被电脑当病毒杀灭了。

1.1 Windows

第一步,点击nodejs进行下载nodejs。

按照提示一步一步进行安装操作即可(时间比较长,请保持耐心~)。下一步是Windows平台配置环境。

高级系统设置——环境变量(配置环境变量的目的是,以后gitnpm这两个命令可以在任何目录下使用,变成了全局的指令。不配置的话会出错!

新增系统变量:
变量名填:NODE_PATH
变量值填:(文件路径)

还需要在Path变量名中修改nodejs文件默认路径

第二步,git环境搭建

到Git官网下载,下载地址,一步一步进行安装。如下图(仅展示需要提示的部分,基本上选择默认选项即可):

1.2 Mac

苹果电脑上的安装相对比较简单。

下载nodejs:node-v16.11.1.pkg

下载git:git-2.33.0-intel.dmg

检查安装的版本信息:

# 首先检查是否安装了git和node.js,终端输入一下命令,
node -v #是否出现安装版本信息,出现说明已经安装了
git --version #同上述情况
# 如果没有安装,则进行安装,都可以通过直接下载安装测序进行安装,这里不演示了。

如果已经安装好了上述的软件,那么可以安装hexo,然后等待安装成功即可。

1.3 Nas

略,可参考虚拟机玩群晖

2. 搭建博客站点

因为npm的服务器在海外,所以有时候用这个指令安装东西的话速度比较慢。好在国内有淘宝镜像cnpm,功能和原装的npm一模一样,只需在命令行中输入:

npm config set registry https://registry.npm.taobao.org
npm install -g cnpm --registry=https://registry.npm.taobao.org #(Windows)
sudo npm install -g cnpm --registry=https://registry.npm.taobao.org #(MacBook)

以后直接用cnpm替换npm即可!如有可选更新,cnpm也可以进行升级:

npm -g i cnpm #更新cnpm
npm install -g npm@[版本号] #更新npm
sudo npm install -g npm@[版本号] #更新npm(MacBook)

2.1 Windows

第一步是安装Hexo环境:

npm install -g hexo-cli

第二步是新建一个存放博客的专属文件夹,然后在这个文件夹下进行操作:

# 在你的家目录下创建一个blog文件夹
mkdir dlog
# 进入目录
cd dlog
# 初始化目录
hexo init
#开启本地服务 
hexo s

出现以下信息,说明你可以本地访问博客系统,在浏览器输入http://localhost:4000这个网址,就可以看到博客首页。

2.2 Mac

Mac上的步骤不能说一模一样的,反正也是八九不离十了。

sudo npm install -g hexo-cli
# 在你的家目录下创建一个blog文件夹
mkdir blog
# 进入目录
cd blog
# 初始化目录
hexo init
#开启本地服务 
hexo s

2.3 部署hexo博客

以下步骤对Windows和Mac而言,没有任何区别。

首先需要安装hexo-deployer-git插件,这样我们才能把生成的public文件夹部署到Github或者其他代码托管平台。

npm install hexo-deployer-git --save

下面你需要打开根目录,然后在这个目录下找到站点配置文件_config.yml(如下图左)。并且打开这个文件,进行编辑。

在站点配置文件内(如上图右)找到deploy这一行代码,按照以下的格式修改即可。(下面是本人的个性化设置,各个参数是什么意思,容我细细道来

deploy:
  type: git
  repo: git@github.com:dhndzwxj/dhndzwxj.github.io.git 
  branch: master
  • deploy: 部署的意思,一个关键词
  • type:部署的类型,这里选择的是git,就是上面我们已经安装的那个!
  • repo:仓库的意思,我们所有的本地文件都要上传到这里!我们要在github上面申请自己的账号,建造自己的仓库~(具体怎么做见后文!)
  • branch:仓库的分支的意思,一个仓库里面可以有多个分支,就像是一个大院里面有很多间厢房一样。具体的含义后面解释。

2.4 Nas

略,可参考虚拟机玩群晖

3. 博客代码部署

3.1 Github

对我们而言,Github可能只是一个存数据的仓库而已。但对于大多数编程爱好者而言,这是一个巨大的资源宝库,是全球编程爱好者交流思想的平台。它的官方网址是:Github

第一步要在Github上申请一个账号。点击网站右上角的Sign up,然后在下一个页面用自己的邮箱进行注册,输入一个比较复杂的密码,输入自己的个性化用户名,然后选择是否接受更新(输入y表示同意,n表示不同意),然后进行人机验证,最后在邮箱接收验证码。

后面就是一些个性化的设置,不关键,此处略去。如果想跳过这些烦人的步骤,直接把网页拉倒最底下,点一下skip personalization即可。

接下来进入github个人首页,如下图所示。

下面我们就要建立“仓库”用以储存我们的博客网站了!点击如上图中左边的“create repository”(如果你已经有仓库了,那么酒店左上角的“New”,或者点右上角“+”,里面有个“New repository”),进入下面的页面。这个页面里面一些设置要特别注意了!仓库的名字(repository name)一定要和自己的名字(owner)一模一样!而且一定要写成yourname.github.io的格式。不然后面没办法生成相应的github页面。

这样仓库就建好了。每一个仓库都有自己的地址,如下图:

一般而言,最重要的地址是SSH地址,将其复制下来,粘贴到博客站点配置文件blog/_config.yml就可以了。这里解释了上面deployrepo的参数的含义!

deploy:
  type: git
  repo: git@github.com:dhndzwxj/dhndzwxj.github.io.git 
  branch: master

下一步要做的呢,就是在本地的文件和github仓库之间建立联系,其实两行代码也就解决了:

git config --global user.name "dhndzwxj"
git config --global user.email dhndzwxj@ruc.edu.cn

注意啊,上面的user.name和user.email一定替换成自己的githubID和注册的邮箱~

接下来还有个问题!我们目的是把本地的文件上传到云端的github,但是每次访问github,它都会要求我们输入密码,否则就没有权限(也是出于安全的目的)。怎么省掉这个麻烦呢?方法就是创建SSH(别问我这是啥,照着下面的说法做就是了)!

(1)检查是否有SSH key

登陆github,点击头像位置处 Settings ——> SSH and GPG keys ,查看是否有SSH keys。如果有,直接跳到第(3)步;如果没有,则继续。

(2)新建 SSH key,在CMD中输入如下代码,注意大小写,后面的邮箱替换成自己注册github的邮箱:

ssh-keygen -t rsa -C "dhndzwxj@ruc.edu.cn"
ssh-keygen -t ed25519 -C "dhndzwxj@ruc.edu.cn"

然后会出现:

Generating public/private rsa key pair.
Enter file in which to save the key (/c/Users/电脑名/.ssh/id_rsa):

直接回车就可以。然后会出现:

Enter passphrase (empty for no passphrase):
Enter same passphrase again:

要求你输入密码,这个密码会在你提交项目时使用,如果为空的话提交项目时则不用输入。这个设置是防止别人往你的项目里提交内容,但就我个人的使用体验而言,还是不设置的好,否则的话每次上传都需要输入密码,很麻烦。

注意:输入密码的时候没有*字样的,直接输入就好。然后会出现:

BASH
Your identification has been saved in /c/Users/电脑名/.ssh/id_rsa.
Your public key has been saved in /c/Users/电脑名/.ssh/id_rsa.pub.
The key fingerprint is:
65:69:······02:4b emailname@email.com
The key's randomart image is:
+--[ RSA 2048]----+
|                 |
|       .   o .   |
|    . o o = o    |
|   . o * = o     |
|  E  o + o .     |
| . o.   . .      |
|     ..          |
+-----------------+

至此,密钥已经成功生成。

(3)接下来在github上添加SSH key:

  • 打开本地文件:id_rsa.pub(文件路径可以在上一步SSH生成成功后看到路径,比如我的是/Users/电脑名/.ssh/id_rsa.pub),可以将这个文件在编辑器中打开,然后全选复制。
    • 这一步需要注意,打开后直接复制可能会损坏原来的字段格式,推荐使用命令行进行复制。mac上可以使用这个代码:cat ~/.ssh/id_rsa.pub。windows电脑就使用这个代码:clip < C:/Users/Administrator/.ssh/id_rsa.pub
    • 登陆github,点击头像位置处 Settings ——> SSH and GPG keys ——> New SSH key,点击新建SSH key。
  • 将 ① 中复制的内容粘贴在key文本框里,title可以不用填(或者自己起一个名字也可以)。

(4)测试设置是否成功:

ssh -T git@github.com

如果出现以下内容:Hi dhndzwxj! You've successfully authenticated, but GitHub does not provide shell access.。或者kex_exchange_identification: Connection closed by remote host。可以尝试访问该网站:https://docs.github.com/cn/authentication/troubleshooting-ssh/error-permission-denied-publickey

npm un hexo-deployer-git
npm i hexojs/hexo-deployer-git
ssh -T GITHUB-dhndzwxj@github.com

(5)然后就可以部署你的博客到github啦。这之后,输入网址yourname.github.io,就能访问自己的网站了。

hexo cl && hexo g && hexo d

如果这一步报错,可能是因为没有安装hexo-deployer-git插件,安装即可:

cnpm install hexo-deployer-git --save

代码解读:第(6)步实现了三个步骤:

  • hexo clhexo clean的简写。意思是清除本地的缓存,实际上就是把博客文件夹下的public文件夹删除掉了。这个public是基于本地的文件生成的、用于上传到仓库或者其他网站服务器上的文件夹,可以理解为本地文件上网的中转站、交通工具,删掉了也不影响本地的内容。

  • hexo ghexo generate的简写,意思是生成public文件夹。

  • hexo dhexo deploy的简写,意思是将生成的public文件夹部署到网上,我们这里是部署到github上面。为了顺利部署,我们前面也提到过,要在站点文件夹下_config.yml文件中修改一些内容,如下:

deploy:
  type: git
  repo: git@github.com:dhndzwxj/dhndzwxj.github.io.git 
  branch: master

上面的repo后面要换成自己仓库的SSH,SSH在如下的位置:

3.1a 部署意外情况

不过hexo d还可能遇到其他一些情况:Please make sure you have the correct access rights and the repository exists 。解决方法是:

到这里问题就很明确了,是DNS解析出问题了,导致github.com域名被解析成了localhost的ip地址,就自然连不上GitHub了。

Windows下执行ipconfig /flushdns 清楚DNS缓存后也没用,最后修改/etc/hosts文件,增加一条github.com的域名映射搞定。

140.82.113.4 github.com

设置http proxy

git config --global http.proxy socks5://127.0.0.1:7890

git config --local http.postBuffer 524288000 
#如果是缓存不够,设置500M缓存

git config --global sendpack.sideband false

如果要取消代理:

git config --global --unset http.proxy
git config --global --unset httpx.proxy

client_loop: send disconnect: Broken pipeiB

I solved the same problem by editing the file ~/.ssh/config to have:

Host *
    User dhndzwxj@ruc.edu.cn
    Hostname ssh.github.com
    IdentityFile ~/.ssh/id_rsa
    ServerAliveInterval 20
    TCPKeepAlive no
    Port 22

如果还有ip地址问题,就删掉/.ssh/known_hosts

github 的ip地址为。

ping github.com
PING github.com (140.82.113.4): 56 data bytes

!!!!!!!!!!!!!!!!!!!!

删除.ssh文件夹下的known_hosts

git 设置和取消代理

# 设置ss
git config --global http.proxy 'socks5://127.0.0.1:1080'

git config --global https.proxy 'socks5://127.0.0.1:1080'

# 设置代理
git config --global https.proxy http://127.0.0.1:1080

git config --global https.proxy https://127.0.0.1:1080

# 取消代理
git config --global --unset http.proxy

git config --global --unset https.proxy

还可能遇见这种问题:

On branch master
Changes not staged for commit:   
	(use "git add <file>..." to update what will be committed)
	(use "git restore <file>..." to discard changes in working directory)
  (commit or discard the untracked or modified content in submodules)

删掉博客目录下的.git文件夹(一般为隐藏状态)即可。不过值得注意的是,这个文件夹被删掉之后,所有的远程“代号需要重新设置”:

git remote add [代号] [仓库名]

3.2 Gitee

别名“码云”,服务器在国内。优点是下载速度比Github快了一个数量级,缺点是一旦博客中有某些“敏感词”就无法上线自己的博客。它的官方网址是:Gitee。后续的过程基本上重复了上一步在Github中所作的一切,因此不再赘述。

不过gitee的站点发布和github不太一样。需要在最后一步开启网站服务。不过我的博客经常被gitee检查出敏感词汇,因此一直没有上线成功,就不展示了哈~

如何将一个项目同时提交到GitHub和Gitee(码云)上?

找到.git下面的config文件,通过vi命令进行修改,笔者起初文件内容如下:

[core]
        repositoryformatversion = 0
        filemode = true
        bare = false
        logallrefupdates = true
        ignorecase = true
        precomposeunicode = true
[remote "origin"]
        url = git@github.com:secbr/shiro.git
        fetch = +refs/heads/*:refs/remotes/origin/*
[branch "main"]
        remote = origin
        merge = refs/heads/main

修改之后变为:

[core]
        repositoryformatversion = 0
        filemode = true
        bare = false
        logallrefupdates = true
        ignorecase = true
        precomposeunicode = true
[remote "origin"]
        // github的仓库地址
        url = git@github.com:secbr/shiro.git
        // gitee的仓库地址
        url = git@gitee.com:secbro/shiro.git
        fetch = +refs/heads/*:refs/remotes/origin/*
[branch "main"]
        remote = origin
        merge = refs/heads/main

4. 撰写博客

4.1 博客文件夹

我们打开自己的博客根目录,跟着我一个个了解里面的这些文件(夹)都是干什么的:

  • _config.yml:俗称站点配置文件,很多与博客网站的格式、内容相关的设置都需要在里面改。
  • node_modules:存储Hexo插件的文件,可以实现各种扩展功能。一般不需要管。
  • package.json:别问我,我也不知道干嘛的。
  • scaffolds:模板文件夹,里面的post.md文件可以设置每一篇博客的模板。具体用起来就知道能干嘛了。
  • source:非常重要。所有的个人文件都在里面!
  • themes:主题文件夹,可以从Hexo主题官网或者网上大神的Github主页下载各种各样美观的主题,让自己的网站变得逼格高端的关键!

接下来重点介绍source文件夹。新建的博客中,source文件夹下默认只有一个子文件夹——_posts。我们写的博客都放在这个子文件夹里面。我们还可以在source里面新建各种子文件夹满足自己的个性化需求,对初学者而言,我们先把精力放在主线任务上,然后再来搞这些细节。

4.2 新建博客文件

博客文件格式为.md(Markdown格式文件,建议使用typora软件进行编辑)。我们一定要用代码建立新的博客 :

hexo n 文件名

如上图,左边的是scafford文件夹下的模板文件,右边是我们刚刚新建的markdown文件。我们可以看出模板文件是干嘛用的了吧!之所以要用代码新建博客文件,是因为这种方式生成的文件有前面的头文件(Front Matter),这些内容对于后面生成网页很有用!默认的模板文件中并没有这么丰富的内容,这些新添加的东西都是我写博客一年来积累的符合个人习惯的设置,不必邯郸学步。

写好内容后,在命令行一键三连:

hexo cl && hexo g && hexo s

然后随便打开一个浏览器,在网址栏输入localhost:4000/,就能发现自己的网站更新了!不过这只是在本地进行了更新,要想部署到网上(Github上),输入如下代码:

hexo d

然后在浏览器地址栏输入https://yourname.github.io,或者yourname.github.io就能在网上浏览自己的博客了!

以上,我们的博客网站1.0版本就搭建完成了,如果没有更多的需求,做到这里基本上就可以了。如果有更多的要求,还需要进一步的精耕细作!

4.3 Typora-Markdown语法

如果想要系统学习相关的编码知识,需要研究以下两套代码:

Markdown语法:菜鸟教程

Markdown是支持html和html5的语法的:菜鸟教程

不过出于快速上手的目的,下面我分享一些设置和模板,方便新学者能快速投入使用。

首先要对Typora软件进行一些设置。按Ctrl+,打开其设置界面。

(1)由于个人喜欢使用LaTeX语法,因此要在Markdown标签中打开内联公式:

(2)此外如果在Typora中插入图片,我的做法是用QQ截图(或者其他截图工具)然后直接粘贴到Typora里。这样做一般都会成功,生成的图片格式如下:

![image-20211026101023735](/../../images/%E5%8D%9A%E5%AE%A2%E6%90%AD%E5%BB%BA%E6%97%A5%E5%BF%97/image-20211026101023735.png)
  • 插入图片代码的基本格式为![图片描述](图片路径)。其中“图片描述”部分可以省略不写。图片路径是我自己设置过的:打开Typora的设置,在“图像”标签下,自定义的图片插入路径:(如果不设置,我都不知道这插入的图片存到我电脑的什么奇怪位置去了!

  • 我的图片和文章的存储结构为:

  • source

    • _posts
    • images
  • 下一张图的../images/${filename}是在macOS系统下设置的。不过在windows系统下这么设置就出现了问题,要改成/../../images/${filename}。【在相对路径的语法中,.代表文件所在的目录,..代表上一级目录,${filename}代表文件名。】

  • /../../source/images/${filename}的含义是,在_post的同目录(source)下,创建一个images子文件夹(如果文件夹已经存在就不另建了),然后在images下,以当前工作的markdown文件名新建子子文件夹,于是这篇博文中的所有图片都保存在这个新的子子文件夹下了。

  • 当然,我不推荐用上面的方法插入图片,因为没办法设置图片的大小、位置等信息。我一般用html标签语言插入图片,代码如下:

<div align='center'>
  <img src='图片路径' width=450px>
  <p align='center' style='font-size:15px;font-family:kaiti;color:red'>图片标题 </p>
</div>
  • <div></div>是html中表示代码块的标签,相似的还有<span></span>。只不过前者用于行间,后者用于行内。它包裹住所有的内容都会独立出现为一个大模块,里面的align='center'是设置整个模块的位置。

  • <img>是设置图片插入的标签(这个标签没有</img>配合),src写的是图片路径参数,width是图片宽度参数,px是宽度单位。其实还有高度的参数height,只不过我一般宽高只设置一个。还可以加入参数float='left',效果就像是word里面的“四周环绕型”,不推荐。

  • <p></p>是图片周边的文字内容。align前面已经介绍,style下可以设置文字的字号(font-size)、字体(font-family)、颜色(color)等,彼此之间用英文的分号间隔。(注意:这些属性都是可选的,也可以一个style都不写,那样就随系统默认格式。

(3)行间文字的设置,和上面介绍的图片周边的文字基本一样,只不过这里一般使用<span></span>标签。格式如下:

<span style='color:blue;background:yellow;font-size:18px;font-family:hei'>测试字体</span>

(4)如果要插入代码块,把自己的代码存在文章中,在英文输入法下按三下键盘上的这个键(下图),然后回车,代码环境就出现了:

(5)插入表格:段落——表格——插入表格

(6)无序列表

就是输入减号-然后按下空格,按下回车。如下:

  • 一级列表
    • 二级列表

生成二级列表的方法是按键盘的Tab键,返回上一级列表的方法是按键盘的Shift+Tab键。

(7)有序列表

就是输入1.然后按下空格,按下回车,如下:

(8)文中尾注

马克思[^2]是全世界无产阶级和劳动人民的伟大导师。

[^2]: 卡尔·马克思,全名卡尔·海因里希·马克思(德语:Karl Heinrich Marx,1818年5月5日-1883年3月14日),马克思主义的创始人之一。……

注意:在typora中写文章,能用html代码就别用markdown语法,否则生成网页的时候非常容易出现问题。比如对于加粗,建议使用标签<b></b>。而不是markdown的**内容**

5. 插件

使用插件可以让我们的博客功能更为强大,也能满足自己的一些特殊需要。下面列举一些事例,如有其他的要求,可以自行上网搜索、学习。

5.1 永久链接

在做优化之前,hexo-next文章链接默认的生成规则是::year/:month/:day/:title,是按照年、月、日、标题来生成的。这样,如果文章标题是中文的话,URL链接是也会是中文,特别容易出现编码错乱的问题。那能不能生成唯一不变的URL链接呢?答案是可以的,已经有人给我们实现了。这就是我们要说的hexo-abbrlink插件:

cnpm install hexo-abbrlink --save

修改博客站点配置文件_config.yml,将带有permalink:的一行改为:

permalink: /:abbrlink.html  # 此处可以自己设置
abbrlink:
    alg: crc32   #算法: crc16(default) and crc32
    rep: hex     #进制: dec(default) and hex

5.2 LaTeX

推荐这个插件:https://github.com/hexojs/hexo-math

npm i hexo-math --save

有几个坑:

  • 行间公式的换行,\\是远远不够的,要写成\\\tag\\;
  • 上标符号^后面接了一个内容后要紧接着空格,不然会出错;例如:^\star =, ^{ \star}
  • 下标符号_和后面的内容一定要有间隔,例如:x_ 1,\mathscr{R}_ {++}
  • 数学公式中,二阶导数的两个“撇”最好用代码^{ \prime\prime}来表示。
  • 数学公式中。减号’-'前后一定不要什么内容都没有,不然会被渲染为markdown语法中的item!
  • latex中的公式环境eqnarray会在html中自动生成公式的tag,因此最好使用{eqnarray*}环境——或者添加\notag

Hexo默认使用marked.js去解析我们写的markdown,比如一些符号,_代表斜体,会被处理为标签,比如x_i在开始被渲染的时候,处理为xi,比如init会被处理成init。

因此,我们需要采取更换Markdown引擎的方式。

npm i hexo-renderer-mathjax --save
npm uninstall hexo-renderer-marked --save
npm install hexo-renderer-kramed --save

执行上面的命令即可,先卸载原来的渲染引擎,再安装新的。

之后,现在站点配置文件下添加如下内容:

# _config.yml
math:
  katex:
    css: 'https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css'
    options:
      throwOnError: false
  mathjax:
    css: 'https://cdn.jsdelivr.net/npm/hexo-math@4.0.0/dist/style.css'
    options:
      conversion:
        display: false
      tex:
      svg:

要在主题配置文件中开启mathjax开关,把mathjax默认的false修改为true,具体如下:

# MathJax Support
mathjax:
  enable: true
  per_page: false

别着急,这样还不够,还需要在文章的Front-matter里打开mathjax开关,如下:

---
title: index.html
date: 2016-12-28 21:01:30
tags:
mathjax: true
--

5.3 行内公式bug

对于Typora软件而言,按Crtl+/(macOS按command+/)。可以进入源代码模式,这样就能看清楚那部分代码除了问题了!这样修改效率很高!(如下图)

5.4 脚注

hexo-renderer-markdown-it-plus插件(目前在使用,这个插件和hexo-renderer-kramed有冲突)

原生的主题是不支持网页脚注的,非常不方便。几经查找、探索,终于找到合适的方法,点击脚注即可实现跳转。

首先卸载原来的 markdown 渲染插件(我这里原来是 marked,这是 Hexo 提供的默认渲染插件),然后把 hexo-renderer-markdown-it-plus 装上:

cnpm un hexo-renderer-marked --save
cnpm i hexo-renderer-markdown-it-plus --save

然后在博客站点根目录下的_config.yml中进行相应的配置。简单的配置方法只包括设置 markdown 格式,这里就不详述了。高级的配置方法中包含很多可选项:

markdown:
  # 渲染设置
  render:
    # 置为true时,html内容保持不变;置为false时,html内容将被转义成普通字符串
    html: true
    # 是否生成与XHTML完全兼容的标签(虽然我不懂是什么意思)
    xhtmlOut: false
    # 置为true时,每个换行符都被渲染成一个<br>(即Hexo的默认表现);置为false时,只有空行才会被渲染为<br>(GFM的默认表现)
    breaks: true
    # 是否自动识别链接并把它渲染成链接
    linkify: true
    # 是否自动识别印刷格式(意思是把(c)渲染为©这样的)
    typographer: true
    # 如果typographer被设置为true,则该选项用于设置将dumb quotes("")自动替换为smart quotes
    quotes: '“”‘’'
  # 设置所需插件
  plugins:
    - markdown-it-abbr
    - markdown-it-footnote
    - markdown-it-ins
    - markdown-it-sub
    - markdown-it-sup
  # 锚点设置(因为我没有尝试相关内容,所以就不翻译相关说明了)
  anchors:
    level: 2
    collisionSuffix: 'v'
    permalink: true
    permalinkClass: header-anchor
    permalinkSymbol: § 

这里设置了文中章节、脚注等内容的格式。

5.5 文章加密功能

有些文章的内容,我们不希望别人能看到,Hexo提供了加密文章的插件:

cnpm install --save hexo-blog-encrypt

然后在你的文章的头部(Front Matter)添加上对应的字段,如 password, abstract, message。(这时候文章模板blog/scaffod/post.md的作用就体现出来了,这些内容加在模板里,以后生成的每一篇文章都能选择是否进行加密

如果password:后面空着,就是无密码;我发现如果密码内容加上英文输入法下的引号,引号也不会被算作密码内容。

---
title: 文章加密
date: 2019-01-04T22:20:13.000Z
category: 教程
tags:
  - 博客
  - Hexo
keywords: 博客文章密码		
password: TloveY				#是该博客加密使用的密码
abstract: 密码:TloveY				#是该博客的摘要,会显示在博客的列表页
message:  输入密码,查看文章			#这个是博客查看时,密码输入框上面的描述性文字
---

不过加密文件的toc无法上下拉,如果网页目录特别长就很不方便。原因是插件hexo-blog-encrypttoc添加了display inline (如下图),导致没办法滚动。

未加密的应该是这样的:

尝试修改插件:

找到需要修改的文件:node_modules/hexo-blog-encrypt/lib/hbe.js。然后注释这一段代码:

// document.getElementById('hexo-blog-encrypt').style.display = 'inline';

把这两句代码中的display = 'inline',改为display = ''

// TOC part
var tocDiv = document.getElementById("toc-div");
if (tocDiv) {
  tocDiv.style.display = '';
}

var tocDivs = document.getElementsByClassName('toc-div-class');
if (tocDivs && tocDivs.length > 0) {
  for (var idx = 0; idx < tocDivs.length; idx++) {
    tocDivs[idx].style.display = '';
  }
}

然后就成功了!

自主修改hbe.js文件完整版:

解决加密文章toc无法滚动的问题
//node_modules/hexo-blog-encrypt/lib/hbe.js

(() => {
  'use strict';

  const cryptoObj = window.crypto || window.msCrypto;
  const storage = window.localStorage;

  const storageName = 'hexo-blog-encrypt:#' + window.location.pathname;
  const keySalt = textToArray('hexo-blog-encrypt的作者们都是大帅比!');
  const ivSalt = textToArray('hexo-blog-encrypt是地表最强Hexo加密插件!');

// As we can't detect the wrong password with AES-CBC,
// so adding an empty div and check it when decrption.
const knownPrefix = "<hbe-prefix></hbe-prefix>";

  const mainElement = document.getElementById('hexo-blog-encrypt');
  const wrongPassMessage = mainElement.dataset['wpm'];
  const wrongHashMessage = mainElement.dataset['whm'];
  const dataElement = mainElement.getElementsByTagName('script')['hbeData'];
  const encryptedData = dataElement.innerText;
  const HmacDigist = dataElement.dataset['hmacdigest'];

  function hexToArray(s) {
    return new Uint8Array(s.match(/[\da-f]{2}/gi).map((h => {
      return parseInt(h, 16);
    })));
  }

  function textToArray(s) {
    var i = s.length;
    var n = 0;
    var ba = new Array()

    for (var j = 0; j < i;) {
      var c = s.codePointAt(j);
      if (c < 128) {
        ba[n++] = c;
        j++;
      } else if ((c > 127) && (c < 2048)) {
        ba[n++] = (c >> 6) | 192;
        ba[n++] = (c & 63) | 128;
        j++;
      } else if ((c > 2047) && (c < 65536)) {
        ba[n++] = (c >> 12) | 224;
        ba[n++] = ((c >> 6) & 63) | 128;
        ba[n++] = (c & 63) | 128;
        j++;
      } else {
        ba[n++] = (c >> 18) | 240;
        ba[n++] = ((c >> 12) & 63) | 128;
        ba[n++] = ((c >> 6) & 63) | 128;
        ba[n++] = (c & 63) | 128;
        j += 2;
      }
    }
    return new Uint8Array(ba);
  }

  function arrayBufferToHex(arrayBuffer) {
    if (typeof arrayBuffer !== 'object' || arrayBuffer === null || typeof arrayBuffer.byteLength !== 'number') {
      throw new TypeError('Expected input to be an ArrayBuffer')
    }

    var view = new Uint8Array(arrayBuffer)
    var result = ''
    var value

    for (var i = 0; i < view.length; i++) {
      value = view[i].toString(16)
      result += (value.length === 1 ? '0' + value : value)
    }

    return result
  }

  async function getExecutableScript(oldElem) {
    let out = document.createElement('script');
    const attList = ['type', 'text', 'src', 'crossorigin', 'defer', 'referrerpolicy'];
    attList.forEach((att) => {
      if (oldElem[att])
        out[att] = oldElem[att];
    })

    return out;
  }

  async function convertHTMLToElement(content) {
    let out = document.createElement('div');
    out.innerHTML = content;
    out.querySelectorAll('script').forEach(async (elem) => {
      elem.replaceWith(await getExecutableScript(elem));
    });

    return out;
  }

  function getKeyMaterial(password) {
    let encoder = new TextEncoder();
    return cryptoObj.subtle.importKey(
      'raw',
      encoder.encode(password),
      {
        'name': 'PBKDF2',
      },
      false,
      [
        'deriveKey',
        'deriveBits',
      ]
    );
  }

  function getHmacKey(keyMaterial) {
    return cryptoObj.subtle.deriveKey({
      'name': 'PBKDF2',
      'hash': 'SHA-256',
      'salt': keySalt.buffer,
      'iterations': 1024
    }, keyMaterial, {
      'name': 'HMAC',
      'hash': 'SHA-256',
      'length': 256,
    }, true, [
      'verify',
    ]);
  }

  function getDecryptKey(keyMaterial) {
    return cryptoObj.subtle.deriveKey({
      'name': 'PBKDF2',
      'hash': 'SHA-256',
      'salt': keySalt.buffer,
      'iterations': 1024,
    }, keyMaterial, {
      'name': 'AES-CBC',
      'length': 256,
    }, true, [
      'decrypt',
    ]);
  }

  function getIv(keyMaterial) {
    return cryptoObj.subtle.deriveBits({
      'name': 'PBKDF2',
      'hash': 'SHA-256',
      'salt': ivSalt.buffer,
      'iterations': 512,
    }, keyMaterial, 16 * 8);
  }

  async function verifyContent(key, content) {
    const encoder = new TextEncoder();
    const encoded = encoder.encode(content);

    let signature = hexToArray(HmacDigist);

    const result = await cryptoObj.subtle.verify({
      'name': 'HMAC',
      'hash': 'SHA-256',
    }, key, signature, encoded);
    console.log(`Verification result: ${result}`);
    if (!result) {
      alert(wrongHashMessage);
      console.log(`${wrongHashMessage}, got `, signature, ` but proved wrong.`);
    }
    return result;
  }

  async function decrypt(decryptKey, iv, hmacKey) {
    let typedArray = hexToArray(encryptedData);

    const result = await cryptoObj.subtle.decrypt({
      'name': 'AES-CBC',
      'iv': iv,
    }, decryptKey, typedArray.buffer).then(async (result) => {
      const decoder = new TextDecoder();
      const decoded = decoder.decode(result);

      // check the prefix, if not then we can sure here is wrong password.
      if (!decoded.startsWith(knownPrefix)) {
        throw "Decode successfully but not start with KnownPrefix.";
      }

      const hideButton = document.createElement('button');
      hideButton.textContent = 'Encrypt again';
      hideButton.type = 'button';
      hideButton.classList.add("hbe-button");
      hideButton.addEventListener('click', () => {
        window.localStorage.removeItem(storageName);
        window.location.reload();
      });

      // document.getElementById('hexo-blog-encrypt').style.display = 'inline';
      document.getElementById('hexo-blog-encrypt').innerHTML = '';
      document.getElementById('hexo-blog-encrypt').appendChild(await convertHTMLToElement(decoded));
      document.getElementById('hexo-blog-encrypt').appendChild(hideButton);

      // support html5 lazyload functionality.
      document.querySelectorAll('img').forEach((elem) => {
        if (elem.getAttribute("data-src") && !elem.src) {
          elem.src = elem.getAttribute('data-src');
        }
      });

      // support theme-next refresh
      window.NexT && NexT.boot && typeof NexT.boot.refresh === 'function' && NexT.boot.refresh();

      // TOC part
      var tocDiv = document.getElementById("toc-div");
      if (tocDiv) {
        tocDiv.style.display = '';
      }

      var tocDivs = document.getElementsByClassName('toc-div-class');
      if (tocDivs && tocDivs.length > 0) {
        for (var idx = 0; idx < tocDivs.length; idx++) {
          tocDivs[idx].style.display = '';
        }
      }

      return await verifyContent(hmacKey, decoded);
    }).catch((e) => {
      alert(wrongPassMessage);
      console.log(e);
      return false;
    });

    return result;

  }

  function hbeLoader() {

    const oldStorageData = JSON.parse(storage.getItem(storageName));

    if (oldStorageData) {
      console.log(`Password got from localStorage(${storageName}): `, oldStorageData);

      const sIv = hexToArray(oldStorageData.iv).buffer;
      const sDk = oldStorageData.dk;
      const sHmk = oldStorageData.hmk;

      cryptoObj.subtle.importKey('jwk', sDk, {
        'name': 'AES-CBC',
        'length': 256,
      }, true, [
        'decrypt',
      ]).then((dkCK) => {
        cryptoObj.subtle.importKey('jwk', sHmk, {
          'name': 'HMAC',
          'hash': 'SHA-256',
          'length': 256,
        }, true, [
          'verify',
        ]).then((hmkCK) => {
          decrypt(dkCK, sIv, hmkCK).then((result) => {
            if (!result) {
              storage.removeItem(storageName);
            }
          });
        });
      });
    }

    mainElement.addEventListener('keydown', async (event) => {
      if (event.isComposing || event.keyCode === 13) {
        const password = document.getElementById('hbePass').value;
        const keyMaterial = await getKeyMaterial(password);
        const hmacKey = await getHmacKey(keyMaterial);
        const decryptKey = await getDecryptKey(keyMaterial);
        const iv = await getIv(keyMaterial);

        decrypt(decryptKey, iv, hmacKey).then((result) => {
          console.log(`Decrypt result: ${result}`);
          if (result) {
            cryptoObj.subtle.exportKey('jwk', decryptKey).then((dk) => {
              cryptoObj.subtle.exportKey('jwk', hmacKey).then((hmk) => {
                const newStorageData = {
                  'dk': dk,
                  'iv': arrayBufferToHex(iv),
                  'hmk': hmk,
                };
                storage.setItem(storageName, JSON.stringify(newStorageData));
              });
            });
          }
        });
      }
    });
  }

  hbeLoader();

})();

5.6 上传本地pdf并在线预览

安装插件

cnpm i hexo-pdf -S

如果是网上的pdf,参考格式:

{% pdf http://www.u.arizona.edu/~compitel/marston.pdf %} 

如果直接在想添加的页面中 (md 文件) 添加pdf,就以网页标签的格式写代码:

<embed width='700' height='650' fullscreen='yes' src='http://www.u.arizona.edu/~compitel/marston.pdf'>点击打开pdf文件</embed>

对于本地的pdf文件,在搭建好的hexo博客的根目录的blog/source文件夹下新建一个文件夹,如local_file(用来存pdf文件。参考格式:

{% pdf '/local_file/test.pdf' %} 

如果要让pdf文件在一个新的页面打开,参考下面的代码:

<a href='/local_file/test.pdf' target='_blank'>点击这里在新的网页打开pdf文件</a>

注意,要先执行hexo d命令将本地的pdf文件上传到github上,然后pdf文件才能显示在网站上!这里我们就不难发现,github的仓库充当的是远程服务器的作用!

参考:https://github.com/superalsrk/hexo-pdf

5.7 引用本地文件的链接

其实不只是pdf文件,其他文件也可以上传到github上,并且在文章中直接引用的。可以在根目录下创建自己的文件夹,比如说my_folder,然后在这个文件夹下创建一个文档test.txt。那么通过以下代码就可以直接插入到网站上面了:

<a href='/my_folder/test.txt' target='_blank'>test文件</a>

区别不过是,如果引用的是pdf文件,就能直接打开一个新页面并进行文件预览。如果是其他格式的文件,浏览器默认为我们把文件下载到本地。

5.8 归档计数

npm install --save hexo-generator-index
npm install --save hexo-generator-archive

然后在博客站点配置文件blog/_config.yml中更改:

index_generator:
  path: ''
  per_page: 10
  order_by: -date
# 归档页面
archive_generator:
  per_page: 50
  yearly: true
  monthly: true
#效果,首页每间隔10篇文章就分页,归档页每间隔50篇文章才分页。

5.9 mermaid

这个博主介绍了一些标签外挂功能,可以按需索取:

mermaid标签不允许嵌套于一些隐藏属性的标签外挂,例如: tag-hide内的标签外挂和tabs标签外挂,这会导致mermaid图示无法正常显示,使用时请留意。

请不要压缩html代码,不然会导致mermaid显示异常

使用mermaid标签可以绘制Flowchart(流程图)、Sequence diagram(时序图 )、Class Diagram(类別图)、State Diagram(状态图)、Gantt(甘特图)和Pie Chart(圆形图),具体可以查看mermaid文檔

由于网页不支持直接显示Markdown中的mermaid语法,需要安装一个插件:

cnpm install --save hexo-filter-mermaid-diagrams

然后设置站点根目录:

# mermaid chart
mermaid: ## mermaid url https://github.com/knsv/mermaid
  enable: true  # default true
  version: "7.1.2" # default v7.1.2
  options:  # find more api options from https://github.com/knsv/mermaid/blob/master/src/mermaidAPI.js
    #startOnload: true  // default true

最后一步需要对js文件进行修改,需要修改的文件为footer.swig或者footer.pug或者footer.ejs或者

//footer.swig
{% if theme.mermaid.enable %}
  <script src='https://unpkg.com/mermaid@{{ theme.mermaid.version }}/dist/mermaid.min.js'></script>
  <script>
    if (window.mermaid) {
      mermaid.initialize({{ JSON.stringify(theme.mermaid.options) }});
    }
  </script>
{% endif %}
//- footer.pug
if theme.mermaid.enable == true
  script(type='text/javascript', id='maid-script' mermaidoptioins=theme.mermaid.options src='https://unpkg.com/mermaid@'+ theme.mermaid.version + '/dist/mermaid.min.js' + '?v=' + theme.version)
  script.
    if (window.mermaid) {
      var options = JSON.parse(document.getElementById('maid-script').getAttribute('mermaidoptioins'));
      mermaid.initialize(options);
    }
<!-- footer.ejs -->
<% if (theme.mermaid.enable) { %>
  <script src='https://unpkg.com/mermaid@<%= theme.mermaid.version %>/dist/mermaid.min.js'></script>
  <script>
    if (window.mermaid) {
      mermaid.initialize({theme: 'forest'});
    }
  </script>
<% } %>

例子

pie title 日常支出
	"衣" : 100
	"食" : 200
	"住" : 200
	"行" : 200
flowchart LR
	1 --> 2 x--x 3 --- 4 <--> 5
stateDiagram-v2
[*] --> Still
Still --> [*]
Still --> Moving
Moving --> Still
Moving --> Crash
Crash --> [*]

5.10 插入视频

首先安装插件:

cnpm i hexo-tag-aplayer --save
cnpm i hexo-tag-dplayer --save

然后写代码:

<p align='center'>
  <video height="400" autoplay="autoplay" controls="controls" allowfullscreen="true">
  	<source src="/local_video/各省GDP排行榜.mp4" type="video/mp4">
	</video>
</p>

对于B站视频而言,在网页端获取分享链接,然后直接粘贴进markdown文件即可(如下图)。

当然这种方式生成的视频无法控制其大小和位置,因此我这么写:

<p align='center'>
 	<iframe src="//player.bilibili.com/player.html?aid=799951124&bvid=BV1ey4y1b7GA&cid=320466524&page=1" style="width:100%; height:600px;" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"> </iframe>
</p>

如果需要禁止视频自动播放,只需要在视频链接后面加上&autoplay=0即可

效果如下图:

至于来自其他视频网站的资源如何挂在自己的博客上,需要自己慢慢摸索了,不一一列举。

Safari上出现问题的原因可以参考这篇文章:修复video标签在safari中无法播放mp4视频的问题

6 butterfly主题

butterfly美化魔改教程合集

注意:不要把个人需要的文件/图片放在主题source文件夹里,因为在升级主题的过程中,可能会把文件覆盖删除了。

在Hexo根目录的source文件夹里,创建一个文件夹来放置个人文件/图片。引用文件时直接写为/文件夹名称/文件名。这个技巧在配置主题文件时尤其要注意!

6.1 主题下载

这是从大神的github仓库里把主题克隆到我们的博客网站目录blog/themes/butterfly下:

git clone -b master https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly

这个主题的模板引擎为pug格式的文件,因此需要安装对应的支持程序:(至于模板引擎文件是啥,可参考后面——不知道也无所谓)

cnpm install hexo-renderer-pug hexo-renderer-stylus --save

这里我经常会遇到一个问题:我的错误是 Cannot GET /,因此在public目录下寻找index.html是否存在。如果不存在,执行下面代码对其进行修复:

npm audit fix

查看是否少了什么组件,通过cnpm install hexo-xxx-xxx 安装即可。

然后在博客站点配置文件blog/_config.yml中更改:

theme: butterfly

注意,themes后面的参数,要和主题所在的文件夹名一致!

6.1a 网站主题的更新

此部分仅对于已经配置好的用户参考。如果您是第一次使用本主题,请直接前往6.2

由于大多数主题都会定期更新,但是把自己原来的个性化修改内容重新在新版本上体现,又会浪费大量时间。这里总结一个相对节约时间、清晰明了的办法:利用github自带的compare功能。

首先fork更新的主题仓库,这样自己的github上就有了这个新的repository,然后把这个仓库clone到本地,然后命名为Jerryc227

git clone git@github.com:dhndzwxj/hexo-theme-butterfly.git

然后将这个本地文件夹与原作者的仓库建立联系

git remote add upstream git@github.com:jerryc127/hexo-theme-butterfly.git

接下来将自己的旧版本的主题文件拷贝并替换掉Jerryc227的原有内容,然后“三连”。

git add .
git commit -m '主题更新'
git push origin master

这样自己fork掉的仓库的内容就成功更新了。最后将自己本地的修改同原作者的仓库做比较:

6-2

6.2 网站背景

默认显示白色,可设置图片或者颜色。

修改主题配置文件blog/themes/butterfly/_config.yml

#圖片格式 url(http://xxxxxx.com/xxx.jpg)
#顏色(HEX值/RGB值/顔色單詞/漸變色)
#留空 不顯示背景
background:

留意: 如果你的网站根目录不是/,使用本地图片时,需加上你的根目录。
例如:网站是https://yoursite.com/blog,引用一张img/xx.png图片,则设置backgroundurl(/blog/img/xx.png)

6.3 字数统计

npm install hexo-wordcount --save

修改主题配置文件:

wordcount:
  enable: true
  post_wordcount: true
  min2read: true
  total_wordcount: true

如需调整右侧卡片网站信息内项目的数据,在文件/butterfly/layout/includes/widgets/card_webinfo.pug中操作。

6.3a 文章meta信息

post_meta:
  page: # Home Page
		...
  post:
		...
  tags: true # true or false 文章頁是否顯示標籤

我发现即使将主题配置文件中如下tags设置为true,依然没有显示标签。检查后发现是缺少对应代码,在大概38行的位置添加如下代码:

//- \blog\themes\butterfly\layout\includes\header\post-info.pug
if (theme.post_meta.post.tags && page.tags.data.length > 0)
  span.post-meta-tags
    span.post-meta-separator |
    each item, index in page.tags.data
      i.fas.fa-tag
      a(href=url_for(item.path)).article-meta__tags #[=item.name]

注意该if要和前面的if (theme.post_meta.post.categories && page.categories.data.length > 0)缩进相同。

小结:各个特殊符号的含义

  • #:例如#post-meta,生成的HTML代码为<div id='post-meta'>。(不过会另起一行)
  • .:例如.meta-firstline,生成的HTML代码为<div class='meta-firstline'>
  • .span.post-meta-author=page.author,生成的HTML代码为<span class='post-meta-author'>page.author代表的内容</span>

若要把第一行的meta信息整体修改,下面是现成的代码[大概第8行](对于author,我在其网页标签添加了id,<div id="post-meta-author">

//- \blog\themes\butterfly\layout\includes\header\post-info.pug
  .meta-firstline
    if (theme.post_meta.post.author)
        i.fa-solid.fa-user
        span.post-meta-author=page.author
    if (theme.post_meta.post.date_type)
      span.post-meta-date
        if (theme.post_meta.post.author)
          span.post-meta-separator |
        if (theme.post_meta.post.date_type === 'both')
          i.far.fa-calendar-alt.fa-fw.post-meta-icon
          span.post-meta-label= _p('post.created')
          time.post-meta-date-created(datetime=date_xml(page.date) title=_p('post.created') + ' ' + full_date(page.date))=date(page.date, config.date_format)
          span.post-meta-separator |
          i.fas.fa-history.fa-fw.post-meta-icon
          span.post-meta-label= _p('post.updated')
          time.post-meta-date-updated(datetime=date_xml(page.updated) title=_p('post.updated') + ' ' + full_date(page.updated))=date(page.updated, config.date_format)
        else
          - let data_type_update = theme.post_meta.post.date_type === 'updated'
          - let date_type = data_type_update ? 'updated' : 'date'
          - let date_icon = data_type_update ? 'fas fa-history' :'far fa-calendar-alt'
          - let date_title = data_type_update ? _p('post.updated') : _p('post.created')
          i.fa-fw.post-meta-icon(class=date_icon)
          span.post-meta-label= date_title
          time(datetime=date_xml(page[date_type]) title=date_title + ' ' + full_date(page[date_type]))=date(page[date_type], config.date_format)    
    if (theme.post_meta.post.categories && page.categories.data.length > 0)
      span.post-meta-categories
        if (theme.post_meta.post.date_type)
          span.post-meta-separator |
        each item, index in page.categories.data
          i.fas.fa-inbox.fa-fw.post-meta-icon
          a(href=url_for(item.path)).post-meta-categories #[=item.name]
          if (index < page.categories.data.length - 1)
            i.fas.fa-angle-right.post-meta-separator

也在网站首页修改展示文章的meta[大概在第26行添加]

//- \blog\themes\butterfly\layout\includes\mixins\post-ui.pug
        if (theme.post_meta.page.author)
          span.post-meta-author
            i.fa-solid.fa-user
            span.post-meta-author=article.author
            span.article-meta-separator |

6.4 主页和文章页面侧边栏

需要在文件/butterfly/layout/includes/widgets/index.pug中操作。我这里是去掉了文章页面的“最新文章”部分,于是我在读懂这段代码后,把“最新文章”和“广告”对应的两条代码删除了。此外,我也想让头像成为sticky_layout(随着网页滑动跟着走的效果),我的代码改成了下面的样子:

//- /butterfly/layout/includes/widgets/index.pug

#aside-content.aside-content
  //- post
  if is_post()
    //- 如果是文章页面,下面对应的else应该指的是主页面
    - const tocStyle = page.toc_style_simple
    - const tocStyleVal = tocStyle === true || tocStyle === false ? tocStyle : theme.toc.style_simple
    if showToc && tocStyleVal
      .sticky_layout
        include ./card_post_toc.pug
    else
      !=partial('includes/widget/card_author', {}, {cache: true})
      !=partial('includes/widget/card_announcement', {}, {cache: true})
      !=partial('includes/widget/card_top_self', {}, {cache: true})
      .sticky_layout
        if showToc
          include ./card_post_toc.pug
        !=partial('includes/widget/card_recent_post', {}, {cache: true})
        !=partial('includes/widget/card_ad', {}, {cache: true})
  else
    //- page
    !=partial('includes/widget/card_author', {}, {cache: true})
    !=partial('includes/widget/card_announcement', {}, {cache: true})
    !=partial('includes/widget/card_top_self', {}, {cache: true})      

    .sticky_layout
      if showToc
        include ./card_post_toc.pug
      !=partial('includes/widget/card_recent_post', {}, {cache: true})
      //- !=partial('includes/widget/card_ad', {}, {cache: true})
      //- !=partial('includes/widget/card_newest_comment', {}, {cache: true})
      !=partial('includes/widget/card_categories', {}, {cache: true})
      !=partial('includes/widget/card_tags', {}, {cache: true})
      //- !=partial('includes/widget/card_archives', {}, {cache: true})
      !=partial('includes/widget/card_webinfo', {}, {cache: true})
      !=partial('includes/widget/card_bottom_self', {}, {cache: true})

个人觉得card_author头像下面“文章”“标签”“分类”三个栏目没啥用,就把这个文件butterfly/layout/includes/widget/card_author.pug中的这些代码删掉,然后就有简洁的头像了~

.card-info-data.site-data.is-center
  a(href=url_for(config.archive_dir) + '/')
    .headline= _p('aside.articles')
    .length-num= site.posts.length
  a(href=url_for(config.tag_dir) + '/')
    .headline= _p('aside.tags')
    .length-num= site.tags.length
  a(href=url_for(config.category_dir) + '/')
    .headline= _p('aside.categories') 
    .length-num= site.categories.length

此外,主页侧边栏“最新文章”、归档以及主页文章简介面上有图片,如果不想要这些图片,需要在主题配置文件下面的部分进行修改:

cover:
  # display the cover or not (是否顯示文章封面)
  index_enable: false
  aside_enable: false
  archives_enable: false
  # the position of cover in home page (封面顯示的位置)
  # left/right/both
  position: both
  # When cover is not set, the default cover is displayed (當沒有設置cover時,默認的封面顯示)
  default_cover: /private_img/banner2.gif
    # - https://i.loli.net/2020/05/01/gkihqEjXxJ5UZ1C.jpg

文章加密功能的设置,在文件butterfly/layout/includes/widget/card_post_toc.pug中进行编辑。

6.5 文章置顶

【推荐】hexo-generator-index从 2.0.0 开始,已经支持文章置顶功能。你可以直接在文章的front-matter区域里添加sticky: 1属性来把这篇文章置顶。数值越大,置顶的优先级越大

---
title: {{ title }}
author: 揭晓
categories:
  - null
  - null
  - null
tags:
  - null
  - null
  - null
mathjax:
abstract: 这里有东西被加密了,需要输入密码查看哦。
message: 您好,这里需要密码。
wrong_pass_message: 抱歉,这个密码看着不太对,请再试试。
wrong_hash_message: 抱歉,这个文章不能被纠正,不过您还是能看看解密后的内容。
typora-root-url: images
abbrlink: 
date: {{ date }}
sticky:
description:
password:
---

6.6 目录折叠

由于我个人的目录比较大,完全展开三级目录的话,右边栏就完全被目录铺满了。butterfly主题提供了目录可折叠的选项,只需要在主题配置文件/butterfly/config.yml设置:

card_categories: 
  enable: true
  limit: 0 # if set 0 will show all
  expand: true # none/true/false
  sort_order: # Don't modify the setting unless you know how it works

6.7 本地搜索

6.7.1 主题v4.5.1原生功能

插件安装

npm install hexo-generator-search --save

修改站点配置文件_config.yml, 添加以下内容

search:
  path: search.xml
  field: post #默认post,将覆盖博客所有帖子;page,只覆盖博客所有页面;all所有
  content: true #若为false,则设置不搜索文章内容,只根据搜索标题
  format: html

主题配置文件/blog/themes/butterfly/_config.yml如下修改:

# Local search
local_search:
  enable: true
  preload: true #預加載,開啟後,進入網頁後會自動加載搜索文件。關閉時,只有點擊搜索按鈕後,才會加載搜索文件
  CDN:

6.7.2 原生搜索功能解读

需要编辑的文件为主题下的:themes/butterfly/source/js/search/local-search.js

基于butterfly下local-search文档解读的js学习笔记

通过阅读这个文件,我发现决定了搜索结果的输出内容的代码为:[大概在166行]

if (dataContent !== '') {
                str += '<p class="search-result">' + pre + matchContent + post + '</p>'
              }

如果要自定义搜索结果样式(不推荐修改,因为加载速度会变慢)最关键的一行代码为[大概在153行]:

keywords.forEach(keyword => {
    if(keyword[0]==='#' && keyword.length>1){
        keyword = keyword.substring(1)
      } 
      let regexStr = keyword
      const specialRegex = /[^\w\s]+/ // match special characters
      if (keyword.length === 1 && specialRegex.test(keyword)) {
        regexStr = `\\${keyword}`
      }
  matchContent = matchContent.replaceAll(keyword, '<span class="search-keyword">' + keyword + '</span>')
  dataTitle = dataTitle.replaceAll(keyword, '<span class="search-keyword">' + keyword + '</span>')
})

由上述两段代码此可见,最关键的变量是matchContent(正文中包含关键词的部分),它是怎么定义的?[大概在151行]

let matchContent = dataContent.substring(start, end)

它由dataContent定义【范围是从start到end】,dataContent又是怎么定义的?

✓ [大概在第102行]
let dataTitle = data.title ? data.title.trim().toLowerCase() : ''  //获取标题
const dataContent = data.content ? data.content.trim().replace(/<[^>]+>/g, '').toLowerCase() : '' //获取正文,其中【.replace(/<[^>]+>/g, '')】去掉了网页标签(正则表达式的意思是去掉'<>'中的内容)
const dataUrl = data.url.startsWith('/') ? data.url : GLOBAL_CONFIG.root + data.url //获取链接
+ const dataTags = data.tags ? data.tags : ''  //获取标签

这里定义了文章的标题dataTitle、正文内容dataContent、文章链接dataUrl。这三项内容都来自data变量的方法。而data的方法是怎么定义的?

✓ 在data方法里增加一个tags方法[大概在第59行]
      const res = await response.text()
      const t = await new window.DOMParser().parseFromString(res, 'text/xml')
      const a = await t
			data = [...a.querySelectorAll('entry')].map(item =>{
        return {
          title: item.querySelector('title').textContent,
          content: item.querySelector('content') && item.querySelector('content').textContent,
          url: item.querySelector('url').textContent,
+         tags: item.querySelector('tags') && item.querySelector('tags').textContent
        }
      })

back_selec

通过读data定义的代码,我们发现querySelector很关键!深入学习可以参考js学习笔记的相关内容

其实对于data变量的各个方法,其定义的根据来自插件hexo-generator-search生成的模板文件/hexo-generator-search/templates/search.xml。打开这个search.xml文件,我们就能看到entry title url tags tag这些奇怪的css选择器了。

6.7.3 搜索结果对话框显示标签

上面的tags行是参照content行添加的(url行末尾要加一个英文逗号,否则会报错)。因为一篇post的front-matter不一定包含tagscontent,因此需要做一下是否有tags的判断(&&),否则会出错。

原版代码:

if (dataContent !== '') {
  str += '<p class="search-result">' + pre + matchContent + post + '</p>'
}
✓ 原版生成的搜索结果只显示标题和正文节选,没有标签tags,这里的魔改就是增加了这个内容。[大概在第191行]
【这部分是反复修改的部分,也是标签搜索功能实现最为关键的地方】
if (dataContent !== '') {
//- 自定义开始:生成的搜索结果框里,加入显示tags
let splitT = '' 
//- 第一步:下面是去掉dataTags里非汉字和字母(数字)的部分,然后用两个汉字分号';;'把各个tags分隔开(保存在spliT变量里)
let space = 1
for (let i=0;i<dataTags.length;i++){
  if (/\S/.test(dataTags[i])){ 
    // \S 匹配Unicode非空白
    space = 0
    splitT = splitT.concat(dataTags[i])
  }else{
    if(space===0){
      splitT = splitT + ';;' 
      space = 1
    } 
  }          
}
//去掉splitT末尾的双分号;;
for(let i=0;i<splitT.length;i++){
  let l = splitT.length
  if(splitT[l-1]==';' && l>1){
    splitT = splitT.substring(0,l-2)
  }
}

//- 第二步: highlight all keywords
keywords.forEach(keyword => {
  if(keyword[0] === '#' & keyword.length>1){
    keyword = keyword.substring(1) // 如果第一个字符为#且长度大于1,将关键词第一个#去掉后再匹配
  }     
  splitT = splitT.replace(new RegExp(keyword,"gi"),'<span class="search-keyword">' + keyword + '</span>')
}) 

//- 第三步:由于第一步产生的为纯文本且包括双分号,此步骤去掉分号且加上fas fa-tag、控制字体(保存在splitTags里)
let splitTags = '<br/><i class="fas fa-tag"><span style="font-family:times">'
space = 1
for(let i=0;i<splitT.length;i++){
  if(splitT[i] !== ';'){
    space = 0
    splitTags = splitTags.concat(splitT[i])
  }else{
    if(space===0){
      splitTags = splitTags + '</span></i>&nbsp &nbsp<i class="fas fa-tag"><span style="font-family:times">'
      space = 1
    }
  }         
}
splitTags = splitTags + '</span></i>'

post = /S/.test(splitT) ?  post + splitTags : post  
//- 自定义结束

  str += '<p class="search-result">' + pre + matchContent + post + '</p>'
}

效果如下:

6.7.4 按照标签搜索

【最终目标】搜索框输入的关键词,如果其第一个字符是#关键词长度大于1,就进行标签匹配;否则和之前的搜索功能一样。

【准备工作】

需要编辑的文件为主题下的:themes/butterfly/source/js/search/local-search.js

hexo-generator-search插件里负责搜索的js文件为/hexo-generator-search/lib/json_generaotr.js。该文件里面提供了获取帖子(posts)和页面(pages)标题(title)、内容(content)、标签(tags)、目录(categories)……,确实没有获取author的方法。不要随便添加,因为还要和/hexo-generator-search/templates/search.xml对应上!

先读一下butterfly主题local-search的代码逻辑!大致分为以下几个步骤:

①搜索功能从大概96行data.forEach(data => {开始。foreach()是一个遍历函数,用来遍历所有的帖子。遍历过程分为两个子步骤:第一,先看遍历的帖子的标题、正文、标签(tags是我自己加的)是否包含关键词,如果包含令isMatch=true,否则isMatich=false;【大概107-128行】第二,对于isMatch=true的帖子,在搜索框显示其内容【大概131-201行】。

②我的目的是,搜索框输入的关键词,如果其第一个字符是#关键词长度大于1,就进行标签匹配;否则和之前的搜索功能一样。

③为了匹配标签,需要在data.forEach(data => {部分新定义2个变量。[大概103行]

let dataTags = data.tags ? data.tags.trim().toLowerCase() : ''  //获取标签
let indexTag = -1	//- +++添加标签定位变量
let l_keywords = keywords.toString().split('').length //- +++获取搜索关键词的长度

以及给data增加了一个tags方法:【前面已添加】

	data = [...a.querySelectorAll('entry')].map(item =>{
        return {
          title: item.querySelector('title').textContent,
          content: item.querySelector('content') && item.querySelector('content').textContent,
          url: item.querySelector('url').textContent,
+         tags: item.querySelector('tags') && item.querySelector('tags').textContent
        }
      })

主要是对下面这点代码进行魔改:【大概111行】

keywords.forEach((keyword, i) => {
  indexTitle = dataTitle.indexOf(keyword)
  indexContent = dataContent.indexOf(keyword)
  if (indexTitle < 0 && indexContent < 0) {
    isMatch = false
  } else {
    if (indexContent < 0) {
      indexContent = 0
    }
    if (i === 0) {
      firstOccur = indexContent
    }
  }
})

魔改之后是下面这个样子:

if (dataTitle !== '' || dataContent !== '') {
  keywords.forEach((keyword, i) => {
    if (keywords[0][0] === '#' && l_keywords > 1 && keywords[0][1] !== '#'){ //- 最后一个判断条件修复了正文中'##'无法搜索的问题
        //如果关键词第一个字符是#且长度大于1,那么进行tag搜索
        keyword = keyword.substring(1) // 将关键词第一个#去掉后再匹配
        //- 定义dataTags0的意义:去掉tags里面的网页标签代码,否则会把网页标签里面的代码(非正文内容)也匹配
        let dataTags0 = ''
        for(let i=0; i<dataTags.length;i++){
          dataTags0 = dataTags0.concat(dataTags[i].replace(/<[^>]+>/g, ''))
        }
        dataTags0 = dataTags0.trim().toLowerCase()
        indexTag = dataTags0.indexOf(keyword)
        if ( indexTag < 0 ){
          isMatch = false
        }else{
          firstOccur = 0
        }
      }else {
        indexTitle = dataTitle.indexOf(keyword)
        indexContent = dataContent.indexOf(keyword)
        if (indexTitle < 0 && indexContent < 0) {
          isMatch = false
        } else {
          if (indexContent < 0) {
            indexContent = 0
          }
          if (i === 0) {
            firstOccur = indexContent
          }
        }
      }
  })
} else {
  isMatch = false
}

6.7a butterfly4.9.0的本地搜索

2023年6月6日,jerryc更新了butterfly主题的4.8版本,这个版本对本地搜索功能进行了大幅度修改,特别是基础框架进行了比较大的调整。对于我而言,意味着之前折腾的“标签搜索”、“标题搜索”的功能都没法派上用场了。于是为了自己的使用方便,不能不重新探索,在此做学习笔记。

网页上显示的搜索结果的代码框架为:

<div class="local-search-hit-item">
    <a href="链接">
    	<span class="search-result-title">标题</span>
        <p class="search-result">
            正文片断<mark class="search-keyword">搜索关键词</mark>正文片断
        </p>
    </a>
</div>

通读butterfly/source/js/search/local-search.js这个文件,发现基本逻辑是:

  • 对于目录_post下所有文章进行遍历
  • 通过getIndexByWord函数生成标题、正文片段中keyword出现的各个位置【生成一个类似于数组的东西(indexOfTitle,indexOfContent),用来存位置信息?】
  • 利用变量hitCount来控制筛选条件,它等于上一步生成的“数组”长度之和。如果等于0,说明这一篇文章全篇不包括keyword
  • 对于已经筛选出来的文章,下一步是利用mergeIntoSlice函数生成一些slice,包括标题slice、正文片段slice(当然也包括我们即将生成的tags slice)
  • 下一步是操作这些slice,利用highlightKeyword函数将这些slice中的关键词部分加上着重号,然后再拼接在变量resultItem里(typeof(resultItem)===string)。resultItem就是我们展现在搜索框里面的结果。

弄清楚整体思路,下面是细节的修改,以及一些经验之谈。

6.7a.1 在搜索结果框内显示tags标签

首先在fetchData函数当中增加tags属性,方便其他部分对tags进行引用。

  fetchData () {
    const isXml = !this.path.endsWith('json')
    fetch(this.path)
      .then(response => response.text())
      .then(res => {
        // Get the contents from search data
        this.isfetched = true
        this.datas = isXml
          ? [...new DOMParser().parseFromString(res, 'text/xml').querySelectorAll('entry')].map(element => ({
              title: element.querySelector('title').textContent,
+              content: element.querySelector('content') && element.querySelector('content').textContent,
+              url: element.querySelector('url').textContent,
+              tags: element.querySelector('tags') && element.querySelector('tags').textContent
            }))
          : JSON.parse(res)
        // Only match articles with non-empty titles
        this.datas = this.datas.filter(data => data.title).map(data => {
          data.title = data.title.trim()
          data.content = data.content ? data.content.trim().replace(/<[^>]+>/g, '') : ''
          data.url = decodeURIComponent(data.url).replace(/\/{2,}/g, '/')
+          data.tags = data.tags ? data.tags.trim().replace(/<[^>]+>/g, '') : ''
          return data
        })
        // Remove loading animation
        window.dispatchEvent(new Event('search:loaded'))
      })
  }

下一步是在搜索结果里显示tags!在文章遍历的函数那里【大概124行】开始魔改,首先生成tagskeywords对应的indexOfTags0。(由于一篇文章可能有多个tag,为了对各个标签加以区分,标签之间加了双分号;;以分割。)

    this.datas.forEach(({ title, content, tags, url }) => {  
      // The number of different keywords included in the article.
      const [indexOfTitle, keysOfTitle] = this.getIndexByWord(keywords, title)
      const [indexOfContent, keysOfContent] = this.getIndexByWord(keywords, content)
////////////////////////////////////////////////////////////////////////////////////////
//---------------------定义了tags-------------------------------------------------------
      let tags0 = ''
      let space = 1
      //-以双分号;;分割一篇文章内的标签
      for(let i=0;i<tags.length;i++){
        if(/\S/.test(tags[i])){
          space = 0
          tags0 += tags[i]
        }else{
          if(space === 0){
            tags0 += ';;'
            space = 1
          }
        }
      }

      //增加Tags片断
      const [indexOfTags0, keysOfTags0] = this.getIndexByWord(keywords,tags0)
      const includedCount = new Set([...keysOfTitle, ...keysOfContent, ...keysOfTags0]).size

下一步是给tags也生成一个slice,并把slicesOfTags0“加工”后并入变量resultItem。这一步相当麻烦,我的思路是先将tags并入content的末尾【这一部分完成了keyword标重】,然后把resultItem拆成前后两部分,后一部分(resultItem2)对应于tags部分。然后再给每一个tag前面加一个标签。

//----------------------给tags的关键字强调------------------------------
let slicesOfTags0 = []
//将新生成的(带标签标志的)splitTags生成一个slice
while(indexOfTags0.length !== 0){
  slicesOfTags0.push(this.mergeIntoSlice(0,tags0.length,indexOfTags0))
}
//将新的slice中的关键字强调
if(slicesOfTags0.length !== 0){
  slicesOfTags0.forEach(slice => {
    resultItem += `${this.highlightKeyword(tags0, slice)}</p>`
  })
} else{
  resultItem += `${tags0}</p>`
}

let index = resultItem.indexOf("...<br>")
//以"...</br>"为界,把要展示的结果一分为二;
let resultItem1 = resultItem.substring(0, index+7)
let resultItem2 = resultItem.substring(index+7,resultItem.length)
//下面只改resultItem2,给tags前面加上标签符号

// // 去掉标签后面的分号;;,再在每个标签前面加一个图标
let indexTermin = resultItem2.indexOf("</p>") //终止位置
let resultItem21 = ""
if(indexTermin){
  space = 1
  resultItem21 = resultItem21.concat(`<i class="fas fa-tag"><span style="font-family:times,kaiti">`)
  for(let i=0;i<indexTermin;i++){
    if(resultItem2[i] !== ';'){
      space = 0
      resultItem21 = resultItem21.concat(resultItem2[i])         
    }else{
      if(space === 0)
      resultItem21 += '</span></i>&nbsp &nbsp<i class="fas fa-tag"><span style="font-family:times,kaiti">'
      space = 1          
    }
  }
  resultItem21 += '</span></i>'
}else{
  resultItem21 = resultItem2
}

resultItem = resultItem1 + resultItem21 + `</div>`   

由于作者原生的4.9.0版本的local-search有这么一个特性:如果正文中不包含keyword(标题中含),那么搜索结果框内显示的结果是有标题无正文。[如下图]

为了在搜索结果框内也显示正文,笔者也对此进行了魔改:

//----------------------给正文片断的关键字强调------------------------------
if(slicesOfContent.length !== 0){
  slicesOfContent.forEach(slice => {
    resultItem += `<p class="search-result">${this.highlightKeyword(content, slice)}...<br>`
  })
} else{
  resultItem += `<p class="search-result">${content.substring(0,Math.min(120,content.length))}...<br>`
}

由于作者原生的4.9.0版本的local-search的结果框,对应的链接触发为全文。我改成了只有点击标题才能跳转至文章主界面。

//----------------------给标题的关键字强调------------------------------
if (slicesOfTitle.length !== 0) {
  resultItem += `<div class="local-search-hit-item"><a href="${url.href}" target="_blank"><span class="search-result-title">${this.highlightKeyword(title, slicesOfTitle[0])}</span></a>`
} else {
  resultItem += `<div class="local-search-hit-item"><a href="${url.href}" target="_blank"><span class="search-result-title">${title}</span></a>`
}

6.7a.2 扩展搜索功能:按照文章的tags进行搜索

介绍几个作者的自定义函数:

  • getResultItems(keywords):这是整个搜索功能的主函数。有四个返回值:

    • item:所有返回的标题和正文片段
    • id:item的个数
    • hitCount:返回标题或者正文片段中关键词的个数
    • includeCount:?
  • highlightKeyword (val, slice):将文段val片段中包含的slice【关键词】替换为指定的强调格式。

  • mergeIntoSlice(start, end, index):Merge hits into slices,有四个返回值

    • hits
    • start
    • end
    • count
  • getIndexByWord (words, text, caseSensitive = false):关键词为words,查找的文段为textcaseSensitive用来控制匹配是否对大小写敏感。有两个返回值

    • index:
    • included:

一些变量的类型:

  • slicesOfContent:Object
  • slicesOfTitle:Object
  • indexOfContent:Object
  • indexOfTags:Object
  • tags:String
  • keywords:Object
  • keywords[0]:String

查看Object的属性和值:

resultItem +=  `<p class="search-result">${this.highlightKeyword(content, slice)}...<br/>${Object.keys(keywords)+Object.values(keywords)}</p></a>` //测试版本,获取Object的相关信息

JS常见坑:对象赋值会影响原对象

现象:直接用=的方式把一个对象赋值给另一个对象,会导致修改新对象时,原对象也发生变化

var obj1 = {'name': '1111'};
var obj2 = obj1;
obj2.name = '2222';
console.log(obj1.name); //'2222'

原因:JavaScript 中对象的赋值是默认引用赋值的(两个对象指向相同的内存地址)。具体内容可参考JavaScript语法中的Object部分

思路:如果输入的关键字为#+keyword或者@+keyword,那么我们就分别启动关键字搜索和标题搜索。

根据本人的踩坑经验,对于keywords首字符的判断(判断是否为#或者@)一定要放在文章遍历的外面!!!!!否则就会出问题(有兴趣可以自己试试看啥问题),魔改的代码如下所示:

getResultItems (keywords) {
    const resultItems = []
    let l_keywords = keywords[0].split('').length //- 获取搜索关键词的长度
    //- 将#标签搜索的判断放在循环之外!!!
    let tagSearch = 0 //-判断是否为标签搜索
    if(keywords[0][0] === '#' && l_keywords > 1 && keywords[0][1] !== '#'){
      tagSearch = 1
      keywords[0] = keywords[0].substring(1)
    }
    //- 将@标题搜索的判断放在循环之外!!!
    let titleSearch = 0 //-判断是否为标题搜索
    if(keywords[0][0] === '@' && l_keywords > 1 && keywords[0][1] !== '@'){
      titleSearch = 1
      keywords[0] = keywords[0].substring(1)
    }
    //- 从下面一行开始,是文章遍历
    this.datas.forEach(({ title, content, tags, url }) => {  
      // The number of different keywords included in the article.
      const [indexOfTitle, keysOfTitle] = this.getIndexByWord(keywords, title)
      const [indexOfContent, keysOfContent] = this.getIndexByWord(keywords, content)

记得前面我们分析过,hitCount的作用是控制筛选条件,它等于上一步生成的“数组”长度之和。如果等于0,说明这一篇文章全篇不包括keyword。利用这一功能,我们可以在keywords首字母为#时,只计算tags中出现了多少次keywords;在keywords首字母为@时,只计算title中出现了多少次keywords:

// Show search results
let hitCount = 0
if(tagSearch){
  hitCount =  indexOfTags0.length
}else if(titleSearch){
  hitCount =  indexOfTitle.length
}else{
  hitCount = indexOfTitle.length + indexOfContent.length + indexOfTags0.length
}
// const hitCount = indexOfTitle.length + indexOfContent.length + indexOfTags0.length

以上魔改完毕,演示结果如下:

根据标签搜索

根据标题搜索

6.7b 3-hexo主题的本地搜索

主题项目地址:https://github.com/yelog/hexo-theme-3-hexo

搜索效果:

通过作者搜索

通过标签搜索

实现搜索功能的关键代码:
ejs和js代码
<nav id="title-list-nav">
    <% site.posts.forEach(function(post, i){  %>
    <% if (post.hidden === true) { return true } %>
    <a <% if(post.top){%>id="top"<%}%> class="<%= __('all_articles') %> <% post.categories.forEach(function(category, i){ %><%=category.name%> <% }) %>"
       href="<%- url_for(post.path) %>"
       data-tag="<% post.tags.forEach(function(tag, i){ %><%=tag.name%><% if (i+1<post.tags.length){%>,<%}})%>"
       data-author="<% if(theme.author && theme.author.on==true && post.author) {%><%=post.author %><%}%>" >
        <span class="post-title" title="<%=post.title %>"><%=post.title %></span>
        <span class="post-date" title="<%= date(post.date, 'YYYY-MM-DD HH:mm:ss')%>"><%= date(post.date, 'YYYY/MM/DD') %></span>
    </a>
    <% }) %>
    <div id="no-item-tips">

    </div>
</nav>
// /source/js/script.js
$searchInput.on("input", function (e) {
    inputChange();
});
$searchInput.on("change", function (e) {
    inputChange();
});
/*根据搜索条件,过滤文章列表*/
function inputChange() {
    var i;
    setTimeout(function () {
        $searchInput.focus()
    }, 50)
    var val = $searchInput.val().trim();
    $('#search-panel').show().siblings().hide()
    $outlineList.hide();
    if ($('#local-search-result').length>0) {
        if (val.length>3 && (val.substr(0,3).toLowerCase() === 'in:' || val.substr(0,3).toLowerCase()==='in:')) {
            $outlineList.hide();
            $('#title-list-nav').hide()
            $('#local-search-result').show();
            searchAll(val.substr(3))
        } else {
            $('#title-list-nav').show();
            $('#local-search-result').hide();
        }
    } else {
        $outlineList.hide();
        $('#title-list-nav').show();
    }
    var categories = $(".nav-left ul li>div.active").data('rel').split('<--->')
    // 处理特殊字符
    for (i = 0; i < categories.length; i++) {
        categories[i] =  categories[i]
          .replace(/(?=\/|\\|#|\(|\)|\[|\]|\.)/g, "\\")
    }
    var activeTitle = categories.join('.');
    var searchType = '';
    var containType = '';
    $('#no-item-tips').hide()
    $(".nav-right nav a .post-title .search-keyword").each(function () {
        $(this).parent().html($(this).parent().attr('title'))
    })
    if (val === "") {
        $(".nav-right nav a").css("display", "none");
        $(".nav-right nav a." + activeTitle).css("display", "block");
    } else if (val.substr(0, 1) === "#") {
        searchType = '标签'
        containType = '为'
        if (val.substr(1).length !== 0) {
            $(".nav-right nav a").css("display", "none");
            $(".nav-right nav").find("a." + activeTitle + ":contains_tag('" + val.substr(1) + "')").css("display", "block");
        }
    } else if (val.substr(0, 1) === "@") {
        searchType = '作者'
        containType= '为'
        if (val.substr(1).length !== 0) {
            $(".nav-right nav a").css("display", "none");
            $(".nav-right nav").find("a." + activeTitle + ":contains_author('" + val.substr(1) + "')").css("display", "block");
        }
    } else {
        searchType = '标题'
        containType = '包含'
        // $(".nav-right nav a").css("display", "none");
        $(".nav-right nav").find("a." + activeTitle + ":"+ ($('#search-panel > .icon-case-sensitive').hasClass('active') ? 'containsSensitive' : 'contains') + "('" + val + "')").css("display", "block");
        $(".nav-right nav a").each(function () {
            var title = $(this).children('.post-title').attr('title');
            for (i = 0; i < categories.length; i++) {
                if (!$(this).hasClass(categories[i])) {
                    $(this).css('display', 'none').children('.post-title').html(title)
                    return true;
                }
            }

            var caseSensitive = $('#search-panel > .icon-case-sensitive').hasClass('active');
            var vals = (caseSensitive ? val : val.toUpperCase()).split('');
            var inputReg = new RegExp(vals.join('[\\s\\S]*'));
            if (inputReg.test(caseSensitive ? title : title.toUpperCase())) {
                // 给匹配到的字符添加高亮
                var nowPos = 0;
                var titleHtml = title.split('')
                var titleCase = (caseSensitive ? title : title.toUpperCase()).split('')
                for (i = 0; i < vals.length; i++) {
                    nowPos = titleCase.indexOf(vals[i], nowPos)
                    titleHtml[nowPos] = ['<span class="search-keyword">', titleHtml[nowPos], '</span>'].join('')
                }
                $(this).css('display', 'block').children('.post-title').html(titleHtml.join(''))
            } else {
                $(this).css('display', 'none').children('.post-title').html(title)
            }
        })
    }
    if (val !== '') {
        $('#default-panel .icon-search').addClass('active')
        if (val === 'in:') {
            $('#no-item-tips').show().html('正在进行全局关键字搜索,请输入关键字');
        } else if (!val.startsWith('in:') && $(".nav-right nav a:visible").length === 0) {
            $('#no-item-tips').show().html('未在 <span>' + activeTitle + '</span> 分类中找到'+ searchType + containType + ' <span>' + val.replace(/^[@|#]/g,'') + '</span> 的文章');
        }
    } else {
        $('#default-panel .icon-search').removeClass('active')
    }
}

6.8 博客首页pagination放在最近文章的顶端,便于翻页

需要修改的文件为butterfly/layout/index.pug。改后的代码为:

extends includes/layout.pug

block content
  include ./includes/mixins/post-ui.pug
  #recent-posts.recent-posts
    include includes/pagination.pug
    +postUI
    include includes/pagination.pug

效果如下:

6.8a tag-hide

inline 在文本里面添加按钮隐藏内容,只限文字。格式为:

{% hideInline content,display,bg,color %}
  • content: 文本内容
  • display: 按钮显示的文字(可选)
  • bg: 按鈕的背景颜色(可选)
  • color: 按钮文字的颜色(可选)

例子如下:

哪个英文字母最酷? 因为西装裤(C装酷)

门里站着一个人?

6.9 字体修改

自定义 css并引入,不会的同学参考Hexo 博客添加自定义 css 和 js 文件

选择自己使用的字体,我用的是思源宋体。由于有些浏览器(如Safari和手机上的浏览器)不支持自己修改字体,这种时候就会使用系统的默认字体!

如何破局?需要在网页的头文件处<head></head>将需要设置的字体导入!具体的做法是在主题配置文件themes/butterfly/_config.yml作如下两个地方的修改:(这里是我的个性化修改,看官也可以根据自己的需要来设置,相关的字体在谷歌Fonts API处自行查找即可。)

# Global font settings
# Don't modify the following settings unless you know how they work (非必要不要修改)
font:
  global-font-size: 16.5px
  code-font-size:
  font-family: Times, Noto Serif SC
  code-font-family:

# Font settings for the site title and site subtitle
# 左上角網站名字 主頁居中網站名字
blog_title_font:
  font_link:
  font-family: Ma Shan Zheng
# Inject
# Insert the code to head (before '</head>' tag) and the bottom (before '</body>' tag)
# 插入代码到头部 </head> 之前 和 底部 </body> 之前
inject:
  head:
    - <link rel="preconnect" href="https://fonts.googleapis.com">
    - <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    - <link href="https://fonts.loli.net/css2?family=Noto+Serif+SC:wght@400;500;700&display=swap" rel="stylesheet"> #思源宋体
    - <link href="https://fonts.loli.net/css2?family=Ma+Shan+Zheng:wght@400;500;700&display=swap" rel="stylesheet"> #Mashanzheng
  bottom:
    - <script type='text/javascript' src='https://ajax.loli.net/ajax/libs/jquery/3.2.1/jquery.min.js'></script>
    - <script id="dsq-count-scr" src="//https-www-dhndzwxj-top.disqus.com/count.js" async></script>

高级应用 API

Google Fonts 还开放了一些接口(称为 Developer API),用于获取字体库的信息数据。

比如实时获取字体库实际可用的字体及其相关信息:

https://www.googleapis.com/webfonts/v1/webfonts?key=*YOUR-API-KEY*

这个请求的返回结果是一个 JSON 类型的数据,包括了每种字体的名称,样式种类(比如 regular,italic),版本,修改时间,包含的样式包的请求地址,等等。

请注意,在 URL 里面有一个 key,这个 key 是和您的 web 应用工程相联系的,只有注册过的 web 应用才能成功调用 Developer API。我们必须要在 Google Cloud Console 注册之后,才能获取这个 key。

关于这个 Developer API, 可以参阅这个链接

参考下面这边文章来修改头文件:Web使用思源字体前端 CDNJS 库及 Google Fonts、Ajax 和 Gravatar 国内加速服务

谷歌Fonts API网址:https://fonts.google.com/

解决谷歌字体API加载速度慢的问题:解决引入fonts.googleapis.com/css字体网页响应缓慢问题

6.10 网页侧边栏卡片

最重要的文件是butterfly/layout/includes/widget/index.pug

//- /butterfly/layout/includes/widget/index.pug

#aside-content.aside-content
  //- post
  if is_post()
    if showToc && theme.toc.style_simple
      .sticky_layout
        include ./card_post_toc.pug
    else
      !=partial('includes/widget/card_announcement', {}, {cache: true})
      !=partial('includes/widget/card_top_self', {}, {cache: true})      
      .sticky_layout
        !=partial('includes/widget/card_back', {}, {cache: true})
        if showToc
          include ./card_post_toc.pug
        !=partial('includes/widget/card_recent_post', {}, {cache: true})
        !=partial('includes/widget/card_ad', {}, {cache: true})
  else
    //- page
    !=partial('includes/widget/card_author', {}, {cache: true})
    !=partial('includes/widget/card_announcement', {}, {cache: true})
    !=partial('includes/widget/card_top_self', {}, {cache: true})      

    .sticky_layout
      !=partial('includes/widget/card_recent_post', {}, {cache: true})
      !=partial('includes/widget/card_categories', {}, {cache: true})
      !=partial('includes/widget/card_tags', {}, {cache: true})
      !=partial('includes/widget/card_webinfo', {}, {cache: true})
      !=partial('includes/widget/card_ad', {}, {cache: true})
      !=partial('includes/widget/card_newest_comment', {}, {cache: true})
      !=partial('includes/widget/card_archives', {}, {cache: true})
      !=partial('includes/widget/card_bottom_self', {}, {cache: true})

对于作者卡片butterfly/layout/includes/widget/card_author.pug,我需要两种形态:一方面是主页上内容要全面(如下面左图),另一方面是文章内它的内容要简略。对于后者,我新建了一个card_back.pug文件。

///butterfly/layout/includes/widget/card_back.pug

if theme.aside.card_author.enable
  .card-widget.card-info
    .is-center
      .avatar-img
        img(src=url_for(theme.avatar.img) onerror=`this.onerror=null;this.src='` + url_for(theme.error_img.flink) + `'` alt="avatar")

    if theme.aside.card_author.button.enable
      a#card-info-btn.button--animated(href=theme.aside.card_author.button.link)
        i(class=theme.aside.card_author.button.icon)
        span=theme.aside.card_author.button.text
  
    if(theme.social)
        .card-info-social-icons.is-center
          !=fragment_cache('social', function(){return partial('includes/header/social')}) 

6.11 时钟

  1. 安装插件,在博客根目录[Blogroot]下打开终端,运行以下指令:
npm install hexo-butterfly-clock-anzhiyu --save
  1. 添加配置信息,以下为写法示例 在站点配置文件_config.yml或者主题配置文件_config.butterfly.yml中添加
# electric_clock
# see https://anzhiy.cn/posts/fc18.html
electric_clock:
  enable: true # 开关
  priority: 5 #过滤器优先权
  enable_page: all # 应用页面
  exclude:
  # - /posts/
  # - /about/
  layout: # 挂载容器类型
    type: class
    name: sticky_layout
    index: 0
  loading: https://cdn.cbd.int/hexo-butterfly-clock-anzhiyu/lib/loading.gif #加载动画自定义
  clock_css: https://cdn.cbd.int/hexo-butterfly-clock-anzhiyu/lib/clock.min.css
  clock_js: https://cdn.cbd.int/hexo-butterfly-clock-anzhiyu/lib/clock.min.js
  ip_api: https://widget.qweather.net/simple/static/js/he-simple-common.js?v=2.0
  qweather_key: # 和风天气key
  gaud_map_key: # 高得地图web服务key
  default_rectangle: false # 开启后将一直显示rectangle位置的天气,否则将获取访问者的地理位置与天气
  rectangle: 112.982279,28.19409 # 获取访问者位置失败时会显示该位置的天气,同时该位置为开启default_rectangle后的位置
  1. 参数释义
参数 备选值/类型 释义
priority number 【可选】过滤器优先级,数值越小,执行越早,默认为10,选填
enable true/false 【必选】控制开关
enable_page path 【可选】填写想要应用的页面,如根目录就填’/’,分类页面就填’/categories/’。若要应用于所有页面,就填all,默认为all
exclude path 【可选】填写想要屏蔽的页面,可以多个。写法见示例。原理是将屏蔽项的内容逐个放到当前路径去匹配,若当前路径包含任一屏蔽项,则不会挂载。
layout.type id/class 【可选】挂载容器类型,填写id或class,不填则默认为id
layout.name text 【必选】挂载容器名称
layout.index 0和正整数 【可选】前提是layout.type为class,因为同一页面可能有多个class,此项用来确认究竟排在第几个顺位
loading URL 【可选】电子钟加载动画的图片
clock_css URL 【可选】电子钟样式CDN资源
clock_js URL 【可选】电子钟执行脚本CDN资源
ip_api URL 【可选】获取时钟IP的API
配置文件

6.n 主题配置文件

# Main menu navigation (導航目錄)
# see https://butterfly.js.org/posts/4aa8abbe/#導航菜單
# --------------------------------------

menu:
  主页: / || fas fa-home
  关于: /about/ || fas fa-heart
  结构||fa fa-heartbeat:
    目录: /categories/ || fas fa-folder-open
    归档: /archives/ || fas fa-archive
    标签: /tags/ || fas fa-tags

# Code Blocks (代碼相關)
# --------------------------------------

highlight_theme: light #  darker / pale night / light / ocean / mac / mac light / false
highlight_copy: true # copy button
highlight_lang: true # show the code language
highlight_shrink: false # true: shrink the code blocks / false: expand the code blocks | none: expand code blocks and hide the button
highlight_height_limit: false # unit: px
code_word_wrap: false

# copy settings
# copyright: Add the copyright information after copied content (複製的內容後面加上版權信息)
copy:
  enable: true
  copyright:
    enable: false
    limit_count: 0

# social settings (社交圖標設置)
# formal:
#   icon: link || the description
social:
  # fab fa-github: https://github.com/xxxxx || Github
  # fas fa-envelope: mailto:xxxxxx@gmail.com || Email

# search (搜索)
# see https://butterfly.js.org/posts/ceeb73f/#搜索系統
# --------------------------------------

# Algolia search
algolia_search:
  enable: false
  hits:
    per_page: 6

# Local search
local_search:
  enable: true
  preload: true #預加載,開啟後,進入網頁後會自動加載搜索文件。關閉時,只有點擊搜索按鈕後,才會加載搜索文件
  CDN:

# Math (數學)
# --------------------------------------
# About the per_page
# if you set it to true, it will load mathjax/katex script in each page (true 表示每一頁都加載js)
# if you set it to false, it will load mathjax/katex script according to your setting (add the 'mathjax: true' in page's front-matter)
# (false 需要時加載,須在使用的 Markdown Front-matter 加上 mathjax: true)

# MathJax
mathjax:
  enable: true
  per_page: false

# KaTeX
katex:
  enable: false
  per_page: false
  hide_scrollbar: true

# Image (圖片設置)
# --------------------------------------

# Favicon(網站圖標)
favicon: /private_img/favicon.png

# Avatar (頭像)
avatar:
  img: /private_img/yuyingnan.jpg #https://i.loli.net/2021/02/24/5O1day2nriDzjSu.png
  effect: false

# Disable all banner image
disable_top_img: false

# The banner image of home page
index_img: /private_img/bg0.png

# If the banner of page not setting, it will show the top_img
default_top_img: /private_img/bg1.png

# The banner image of archive page
archive_img:

# If the banner of tag page not setting, it will show the top_img
# note: tag page, not tags page (子標籤頁面的 top_img)
tag_img:

# The banner image of tag page
# format:
#  - tag name: xxxxx
tag_per_img:

# If the banner of category page not setting, it will show the top_img
# note: category page, not categories page (子分類頁面的 top_img)
category_img:

# The banner image of category page
# format:
#  - category name: xxxxx
category_per_img: /private_img/banner2.gif

cover:
  # display the cover or not (是否顯示文章封面)
  index_enable: false
  aside_enable: false
  archives_enable: false
  # the position of cover in home page (封面顯示的位置)
  # left/right/both
  position: both
  # When cover is not set, the default cover is displayed (當沒有設置cover時,默認的封面顯示)
  default_cover: /private_img/banner2.gif
    # - https://i.loli.net/2020/05/01/gkihqEjXxJ5UZ1C.jpg

# Replace Broken Images (替換無法顯示的圖片)
error_img:
  flink: /private_img/404.gif
  post_page: /private_img/banner.gif

# A simple 404 page
error_404:
  enable: true
  subtitle: '无法打开该网页'
  background: https://i.loli.net/2020/05/19/aKOcLiyPl2JQdFD.png

post_meta:
  page: # Home Page
    date_type: both # created or updated or both 主頁文章日期是創建日或者更新日或都顯示
    date_format: date # date/relative 顯示日期還是相對日期
    categories: true # true or false 主頁是否顯示分類
    tags: false # true or false 主頁是否顯示標籤
    label: true # true or false 顯示描述性文字
  post:
    date_type: both # created or updated or both 文章頁日期是創建日或者更新日或都顯示
    date_format: date # date/relative 顯示日期還是相對日期
    categories: true # true or false 文章頁是否顯示分類
    tags: true # true or false 文章頁是否顯示標籤
    label: true # true or false 顯示描述性文字

# wordcount (字數統計)
# see https://butterfly.js.org/posts/ceeb73f/#字數統計
wordcount:
  enable: true
  post_wordcount: true
  min2read: true
  total_wordcount: true

# Display the article introduction on homepage
# 1: description
# 2: both (if the description exists, it will show description, or show the auto_excerpt)
# 3: auto_excerpt (default)
# false: do not show the article introduction
index_post_content:
  method: 3
  length: 500 # if you set method to 2 or 3, the length need to config

# anchor
anchor:
  button:
    enable: false
    always_show: false
    icon: # the unicode value of Font Awesome icon, such as '\3423'
  auto_update: false # when you scroll in post, the URL will update according to header id.

# Post
# --------------------------------------

# toc (目錄)
toc:
  post: true
  page: false
  number: false #自动编号
  expand: true
  style_simple: false # for post

post_copyright:
  enable: false
  decode: false
  author_href:
  license: CC BY-NC-SA 4.0
  license_url: https://creativecommons.org/licenses/by-nc-sa/4.0/

# Sponsor/reward
reward:
  enable: true
  QR_code:
    - img: /private_img/wechat.png
      link:
      text: 微信
    - img: /private_img/alipay.jpg
      link:
      text: 支付宝

# Post edit
# Easily browse and edit blog source code online.
post_edit:
  enable: false
  # url: https://github.com/user-name/repo-name/edit/branch-name/subdirectory-name/
  # For example: https://github.com/jerryc127/butterfly.js.org/edit/main/source/
  url: https://github.com/dhndzwxj/hexo-backup/edit/master/source/

# Related Articles
related_post:
  enable: false
  limit: 6 # Number of posts displayed
  date_type: created # or created or updated 文章日期顯示創建日或者更新日

# figcaption (圖片描述文字)
photofigcaption: false

# post_pagination (分頁)
# value: 1 || 2 || false
# 1: The 'next post' will link to old post
# 2: The 'next post' will link to new post
# false: disable pagination
post_pagination: 1

# Displays outdated notice for a post (文章過期提醒)
noticeOutdate:
  enable: true
  style: flat # style: simple/flat
  limit_day: 30 # When will it be shown
  position: top # position: top/bottom
  message_prev: 本文上次更新距离今天已经过去
  message_next:, 文中内容可能已经过时,望周知。

# Share System (分享功能)
# --------------------------------------

# AddThis
# https://www.addthis.com/
addThis:
  enable: false
  pubid:

# Share.js
# https://github.com/overtrue/share.js
sharejs:
  enable: true
  sites: facebook,twitter,wechat,weibo,qq

# AddToAny
# https://www.addtoany.com/
addtoany:
  enable: false
  item: facebook,twitter,wechat,sina_weibo,facebook_messenger,email,copy_link

# Comments System
# --------------------------------------

comments:
  # Up to two comments system, the first will be shown as default
  # Choose: Disqus/Disqusjs/Livere/Gitalk/Valine/Waline/Utterances/Facebook Comments/Twikoo
  use: Disqus
  text: true # Display the comment name next to the button
  # lazyload: The comment system will be load when comment element enters the browser's viewport.
  # If you set it to true, the comment count will be invalid
  lazyload: true
  count: true # Display comment count in top_img
  card_post_count: false # Display comment count in Home Page

# disqus
# https://disqus.com/
disqus:
  shortname: https-www-dhndzwxj-top #disqus_ZdiIR8wtv4
  apikey: # For newest comments widget

# Alternative Disqus - Render comments with Disqus API
# DisqusJS 評論系統,可以實現在網路審查地區載入 Disqus 評論列表,兼容原版
# https://github.com/SukkaW/DisqusJS
disqusjs:
  shortname:
  apikey:
  option:

# livere (來必力)
# https://www.livere.com/
livere:
  uid:

# gitalk
# https://github.com/gitalk/gitalk
gitalk:
  client_id:
  client_secret:
  repo:
  owner:
  admin:
  option:

# valine
# https://valine.js.org
valine:
  appId: # leancloud application app id
  appKey: # leancloud application app key
  avatar: monsterid # gravatar style https://valine.js.org/#/avatar
  serverURLs: # This configuration is suitable for domestic custom domain name users, overseas version will be automatically detected (no need to manually fill in)
  bg: # valine background
  visitor: false
  option:

# waline - A simple comment system with backend support fork from Valine
# https://waline.js.org/
waline:
  serverURL: # Waline server address url
  bg: # waline background
  pageview: false
  option:

# utterances
# https://utteranc.es/
utterances:
  repo:
  # Issue Mapping: pathname/url/title/og:title
  issue_term: pathname
  # Theme: github-light/github-dark/github-dark-orange/icy-dark/dark-blue/photon-dark
  light_theme: github-light
  dark_theme: photon-dark

# Facebook Comments Plugin
# https://developers.facebook.com/docs/plugins/comments/
facebook_comments:
  app_id:
  user_id: # optional
  pageSize: 10 # The number of comments to show
  order_by: social # social/time/reverse_time
  lang: zh_TW # Language en_US/zh_CN/zh_TW and so on

# Twikoo
# https://github.com/imaegoo/twikoo
twikoo:
  envId:
  region:
  visitor: false
  option:

# Giscus
# https://giscus.app/
giscus:
  repo:
  repo_id:
  category_id:
  theme:
    light: light
    dark: dark
  option:

# Remark42
# https://remark42.com/docs/configuration/frontend/
remark42:
  host: # Your Host URL
  siteId: # Your Site ID
  option:

# Artalk
# https://artalk.js.org/guide/frontend/config.html
artalk:
  server:
  site:
  visitor: false
  option:

# Chat Services
# --------------------------------------

# Chat Button [recommend]
# It will create a button in the bottom right corner of website, and hide the origin button
chat_btn: false

# The origin chat button is displayed when scrolling up, and the button is hidden when scrolling down
chat_hide_show: false

# chatra
# https://chatra.io/
chatra:
  enable: false
  id:

# tidio
# https://www.tidio.com/
tidio:
  enable: false
  public_key:

# daovoice
# http://daovoice.io/
daovoice:
  enable: false
  app_id:

# gitter
# https://gitter.im/
gitter:
  enable: false
  room:

# crisp
# https://crisp.chat/en/
crisp:
  enable: false
  website_id:

# messenger
# https://developers.facebook.com/docs/messenger-platform/discovery/facebook-chat-plugin/
messenger:
  enable: false
  pageID:
  lang: zh_TW # Language en_US/zh_CN/zh_TW and so on

# Footer Settings
# --------------------------------------
footer:
  owner:
    enable: true
    since: 2020
  custom_text: 欢迎参观我的<a href='https://github.com/dhndzwxj' target='_blank'>github</a>个人主页!
  copyright: true # Copyright of theme and framework


# Analysis
# --------------------------------------

# Baidu Analytics
# https://tongji.baidu.com/web/welcome/login
baidu_analytics:

# Google Analytics
# https://analytics.google.com/analytics/web/
google_analytics:

# CNZZ Analytics
# https://www.umeng.com/
cnzz_analytics:

# Cloudflare Analytics
# https://www.cloudflare.com/zh-tw/web-analytics/
cloudflare_analytics:

# Microsoft Clarity
# https://clarity.microsoft.com/
microsoft_clarity:

# Advertisement
# --------------------------------------

# Google Adsense (谷歌廣告)
google_adsense:
  enable: false
  auto_ads: true
  js: https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js
  client:
  enable_page_level_ads: true

# Insert ads manually (手動插入廣告)
# ad:
#   index:
#   aside:
#   post:

# Verification (站長驗證)
# --------------------------------------

site_verification:
  # - name: google-site-verification
  #   content: xxxxxx
  # - name: baidu-site-verification
  #   content: xxxxxxx

# Beautify/Effect (美化/效果)
# --------------------------------------

# Theme color for customize
# Notice: color value must in double quotes like "#000" or may cause error!

theme_color:
  enable: true
  main: '#E68258' #'#00B300' #'#1A1A1A' #滑动条的颜色,背景以上的颜色
  paginator: "#e68258" #底部网页分页数字的颜色
  button_hover: "#FF9966" #鼠标停留在button上颜色渐变的效果;选择了比主题色更浅一号的橙色
  text_selection: '#9F6506' #'#000066' #"#0000CC" #选中文字后的颜色
  link_color: "#b86ed0" #网页链接的颜色
  meta_color: "#858585"
  hr_color: "#A4D8FA"
  code_foreground: "#F47466"
  code_background: "#FFFF00"
  toc_color: '#E68258' #"#e68258" #"#00c4b6"
  blockquote_padding_color: "#e68258" #引用部分左边长条边框颜色
  blockquote_background_color: "#e68258" #引用部分的背景颜色


# The top_img settings of home page
# default: top img - full screen, site info - middle (默認top_img全屏,site_info在中間)
# The position of site info, eg: 300px/300em/300rem/10% (主頁標題距離頂部距離)
index_site_info_top:
# The height of top_img, eg: 300px/300em/300rem (主頁top_img高度)
index_top_img_height:

# The user interface setting of category and tag page (category和tag頁的UI設置)
# index - same as Homepage UI (index 值代表 UI將與首頁的UI一樣)
# default - same as archives UI 默認跟archives頁面UI一樣
category_ui: # 留空或 index
tag_ui: # 留空或 index

# Website Background (設置網站背景)
# can set it to color or image (可設置圖片 或者 顔色)
# The formal of image: url(http://xxxxxx.com/xxx.jpg)
background:

# Footer Background
footer_bg: true

# the position of bottom right button/default unit: px (右下角按鈕距離底部的距離/默認單位為px)
rightside-bottom:

# Enter transitions (開啓網頁進入效果)
enter_transitions: true

# Background effects (背景特效)
# --------------------------------------

# canvas_ribbon (靜止彩帶背景)
# See: https://github.com/hustcc/ribbon.js
canvas_ribbon:
  enable: false
  size: 150
  alpha: 0.6
  zIndex: -1
  click_to_change: false
  mobile: false

# Fluttering Ribbon (動態彩帶)
canvas_fluttering_ribbon:
  enable: true
  mobile: true

# canvas_nest
# https://github.com/hustcc/canvas-nest.js
canvas_nest:
  enable: false
  color: '0,0,255' #color of lines, default: '0,0,0'; RGB values: (R,G,B).(note: use ',' to separate.)
  opacity: 0.7 # the opacity of line (0~1), default: 0.5.
  zIndex: -1 # z-index property of the background, default: -1.
  count: 99 # the number of lines, default: 99.
  mobile: false

# Typewriter Effect (打字效果)
# https://github.com/disjukr/activate-power-mode
activate_power_mode:
  enable: false
  colorful: true # open particle animation (冒光特效)
  shake: true #  open shake (抖動特效)
  mobile: false

# Mouse click effects: fireworks (鼠標點擊效果: 煙火特效)
fireworks:
  enable: false
  zIndex: 9999 # -1 or 9999
  mobile: false

# Mouse click effects: Heart symbol (鼠標點擊效果: 愛心)
click_heart:
  enable: false
  mobile: false

# Mouse click effects: words (鼠標點擊效果: 文字)
ClickShowText:
  enable: false
  text:
    # - I
    # - LOVE
    # - YOU
  fontSize: 16.5px
  random: false
  mobile: false

# Default display mode (網站默認的顯示模式)
# light (default) / dark
display_mode: light

# Beautify (美化頁面顯示)
beautify:
  enable: false
  field: post # site/post
  title-prefix-icon: # '\f0c1'
  title-prefix-icon-color: # '#F47466'

# Global font settings
# Don't modify the following settings unless you know how they work (非必要不要修改)
font:
  global-font-size: 16.5px
  code-font-size:
  font-family: Times, Noto Serif SC
  code-font-family:

# Font settings for the site title and site subtitle
# 左上角網站名字 主頁居中網站名字
blog_title_font:
  font_link:
  font-family: Ma Shan Zheng

# The setting of divider icon (水平分隔線圖標設置)
hr_icon:
  enable: true
  icon: # the unicode value of Font Awesome icon, such as '\3423'
  icon-top:

# the subtitle on homepage (主頁subtitle)
subtitle:
  enable: true
  # Typewriter Effect (打字效果)
  effect: true
  # Effect Speed Options (打字效果速度參數)
  startDelay: 300 # time before typing starts in milliseconds
  typeSpeed: 150 # type speed in milliseconds
  backSpeed: 50 # backspacing speed in milliseconds
  # loop (循環打字)
  loop: true
  # source 調用第三方服務
  # source: false 關閉調用
  # source: 1  調用一言網的一句話(簡體) https://hitokoto.cn/
  # source: 2  調用一句網(簡體) http://yijuzhan.com/
  # source: 3  調用今日詩詞(簡體) https://www.jinrishici.com/
  # subtitle 會先顯示 source , 再顯示 sub 的內容
  source: false
  # 如果關閉打字效果,subtitle 只會顯示 sub 的第一行文字
  sub:
    - 随手写一写,揭晓这世界

# Loading Animation (加載動畫)
preloader:
  enable: false
  # source
  # 1. fullpage-loading
  # 2. pace (progress bar)
  source: 1
  # pace theme (see https://codebyzach.github.io/pace/)
  pace_css_url:

# aside (側邊欄)
# --------------------------------------

aside:
  enable: true
  hide: false
  button: true
  mobile: true # display on mobile
  position: left # left or right
  display:
    archive: true
    tag: true
    category: true
  card_author:
    enable: true
    description:
    button:
      enable: true
      # icon: fab fa-github
      text: 回到首页
      link: / 
  card_announcement:
    enable: false
    content: 随手写一些,揭晓这世界
  card_recent_post:
    enable: true
    limit: 5 # if set 0 will show all
    sort: updated # date or updated
    sort_order: 'updated' # Don't modify the setting unless you know how it works
  card_categories:
    enable: true
    limit: 0 # if set 0 will show all
    expand: false # none/true/false
    sort_order: # Don't modify the setting unless you know how it works
  card_tags:
    enable: false
    limit: 0 # if set 0 will show all
    color: false
    sort_order: # Don't modify the setting unless you know how it works
  card_archives:
    enable: false
    type: monthly # yearly or monthly
    format: MMMM YYYY # eg: YYYY年MM月
    order: -1 # Sort of order. 1, asc for ascending; -1, desc for descending
    limit: 8 # if set 0 will show all
    sort_order: # Don't modify the setting unless you know how it works
  card_webinfo:
    enable: true
    post_count: true
    last_push_date: true
    sort_order: # Don't modify the setting unless you know how it works

# busuanzi count for PV / UV in site
# 訪問人數
busuanzi:
  site_uv: true
  site_pv: true
  page_pv: true

# Time difference between publish date and now (網頁運行時間)
# Formal: Month/Day/Year Time or Year/Month/Day Time
runtimeshow:
  enable: true
  publish_date: 8/21/2020 00:00:00 #发布时间

# Aside widget - Newest Comments
newest_comments:
  enable: true
  sort_order: # Don't modify the setting unless you know how it works
  limit: 6
  storage: 10 # unit: mins, save data to localStorage
  avatar: true

# Bottom right button (右下角按鈕)
# --------------------------------------

# Conversion between Traditional and Simplified Chinese (簡繁轉換)
translate:
  enable: true
  # The text of a button
  default:# the language of website (1 - Traditional Chinese/ 2 - Simplified Chinese)
  defaultEncoding: 2
  # Time delay
  translateDelay: 0
  # The text of the button when the language is Simplified Chinese
  msgToTraditionalChinese: '繁'
  # The text of the button when the language is Traditional Chinese
  msgToSimplifiedChinese: '簡'

# Read Mode (閲讀模式)
readmode: true

# dark mode
darkmode:
  enable: true
  # Toggle Button to switch dark/light mode
  button: true
  # Switch dark/light mode automatically (自動切換 dark mode和 light mode)
  # autoChangeMode: 1  Following System Settings, if the system doesn't support dark mode, it will switch dark mode between 6 pm to 6 am
  # autoChangeMode: 2  Switch dark mode between 6 pm to 6 am
  # autoChangeMode: false
  autoChangeMode: false

# Don't modify the following settings unless you know how they work (非必要請不要修改 )
# Choose: readmode,translate,darkmode,hideAside,toc,chat,comment
# Don't repeat 不要重複
rightside_item_order:
  enable: false
  hide: # readmode,translate,darkmode,hideAside
  show: # toc,chat,comment

# Lightbox (圖片大圖查看模式)
# --------------------------------------
# You can only choose one, or neither (只能選擇一個 或者 兩個都不選)

# medium-zoom
# https://github.com/francoischalifour/medium-zoom
medium_zoom: false

# fancybox
# http://fancyapps.com/fancybox/3/
fancybox: true

# Tag Plugins settings (標籤外掛)
# --------------------------------------

# mermaid
# see https://github.com/mermaid-js/mermaid
mermaid:
  enable: true
  # built-in themes: default/forest/dark/neutral
  theme:
    light: default
    dark: dark

# Note (Bootstrap Callout)
note:
  # Note tag style values:
  #  - simple    bs-callout old alert style. Default.
  #  - modern    bs-callout new (v2-v3) alert style.
  #  - flat      flat callout style with background, like on Mozilla or StackOverflow.
  #  - disabled  disable all CSS styles import of note tag.
  style: flat
  icons: true
  border_radius: 3
  # Offset lighter of background in % for modern and flat styles (modern: -12 | 12; flat: -18 | 6).
  # Offset also applied to label tag variables. This option can work with disabled note tag.
  light_bg_offset: 0

# other
# --------------------------------------

# Pjax
# It may contain bugs and unstable, give feedback when you find the bugs.
# https://github.com/MoOx/pjax
pjax:
  enable: false
  exclude:
    # - xxxx
    # - xxxx

# Inject the css and script (aplayer/meting)
aplayerInject:
  enable: false
  per_page: true

# Snackbar (Toast Notification 彈窗)
# https://github.com/polonel/SnackBar
# position 彈窗位置
# 可選 top-left / top-center / top-right / bottom-left / bottom-center / bottom-right
snackbar:
  enable: false
  position: bottom-left
  bg_light: '#49b1f5' # The background color of Toast Notification in light mode
  bg_dark: '#1f1f1f' # The background color of Toast Notification in dark mode

# https://instant.page/
# prefetch (預加載)
instantpage: false

# https://github.com/vinta/pangu.js
# Insert a space between Chinese character and English character (中英文之間添加空格)
pangu:
  enable: false
  field: site # site/post

# Lazyload (圖片懶加載)
# https://github.com/verlok/vanilla-lazyload
lazyload:
  enable: false
  field: site # site/post
  placeholder:
  blur: false

# PWA
# See https://github.com/JLHwung/hexo-offline
# ---------------
# pwa:
#   enable: false
#   manifest: /pwa/manifest.json
#   apple_touch_icon: /pwa/apple-touch-icon.png
#   favicon_32_32: /pwa/32.png
#   favicon_16_16: /pwa/16.png
#   mask_icon: /pwa/safari-pinned-tab.svg

# Open graph meta tags
# https://developers.facebook.com/docs/sharing/webmasters/
Open_Graph_meta:
  enable: true
  option:
    # twitter_card:
    # twitter_image:
    # twitter_id:
    # twitter_site:
    # google_plus:
    # fb_admins:
    # fb_app_id:

# Add the vendor prefixes to ensure compatibility
css_prefix: true

# Inject
# Insert the code to head (before '</head>' tag) and the bottom (before '</body>' tag)
# 插入代码到头部 </head> 之前 和 底部 </body> 之前
inject:
  head:
    - <link rel="preconnect" href="https://fonts.googleapis.com">
    - <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    - <link href="https://fonts.loli.net/css2?family=Noto+Serif+SC:wght@400;500;700&display=swap" rel="stylesheet"> #思源宋体
    - <link href="https://fonts.loli.net/css2?family=Ma+Shan+Zheng:wght@400;500;700&display=swap" rel="stylesheet"> #Mashanzheng
    # - <link rel="stylesheet" href="/xxx.css">
  bottom:
    - <script type='text/javascript' src='https://ajax.loli.net/ajax/libs/jquery/3.2.1/jquery.min.js'></script>
    - <script id="dsq-count-scr" src="//https-www-dhndzwxj-top.disqus.com/count.js" async></script>
    # - <script src="xxxx"></script>

# CDN
# Don't modify the following settings unless you know how they work
# 非必要請不要修改
CDN:
  # The CDN provider of internal scripts (主題內部 js 的 cdn 配置)
  # option: local/jsdelivr/unpkg/cdnjs/custom
  # Dev version can only choose. ( dev版的主題只能設置為 local )
  internal_provider: local

  # The CDN provider of third party scripts (第三方 js 的 cdn 配置)
  # option: local/jsdelivr/unpkg/cdnjs/custom
  # when set it to local, you need to install hexo-butterfly-extjs
  third_party_provider: jsdelivr

  # Add version number to CDN, true or false  
  version: false

  # Custom format
  # For example: https://cdn.staticfile.org/${cdnjs_name}/${version}/${min_cdnjs_file}
  custom_format:

  option:
    # main_css:
    # main:
    # utils:
    # translate:
    # local_search:
    # algolia_js:
    # algolia_search_v4:
    # instantsearch_v4:
    # pjax:
    # gitalk:
    # gitalk_css:
    # blueimp_md5:
    # valine:
    # disqusjs:
    # disqusjs_css:
    # twikoo:
    # waline_js:
    # waline_css:
    # sharejs:
    # sharejs_css:
    # mathjax:
    # katex:
    # katex_copytex:
    # mermaid:
    # canvas_ribbon:
    # canvas_fluttering_ribbon:
    # canvas_nest:
    # lazyload:
    # instantpage:
    # typed:
    # pangu:
    # fancybox_css_v4:
    # fancybox_v4:
    # medium_zoom:
    # snackbar_css:
    # snackbar:
    # activate_power_mode:
    # fireworks:
    # click_heart:
    # ClickShowText:
    # fontawesomeV6:
    # flickr_justified_gallery_js:
    # flickr_justified_gallery_css:
    # aplayer_css:
    # aplayer_js:
    # meting_js:
    # prismjs_js:
    # prismjs_lineNumber_js:
    # prismjs_autoloader:
    # artalk_js:
    # artalk_css:

7 Git 教程及博客备份

这一步是要把我们本地的所有个人文件在网上进行备份——这很有必要,因为保不齐哪一天我们手残把电脑格式化了。不过想实现备份功能,要求我们对Git的一些内容有必要的了解,否则做起备份来往往会盲人摸象,反而自己把自己的文件用命令行代码清空了(这里有我的故事,有酒么?)。

Git教程:点这里

7.1 生成 SSH Key

本例以 Github 为例作为远程仓库,如果你没有 Github 可以在官网 https://github.com/注册。由于你的本地 Git 仓库和 GitHub 仓库之间的传输是通过SSH加密的,所以我们需要配置验证信息:

使用以下命令生成 SSH Key:

ssh-keygen -t rsa -C "youremail@example.com"
ssh-keygen -t rsa -C "dhndzwxj@ruc.edu.cn"
ssh-keygen -t ed25519 -C "dhndzwxj@ruc.edu.cn"

查看当前的远程库:(执行时加上-v参数,你还可以看到每个别名的实际链接地址。)

$ git remote
backup
$ git remote -v
backup	git@github.com:dhndzwxj/hexo-backup.git (fetch)
backup	git@github.com:dhndzwxj/hexo-backup.git (push)

7.2 查看远程分支的更新状态

git remote show [仓库别名]
* remote backup
  Fetch URL: git@github.com:dhndzwxj/hexo-backup.git
  Push  URL: git@github.com:dhndzwxj/hexo-backup.git
  HEAD branch: master
  Remote branch:
    master tracked
  Local ref configured for 'git push':
    master pushes to master (up to date)

Fetch URL和Push URL表示fetchpush的链接,master tracked表示本地和远程建立了连接。up to date表示本地和远程同步状态,local out of date表示本地落后于远程了。

7.3 博客备份

注意每次备份前,都需要commit一下~(回忆前面讲的内容,如果不commit就只是在本地有缓存,没有上传到缓存区)然后下面一键三连:

➜  dlog git:(master)git add .
➜  dlog git:(master)git commit -m '211023版本提交'
➜  dlog git:(master)git push backup master

后面的backup是远程仓库名,master是backup这个远程仓库的默认分支名,这些名字都可以自己个性化命名,它们都不是代码中的关键字

不过在提交的时候,往往出现主题文件无法commit的情况!为什么呢?因为主题文件也是从仓库里拉取下来的,它被关联到了作者的git仓库,所以提交不上去

怎么解决呢?

①从暂存区删除该文件夹

git rm --cache themes/butterfly451

②把 themes/主题名/.git文件夹到放到别的位置,比方说桌面。记得把 themes/主题名/.gitignore里的 _config去掉

git status查看当前状态

④直接按步骤提交就行了

git add themes/butterfly470/

让后三连即可。

7.3.a 多设备备份不同步的问题

有时候可能忘记先从仓库拉代码(git pull),直接在不是新版本的文件上进行编辑,这样在git push代码的时候会遭遇reject 的情况,也就是版本冲突了。

建议的解决方法是,第一步先把此次修改的文件备份到一个新的文件夹中。第二步,把版本倒退。先看版本号

git log

然后倒退回没有争议的版本:【HEAD->master是本地的版本,backup/master是github仓库中的版本,此处的例子里二者是同步的,出问题的时候是不同步的。commit后面一大长串字母是版本号。按键盘上的Q键可退出此版本号界面。

git reset --hard [版本号]

第三步,按照备份到其他文件夹的文件,把老版本的对应文件进行修改。

标签

7.4 多设备拉取、备份实战

7.4.1 往返于多个设备之间:流程式

在家里上传代码

git remote add [远程仓库别名] [远程仓库地址]
git push [远程仓库别名] [分支]

到公司新电脑上第一次获取代码

git clone [远程仓库地址]
# 切换分支
git checkout [分支]

在Windows下,如果仓库中的一个pdf文件的路径太长,导致clone到本地之后无法checkout,解决方法是:

git reset
git config core.protectNTFS false
git checkout

在公司进行开发(约定在dev分支进行开发)

#切换到dev分支进行开发
git checkout dev
#把master分支合并到dev【仅一次】
git merge master
#修改代码
#提交代码
git add .
git commit -m '记录信息'
git push [远程仓库别名] dev

回到家中继续写代码

#切换到dev分支进行开发
git checkout dev
#拉代码
git pull [远程仓库别名] dev
#继续开发
#提交代码
git add .
git commit -m '记录信息'
git push [远程仓库别名] dev

……以上流程循环往复。

开发完毕,要上线

# 把dev分支的代码合并到master分支上
git checkout master
git merge dev
git push [远程仓库别名] master
# 把dev分支也推送到远程
git checkout dev
git merge master
git push [远程仓库别名] dev

7.4.2 往返于多个设备之间:分布式(多设备共同开发、合并)

在设备1和设备2共同开发,将代码合并的过程中可能会发生冲突。

怎么办?手动解决!

7.5 上传超过100M的文件

参考文章轻松上传超过100M的文件至GitHub

就需要借助Git LFS。首先下载git-lfs(https://github.com/git-lfs/git-lfs),安装好后进入本地仓库目录,执行下面的命令。

git lfs track "file"

file是需要上传的大文件。执行完命令后会发现目录下生成了一个".gitattributes"文件,文件内记录了我们要上传文件的信息。只有先把".gitattributes"传上去,才可以上传大文件。

git add .gitattributes
git commit -m "submit file"
git push -u origin master

上传完毕后,开始上传大文件。

git add file
git commit -m "add file"
git push -u origin master

如果发现自己空间不足,可以删去一些大文件或者购买更多的空间。

如果在上传过程中出现如下报错:

batch response: Git LFS is disabled for this repository.

Uploading LFS objects: 0% (0/1), 0 B | 0 B/s, done

就说明你的账号被冻结了,需要在GitHub后台提交解封申请。https://support.github.com/contact

工作日一般几个小时就会帮你把账号解封,解封后就可以继续上传大文件啦~

8. 将博客站点托管到Vercel上!

为什么使用Vercel托管网站?就我的个人使用体验而言,优点有以下几点:

  • 服务器在国内(据我查到的资料是在中国台湾),网页加载速度比Github Page快太多;
  • 支持绑定Github账号,可以直接导入Github仓库
  • 我看到的资料里,在Github上hexo d后,Vercel这边也会自动更新,比较省心。

下面看看怎么做吧~首先上Vercel官网,注册一个自己的账号(点击网页右上角的“Sign Up”),然后(如下图)用Github账号就可以!

登陆成功后,我们下一步是在Vercel上新建一个项目(Project,如下图左),导入一个Git仓库(如下图右),点击Import就可以了

导入后便进入下面这个页面(下图左)。这里的意思是我们要不要建立一个开发团队,由于这项服务是收费的,所以我们当然就不需要啦,点击“Skip”即可跳过此步。然后进入下一个页面(下图右),直接点击“Deploy”,就可以将我们的Github项目部署到Vercel上面啦!做到这里,基本上就大功告成了!(下图右里面有一个选项叫做“PROJECT NAME”,也就是项目名称的意思,这个是能自己取名字的哦!

最后一步是要知道,我们生成的Vercel网页的网址是什么?

如下图1,先在自己的主页上打开自己刚刚部署好的项目,然后点击图2的“View Domains”,即查看自己的域名。在图3中点击“Edit”,即对自己的域名进行编辑,最后在图4红框处进行改名(后面的".vercel.app"不能改,只能改前面的内容),点击Save进行保存。

图1

图2

图3

图4

vercel的IP地址为76.76.21.98。

9.绑定个人域名

  • 先买一个域名。我买的是阿里云的域名
  • 对域名进行解析。添加A记录类型。共有两类主机记录。记录值对应于服务器的IP地址。
    • @
    • www
  • 如何获得IP地址?方式为ping你的服务器,得到一个IP。
ping badu.com

我自己搞了个腾讯云的服务器,把网页文件托管在腾讯云的服务器上。为了获得这个服务器的IP地址,我输入代码:

ping my-hexo-3gi6oo0367a4730b-1302940033.tcloudbaseapp.com

10 Nas托管静态网页

参考文章虚拟机玩群晖

N-1. 博客站点根目录文件配置

对博客站点根目录进行个性化配置。这是我的个人设置,大家可以根据自己的需要更改。

博客站点根目录文件配置
# Hexo Configuration
## Docs: https://hexo.io/docs/configuration.html
## Source: https://github.com/hexojs/hexo/

# Site
title: 小荷才露尖尖角
subtitle: 蜻蜓
description: '随手写一写,揭晓这世界'   #网站描述
keywords: 马克思主义政治经济学
author: 揭晓                            #你的名字
language: zh-CN                         #网站使用的语言
timezone: Asia/Shanghai                 #网站时区

# URL
## Set your site url here. For example, if you use GitHub Page, set url as 'https://username.github.io/project'
url: https://dhndzwxj.github.io # http://example.com
root: /
permalink: :abbrlink.html #前面绝对不能加'/'
permalink_defaults:
pretty_urls:
  trailing_index: true # Set to false to remove trailing 'index.html' from permalinks
  trailing_html: true # Set to false to remove trailing '.html' from permalinks

#abbrlink配置
abbrlink:
  alg: crc32  # 算法:crc16(default) and crc32
  rep: dec    # 进制:dec(default) and hex  


# Directory 目录配置
source_dir: source    #资源文件夹,这个文件夹用来存放内容
public_dir: public    #公共文件夹,这个文件夹用于存放生成的站点文件
tag_dir: tags         #标签文件夹
archive_dir: archives #归档文件夹
category_dir: categories  #分类文件夹
code_dir: downloads/code  #Include code 文件夹
i18n_dir: :lang       #国际化文件夹
skip_render:          #跳过指定文件的渲染,您可使用 glob 来配置路径

# Writing 写作配置
new_post_name: :title.md  #新文章的文件名称
default_layout: post      #默认文章布局
titlecase: false # Transform title into titlecase
external_link: 
  enable: true # Open external links in new tab
  field: site # Apply to the whole site
  exclude: ''
filename_case: 0          #把文件名称转换为 (1) 小写或 (2) 大写
render_drafts: false      #显示草稿
post_asset_folder: true  #是否启动资源文件夹
relative_link: true       #把链接改为与根目录的相对位址
future: true
highlight:
  enable: false
  line_number: false #决定了代码块左侧是否有行数
  auto_detect: false
  tab_replace: ''
  wrap: true
  hljs: true
prismjs:
  enable: true
  preprocess: true
  line_number: true #决定了代码块左侧是否有行数
  tab_replace: ''

# Home page setting
# path: Root path for your blogs index page. (default = '')
# per_page: Posts displayed per page. (0 = disable pagination)
# order_by: Posts order. (Order by date descending by default)
index_generator:
  path: ''
  per_page: 10
  order_by: -date
# 归档页面
archive_generator:
  per_page: 50
  yearly: true
  monthly: true
#效果,首页每间隔10篇文章就分页,归档页每间隔50篇文章才分页。

# Category & Tag
default_category: #uncategorized
category_map:
tag_map:

# Metadata elements
## https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta
meta_generator: true

# Date / Time format
## Hexo uses Moment.js to parse and display date
## You can customize the date format as defined in
## http://momentjs.com/docs/#/displaying/format/
date_format: YYYY-MM-DD
time_format: HH:mm:ss
## updated_option supports 'mtime', 'date', 'empty'
updated_option: 'mtime'

# Pagination
## Set per_page to 0 to disable pagination
per_page: 10
pagination_dir: page

# Include / Exclude file(s)
## include:/exclude: options only apply to the 'source/' folder
include:
exclude:
ignore:

#搜索功能
search:
  path: search.xml
  field: post
  content: true
  format: html
# #搜索功能2
# algolia:
#   applicationID: 'applicationID'
#   apiKey: 'apiKey'
#   indexName: '...'
# # 文章加密
encrypt: # hexo-blog-encrypt
  abstract: 有东西被加密了, 请输入密码查看.
  message: 您好, 这里需要密码.
  tags:
  - {name: tagName, password: 密码A}
  - {name: tagName, password: 密码B}
  wrong_pass_message: 抱歉, 这个密码看着不太对, 请再试试.
  wrong_hash_message: 抱歉, 这个文章不能被校验, 不过您还是能看看解密后的内容.

# # mermaid chart
# mermaid: ## mermaid url https://github.com/knsv/mermaid
#   enable: true  # default true
#   version: "9.2.2" # default v7.1.2
#   options:  # find more api options from https://github.com/knsv/mermaid/blob/master/src/mermaidAPI.js
#     #startOnload: true  // default true


# # math
# math:
#   engine: 'mathjax' 
#   mathjax:  
# _config.yml
math:
  katex:
    css: 'https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css'
    options:
      throwOnError: false
  mathjax:
    css: 'https://cdn.jsdelivr.net/npm/hexo-math@4.0.0/dist/style.css'
    options:
      conversion:
        display: false
      tex:
      svg:

# electric_clock
# see https://akilar.top/posts/4e39cf4a/
electric_clock:
  enable: true # 开关
  priority: 5 #过滤器优先权
  enable_page: all # 应用页面
  exclude:
    - /posts/
    - /about/
  layout: # 挂载容器类型
    type: class
    name: sticky_layout
    index: 0
  loading: https://npm.elemecdn.com/hexo-butterfly-clock/lib/loading.gif #加载动画自定义
  clock_css: https://npm.elemecdn.com/hexo-butterfly-clock/lib/clock.min.css #电子钟样式CDN资源
  clock_js: https://npm.elemecdn.com/hexo-butterfly-clock/lib/clock.min.js #电子钟执行脚本CDN资源
  ip_api: https://pv.sohu.com/cityjson?ie=utf-8 #获取时钟IP的API

# Deployment
## Docs: https://hexo.io/docs/one-command-deployment
deploy:
  type: git
  # repo: git@gitee.com:dhndzwxj/dhndzwxj.gitee.io.git
  repo: git@github.com:dhndzwxj/dhndzwxj.github.io.git
  branch: master
# Extensions
## Plugins: https://hexo.io/plugins/
plugins:
  # - hexo-footnote
## Themes: https://hexo.io/themes/
theme: butterfly451
# theme: lanscape

N. 拓展知识

1. CSS预处理器

CSS指的是层叠样式表(Cascading Style Sheets),描述了如何在屏幕、纸张或其他媒体上显示 HTML 元素。可以同时控制多张网页的布局,外部样式存储在css文件中。一句话,css的目的是对html中每一个组块(如<a><p><i><h1>)进行个性化定制!

选择器

CSS 规则集(rule-set)由选择器声明块组成:

  • 选择器指向您需要设置样式的 HTML 元素。(css是对html中各种元素进行个性化定制的服务!)
  • 声明块包含一条或多条用分号分隔的声明。每条声明都包含一个 CSS 属性名称和一个值,以冒号分隔。多条 CSS 声明用分号分隔,声明块用花括号括起来。
返回前面

例子:

p {
  color: red;
  text-align: center;
}

例子解释

  • pCSS 中的选择器(它指向要设置样式的 HTML 元素:<p>)。
  • color 是属性,red 是属性值
  • text-align 是属性,center 是属性值

CSS(Cascading Style Sheet-级联样式表)的缺陷也很明显。这种「面向命名语言」,不太友好,格式死板,低复制效率。css 预处理器就是这样被创造出来,弥补了直接写 css 的一些缺憾:

  • 语法不够强大,比如无法嵌套书写导致模块化开发中需要书写很多重复的选择器;
  • 没有变量和合理的样式复用机制,使得逻辑上相关的属性值必须以字面量的形式重复输出,导致难以维护。

Stylus:2010年产生,来自Node.js社区,主要用来给Node项目进行CSS预处理支持,在此社区之内有一定支持者。Stylus 中文文档

Stylus 是一个高效、动态以及丰富的 CSS 预处理器。它同时支持缩进的和通俗的两种风格的 CSS 语法风格。Stylus 扩展名为「 *.styl 」,Nib是 Stylus 的应用的类库。给你的「* .styl 」添加 Nib 的最快方式是克隆 Nib 的 Git 版本库并引入,因为有了 Nib,Stylus 的高效性才更为突出。Stylus 在容错性上的突出特性也十分吸引我,你可以在一个 Stylus 文件里这样写,且它们都会被编译成标准 css:

/*style.styl*/
/*类似于CSS标准语法*/
h1 {
  color: #963;
  background-color:#333;
}
/*省略大括号({})*/
h1 
  color: #963;
  background-color: #333;
/*省略大括号({})和分号(;)*/
h1
  color:#963
  background-color:#333

Try Stylus!

这一部分参考知乎徐小七七

body,html
    margin:0
    padding:0

编译成:

body,
html {
  margin: 0;
  padding: 0;
}

stylus : 强大的功能丰富的语言

-pos(type, args)
  i = 0
  position: unquote(type)
  {args[i]}: args[i + 1] is a 'unit' ? args[i += 1] : 0
  {args[i += 1]}: args[i + 1] is a 'unit' ? args[i += 1] : 0

absolute()
  -pos('absolute', arguments)

fixed()
  -pos('fixed', arguments)

#prompt
  absolute: top 150px left 5px
  width: 200px
  margin-left: -(@width / 2)

#logo
  fixed: top left

编译成

#prompt {
  position: absolute;
  top: 150px;
  left: 5px;
  width: 200px;
  margin-left: -100px;
}
#logo {
  position: fixed;
  top: 0;
  left: 0;
}

stylus:nibstylus插件

@import 'nib'
body
  background: linear-gradient(20px top, white, black) 

编译成

body {
  background: -webkit-linear-gradient(20px top, #fff, #000);
  background: -moz-linear-gradient(20px top, #fff, #000);
  background: -o-linear-gradient(20px top, #fff, #000);
  background: -ms-linear-gradient(20px top, #fff, #000);
  background: linear-gradient(20px top, #fff, #000);
}

2.JavaScript 实例

https://www.w3school.com.cn/js/js_examples.asp

前面的html是网页页面的主要内容,css是格式化定制页面外观的代码。而JavaScript的作用让上面的内容“动起来”,是让页面内容发生改变的代码。

JavaScript是一种脚本语言。脚本语言又被称为扩建的语言,或者动态语言,是一种编程语言,用来控制软件应用程序,脚本通常以文本(如ASCII)保存,只在被调用时进行解释或编译。

根据我的理解,JavaScript就是网页代码中的“函数”(function),它可以根据开发者的目的对网页内容、属性进行一些改变。

语法结构

<script>
	...  
</script>

弹框的脚本:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8"> 
    <title>无标题文档</title>
    <script>
      alert("Welcome! Can I help you?");
    </script>
  </head>
  <body>
    ..
  </body>
</html>

如果将JavaScript代码写在body内,需要这么写:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8"> 
    <title>无标题文档</title>
  </head>
  <body>
    <script>
      document.write("<h1>Welcome!</h1>");
      document.write("<h3>Can I help you?</h3>");
    </script>
  </body>
</html>

复杂一点的代码:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>无标题文档</title>
  </head>
  <body>
    <h1>中国图书网</h1>
    <p id="do">社科类图书</p>
    <div id="DIV">军事类图书</div>
    <p>
      <button type="button" onclick="Function()">
        单击
      </button>
    </p>
    <script>
      function Function(){
        document.getElementById("do").innerHTML="计算机类图书";
        document.getElementById("DIV").innerHTML="历史类图书";
      }
    </script>
    <p style='color:red'>
      单击按钮,改变显示内容。
    </p>
  </body>
</html>