Hexo

包管理器

如何确定hexo的包管理器?

方法1:

在终端运行

ls | Where-Object { $_.Name -match "package-lock|yarn.lock|pnpm-lock" }

方法2:

更简单的方法:直接看文件是否存在

1
2
3
Test-Path package-lock.json   # 返回 True 或 False
Test-Path yarn.lock
Test-Path pnpm-lock.yaml

哪个返回 True,就是哪个包管理器。

【本博客】看到 package-lock.json → 锁定使用 npm,不要混用其他包管理器,避免 lock 文件冲突。

依赖

  • npm install xxx --save → 安装生产依赖
  • npm uninstall xxx --save → 移除生产依赖
  • 如果不带 --save,只是删掉 node_modules 里文件,package.json 还会保留记录

不带 --savenpm uninstall xxx: 只删除 node_modules不会改 package.json 于是就变成:

✅ node_modules 里面文件没了
❌ package.json 还保留这条依赖记录

这种包,npm ls 会标记成 extraneous

npm ls --depth=0 | findstr "extraneous"

含义:

  1. npm ls --depth=0 →列出所有顶层直接依赖
  2. | 管道:把前面命令输出传给后面
  3. findstr "extraneous" →只筛选、打印带 extraneous 标记的行

常用命令

快速开始测试博客

1
2
3
4
hexo init
npm install
git clone -b master https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly
npm install hexo-renderer-pug hexo-renderer-stylus --save

Butterfly美化

_config.yml

网站基本信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# --------------------------------------
# 网站基本信息
# --------------------------------------
# Site 网站基本信息
title: kika-秋 # 网站标题 # Kkkika
subtitle: '' # 网站副标题
description: '品味是最高判断力' # 网站描述(SEO搜索引擎抓取)
keywords: # 网站关键词
author: Kika # 网站作者
language: zh-CN # 语言
timezone: Asia/Shanghai # 时区

# URL
url: https://kkkika.github.io/ # 网址, 必须以 http:// 或 https:// 开头
permalink: :year/:month/:day/:title/ #文章的 永久链接 格式
permalink_defaults:
pretty_urls:
trailing_index: true # 是否在永久链接中保留尾部的 index.html,设置为 false 时去除
trailing_html: true # 是否在永久链接中保留尾部的 .html, 设置为 false 时去除 (对尾部的 index.html无效)

# Directory 目录
source_dir: source # 源文件目录,存储内容的位置
public_dir: public # 公共文件目录,生成静态站点的位置
tag_dir: tags # 标签文件夹
archive_dir: archives # 归档文件夹
category_dir: categories # 分类文件夹
code_dir: downloads/code # Include code 文件夹,source_dir 下的子目录
i18n_dir: :lang # 国际化(i18n)文件夹
skip_render: # 匹配到的文件将会被不做改动地复制到 public 目录中。 您可使用 glob 表达式来匹配路径。

文章/写作设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# --------------------------------------
# 文章/写作设置
# --------------------------------------

# Writing 写作
new_post_name: :title.md # 执行 `hexo new "文章标题"` 新建文章时,生成的 Markdown 文件命名规则
default_layout: post # 默认布局,模板在\scaffolds文件夹下
titlecase: false # 标题转换为Title Case,即每个单词首字母大写,其余字母小写
external_link:
enable: true # 是否在新标签页打开外部链接
field: site # 应用到整个站点(site)或 仅对文章(post)有效
exclude: '' # 需要排除的域名
filename_case: 0 # 文件名转换:0-不转换,1-小写,2-大写
render_drafts: false # 是否显示草稿
# 当post_asset_folder为true时,post的top_img和cver可直接引用同名文件夹下的图片,只需要写图片名字和后缀就行
post_asset_folder: false # 是否开启全局资源文件夹:https://hexo.io/zh-cn/docs/asset-folders
relative_link: false # 是否把链接改为与根目录的相对地址
future: true # 是否显示未来的文章

# 代码块的设置:https://hexo.io/zh-cn/docs/syntax-highlight
syntax_highlighter: 'highlight.js' # choose: highlight/prismjs
highlight:
auto_detect: false # auto_detect 十分耗费资源。 如果你不需要使用「子语言高亮」功能,或者不介意在书写代码块时标记语言,请不要启用此功能。
line_number: true
line_threshold: 1 # 行号阈值,代码块的行数超过这个阈值才显示行数
tab_replace: "" # 用给定字符串替换代码块内的制表符。 默认为 2 个空格。
exclude_languages: ['mermaid']
wrap: true # 是否对代码块进行换行 因为行号功能依赖 wrap 包裹机制,你不能在行号开启的同时关闭 wrap;只要你把 `line_number: true`,`wrap` 会被系统自动强制开启。
hljs: false
prismjs:
preprocess: true
line_number: true
line_threshold: 0
tab_replace: ""

页面设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# --------------------------------------
# 页面设置
# --------------------------------------
# Home page setting 首页设置
index_generator:
path: '' # 首页根路径
per_page: 12 # 每页显示的文章数量,0=禁用分页功能,默认10篇
order_by: -date # 按日期降序排列

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

# Date / Time format # 日期 / 时间格式
date_format: YYYY-MM-DD
time_format: HH:mm:ss
## updated_option supports 'mtime', 'date', 'empty'
updated_option: 'mtime' # 当 Front Matter 中没有指定 updated 时 updated 的取值

# Pagination 分页 (除了首页外的其他页面)
## Set per_page to 0 to disable pagination
per_page: 0
pagination_dir: page

其他设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# --------------------------------------
# 其他设置
# --------------------------------------

# Include / Exclude file(s) # 包含 / 排除文件
## include/exclude: 选项仅适用于 'source/' 文件夹
## 而 ignore 选项会应用到所有文件夹
include:
exclude:
ignore:

# Extensions 扩展
## Plugins: https://hexo.io/plugins/
## Themes: https://hexo.io/themes/
theme: butterfly

# Deployment
## Docs: https://hexo.io/docs/one-command-deployment
deploy:
type: ''

自定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# ----------------------------------------------------------------------------
# 自定义
# ----------------------------------------------------------------------------

# 数学公式:在 Markdown 解析前用 KaTeX 渲染,避免被 marked 破坏
math:
strict: false
katex:
stylesheet_fragment: '<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css" crossorigin="anonymous">'
render_options:
throwOnError: false
strict: false

# 思维导图
hexo_markmap:
darkThemeCssSelector: "[data-theme='dark']" # Butterfly 主题的深色模式标记是 [data-theme='dark']
CDN: 'fastly' # 指定资源CDN: 'fastly', 'jsdelivr', 'unpkg', 'custom'
globalOptions:
colorFreezeLevel: 3 # 冻结颜色级别,此例中前n级节点颜色被固定
color: ['orange'] # 设置颜色数组

# 文章加密
encrypt:
message: "请输入密码"
wrong_pass_message: "密码错误,请重新输入"
tags:
- {name: password, password: 1111}
algorithm: "aes" # 加密算法(默认"aes",可选"rsa")
expire: 1 # 记住密码(默认 7 天,单位:天)
silent: false # 是否禁用日志记录
theme: blink # 主题样式可选 default、blink、shrink、flip、up、surge、wave、xray
kdf:
iterations: 600000

_config.butterfly.yml

下面是_config.butterfly.yml 文件

导航设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# --------------------------------------
# Navigation Settings 导航设置
# --------------------------------------

nav:
logo: /img/icon.jpg # 导航栏 Logo 图片
display_title: true # 是否显示标题
fixed: false # 是否固定导航栏

menu:
主页: / || fas fa-home
归档: /archives/ || fa fa-folder-open
# 分类: /categories/ || fa fa-file-pen #fa fa-archive
# 技术标签: /tags/ || fa fa-tags
生活 || fas fa-sun fa-spin:
# 美食: https://www.xiaohongshu.com/user/profile/62d8c1e5000000001501ce47 || fa fa-laugh-squint
相册: /photos/ || fa fa-image
音乐: /music/ || fa fa-headphones
# 影视: https://www.kdocs.cn/wo/sl/v1YQapK || fa fa-video
快速链接: /links/ || fa fa-link
# 关于笔者: /about/ || fas fa-heart
# 留言板: /comment/ || fa fa-paper-plane

代码块设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# --------------------------------------
# Code Blocks Settings 代码块设置
# --------------------------------------

code_blocks:
theme: darker # choose: darker / pale night / light / ocean / false
macStyle: true # 是否使用Mac风格
height_limit: 300 # 代码块高度限制(单位:px) # 当代码块内容超出高度限制时,启用滚动条,以便用户可以查看完整内容
word_wrap: true # 是否自动换行

# Toolbar 工具栏
copy: true # 是否显示复制按钮
language: true # 是否显示代码语言标签
shrink: false # 收缩代码块,true收缩,false展开,none展开并隐藏按钮。收缩就是不显示代码
fullpage: false # 是否显示 代码全屏
code_word_wrap: true # 代码自动换行
  • Social media links【未改动,不使用】

图片设置

根目录\themes\butterfly\source\img的图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# --------------------------------------
# Image Settings 图片设置
# --------------------------------------

favicon: /img/icon.jpg # 网站的 favicon 图标

avatar:
img: /img/icon.jpg # 头像
effect: false # 设置为true头像会不停地顺时针旋转

# Disable all banner images 禁用所有顶部大图
disable_top_img: false

# If the banner of page not setting, it will show the default_top_img
# 如果页面未设置顶部大图,则显示默认图片
default_top_img: /img/default_top.jpg

# The banner image of index page 【主页】封面图片
index_img: /img/background.jpg

# The banner image of archive page 【归档页】顶部图片
archive_img:

# The background image of footer 页脚的背景图片
footer_img: transparent

# Website Background 网站背景
background: /img/background.jpg # 可设置为颜色值或图片地址

# 封面
cover:
index_enable: false # 首页 是否展示文章封面
aside_enable: true # 文章侧边栏 是否展示文章封面
archives_enable: true # 归档页 是否展示文章封面
position: both # 封面展示的位置 left/right/both
default_cover: /img/default_cover.jpg # 如果文章没有设置封面,则展示默认封面

# Replace Broken Images 替换无法加载的图片
error_img:
flink: /img/friend_404.gif # 友链页面的错误图片
post_page: /img/404.jpg # 文章页面的错误图片

# A simple 404 page 简易 404 页面
error_404:
enable: true
subtitle: '页面不存在' # 'Page Not Found'
background: /img/error-page.png

metadata

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# --------------------------------------
# metadata元数据设置
# --------------------------------------
# 页面 meta 显示: 是用来显示文章的相关信息的
post_meta:
# Home Page 首页
page:
date_type: both # 主页文章日期,created or updated or both 是创建日/更新日/都显示
date_format: date # date/relative 显示日期还是相对日期
categories: true # 主页是否显示分类
tags: true # 主页是否显示标签
label: true # 显示描述性文字
# post 文章页
post:
position: center # Choose: left / center
date_type: both # Choose: created / updated / both
date_format: date # Choose: date / relative
categories: true # 是否显示分类
tags: true # 是否显示标签
label: true # 是否显示文字标签

首页设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# --------------------------------------
# Index page settings 首页设置
# --------------------------------------

# The top_img settings of home page 首页顶部大图top_img设置
# 默认:顶部大图全屏,站点信息居中
index_site_info_top: # 站点信息距离顶部的位置,eg: 300px/300em/300rem/10%
index_top_img_height: 400px # 顶部大图高度。不能使用百分比,eg: 300px/300em/300rem


# The subtitle on homepage 首页副标题设置
subtitle:
enable: true # 是否启用副标题
effect: true # 是否启用打字机效果
loop: true # 是否循环播放打字机效果


typed_option:
# 是否调用第三方服务 API
# false 禁用 # 1 - hitokoto.cn(一言) # 2 - https://api.aa1.cn/doc/yiyan.html # 3 - jinrishici.com(今日诗词)
source: false
# 可写很多句子,不过如果你关闭打字机效果,副标题将只显示 sub 中的第一行
sub:
# - '会在有序中更自由'
- '从提问到输出'

# 首页文章布局 Article layout on the homepage
# 1: Cover on the left, info on the right 左侧封面,右侧信息
# 2: Cover on the right, info on the left 右侧封面,左侧信息
# 3: Cover and info alternate between left and right 左右交替
# 4: Cover on top, info on the bottom 封面上,信息下
# 5: Info displayed on the cover 信息覆盖在封面上
# 6: Masonry layout - Cover on top, info on the bottom 瀑布流布局 - 封面上,信息下
# 7: Masonry layout - Info displayed on the cover 瀑布流布局 - 信息覆盖在封面上
index_layout: 6

# 在首页显示文章简介 Display the article introduction on homepage
# 1: 描述
# 2: 两者(如果存在描述,将显示描述,否则显示自动摘要)
# 3: 自动摘要(默认)
# false: 不显示文章简介
index_post_content:
method: 2
length: 500 # 如果设置 method 为 2 或 3,需要配置长度

文章设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# --------------------------------------
# Post Settings 文章设置
# --------------------------------------
# 目录
toc:
post: true # 是否在post中显示目录
page: true # 是否在page中显示目录
number: true # 目录自动编号
expand: false # 是否展开
style_simple: true # 侧边栏只显示文章目录(Only for post)
scroll_percent: true # 滚动百分比:在目录旁边会显示一个滚动百分比

# 版权信息
post_copyright:
enable: true # 是否开启版权,每篇文章底部会出现版权模块
decode: true # 建议打开。是否对版权内容进行 HTML 解码,如果文章链接有中文可以正常显示中文
author_href: https://kkkika.github.io/ # 作者链接
license: CC BY-NC-SA 4.0 # 许可证类型
license_url: https://creativecommons.org/licenses/by-nc-sa/4.0/ # 许可证链接

# Sponsor/reward 赞助/打赏
reward:
enable: false
text: "请喝咖啡~"
QR_code:
- img: /img_reward/wechat.png
# link:
text: 微信
- img: /img_reward/alipay.png
# link:
text: 支付宝

# Post edit # 在线编辑按钮,在线浏览和编辑博客源码。
post_edit:
enable: false
# url: https://github.com/用户名/仓库名/edit/分支名/子目录名/
# 例如: https://github.com/jerryc127/butterfly.js.org/edit/main/source/
url:

# Related Articles 相关文章推荐
related_post:
enable: true
limit: 6 # 显示的文章数量
date_type: updated # 关联依据: "created"(创建时间)/ "updated"(更新时间)

# 文章分页方式
post_pagination: 1 # 1 - 旧文章在前 | 2 - 新文章在前 | false - 禁用分页

# 文章过期提示(以更新时间为基准)
noticeOutdate:
enable: false
style: flat # 样式: simple(简洁)/ flat(扁平)
limit_day: 365 # 触发天数(超过指定天数未更新显示提示)
position: top # 显示位置: top(顶部)/ bottom(底部)
# 提示信息模板
message_prev: It has been # 已经过了
message_next: days since the last update, the content of the article may be outdated. # 天自上次更新,文章内容可能已过时。

页脚设置

1
2
3
4
5
6
7
8
9
10
11
12
# --------------------------------------
# Footer Settings 页脚设置
# --------------------------------------
footer:
nav: # 页脚导航栏
owner:
enable: true # 是否启用所有者显示
since: 2024 # 网站创建年份
copyright:
enable: true # 是否显示 主题和框架的版权声明
version: true # 显示版本号
custom_text: # 自定义文本,通常你可以在这裡写声明文本等,支持 HTML

侧边栏设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# --------------------------------------
# Aside Settings 侧边栏设置
# --------------------------------------
aside:
enable: true # 是否启用侧边栏
hide: false # 是否默认隐藏侧边栏
button: false # 是否在右下角显示隐藏侧边栏的按钮
mobile: false # display on mobile
position: left # Position: left / right 侧边栏出现位置
display:
archive: true
tag: true
category: true

# 作者卡片
card_author:
enable: true # 是否显示作者信息卡片
description: # 作者描述
button:
enable: true # 是否显示按钮
icon: fab fa-github
text: Follow Me
link: https://github.com/Kkkika

# 公告卡片
card_announcement:
enable: false
content: 持续更新中...<br>欢迎关注我的 GitHub 获取更多开源项目和教程分享!

# 最新文章卡片
card_recent_post:
enable: true
limit: 5 # If set 0 will show all 设为 0 表示显示全部
sort: date # 按发布日期排序 # Sort: date / updated
sort_order: # 排序顺序(未设置,默认为降序)

# 最新评论卡片
card_newest_comments:
enable: true
sort_order:
limit: 3 # 显示评论数量
storage: 10 # 单位:分钟,数据保存到localStorage(本地存储)
avatar: true # 是否显示头像

# 分类卡片
card_categories:
enable: true
limit: 6 # If set 0 will show all
expand: none # Choose: none / true / false
sort_order:

# 标签卡片
card_tags:
enable: true
limit: 0 # If set 0 will show all
color: false # 是否启用颜色
orderby: random # Order of tags, random/name/length
order: 1 # 排序顺序:1 表示升序,-1 表示降序
sort_order:

# 归档卡片
card_archives:
enable: false
type: monthly # Type: monthly / yearly
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:

# 系列文章卡片
card_post_series:
enable: true
series_title: false # The title shows the series name
orderBy: 'date' # Order by title or date
order: -1 # Sort of order. 1, asc for ascending; -1, desc for descending

# 网站信息卡片
card_webinfo:
enable: true
post_count: true # 是否显示文章数量
last_push_date: true # 是否显示最后推送日期
sort_order:
# 发布日期与当前日期的时间差
# 格式:Month/Day/Year Time 或 Year/Month/Day Time
# 如果不启用此功能,请留空
# runtime_date: 2024/01/01 00:00:00

右下角按钮

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# --------------------------------------
# Bottom right button 右下角按钮
# --------------------------------------
rightside_bottom: # 右下角按钮到底部的距离(默认单位:px)

# 简繁体切换 Conversion between Traditional and Simplified Chinese
translate:
enable: false # 是否开启简繁体切换
default: # The text of a button 按钮文字
defaultEncoding: 2 # 网站默认语言(1 繁体中文 / 2 简体中文)
translateDelay: 0 # Time delay 延迟时间
msgToTraditionalChinese: '繁' # 按钮在简体中文时的文本
msgToSimplifiedChinese: '簡' # 按钮在繁体中文时的文本

# Read Mode 阅读模式
readmode: true

# Dark Mode 深色模式
darkmode:
enable: true # 是否启用暗黑模式
button: true # 切换暗黑/明亮模式的按钮
autoChangeMode: 1 # 自动切换暗黑/明亮模式,1:跟随系统;2:定时切换6pm-6am;false:禁用
# Set the light mode time. The value is between 0 and 24. If not set, the default value is 6 and 18
# 设置明亮模式时间,值在 0 到 24 之间。如果未设置,默认值为 6 和 18
start:
end:

# 回到顶部按钮 显示 滚动百分比 Show scroll percent in scroll-to-top button
rightside_scroll_percent: true

# 右侧悬浮栏(隐藏和显示)(顺序)
# Choose: readmode,translate,darkmode,hideAside,toc,chat,comment
# Don't repeat the same value # 除非了解原理,否则勿改
rightside_item_order:
enable: true # 是否启用悬浮栏自动排序
# show Default: toc,chat,comment
show: readmode,darkmode,comment
# hide Default: readmode,translate,darkmode,hideAside
# hide: readmode,hideAside

# 右下角配置按钮的动画 Animation for the bottom right config button
rightside_config_animation: true

全局设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# --------------------------------------
# Global Settings 全局设置
# --------------------------------------
anchor:
auto_update: true # 滚动时,URL 将根据标题 ID 更新
click_to_scroll: true # 点击标题滚动并更新锚点

photofigcaption: false # 图片标题

copy:
enable: true # 是否开启网站复制权限
copyright:
enable: true # 是否在复制内容后添加版权信息
limit_count: 150 # 当复制字符数超过 limit_count 时添加版权信息

# Need to install the 【hexo-wordcount】 plugin(需要安装插件)
wordcount:
enable: false # 是否启用字数统计
post_wordcount: true # 在文章元信息中显示字数统计
min2read: true # 在文章元信息中显示阅读时间
total_wordcount: true # 在侧边栏网站信息中显示总字数

# Busuanzi count for PV / UV in site
busuanzi:
site_uv: false # 本站总访客数
site_pv: false # 本站总访问量
page_pv: false # 本文总阅读量

数学公式

阶段1【弃用】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# --------------------------------------
# Math 数学公式
# --------------------------------------

# About the 【per_page】
# 设为 true 时,每页都会加载 公式渲染脚本
# 如果设置为 false,则需要在文章的 Front-matter 添加 mathjax: true 或 katex: true,对应的文章才会加载数学公式渲染引擎
math:
# Leave it empty if you don't need math
use: katex # Choose: mathjax, katex
per_page: false # 是否每一页都加载数学公式渲染引擎
hide_scrollbar: false # 是否隐藏滚动条

mathjax:
enableMenu: true # 是否启用上下文菜单
tags: none # 选择:all / ams / none,这控制是否对公式编号以及如何编号

katex:
copy_tex: true # 是否允许复制 KaTeX 公式源码

同时要在_config.yml文件配置:

1
2
3
4
# markdown配置
markdown:
plugins:
- '@renbaoshuo/markdown-it-katex' # 数学公式插件

卸载掉 marked 插件,安装 markdown-it

1
2
3
4
5
npm un hexo-renderer-marked --save # 如果有安装这个的话,卸载
npm un hexo-renderer-kramed --save # 如果有安装这个的话,卸载

npm i hexo-renderer-markdown-it --save # 需要安装这个渲染插件
npm install katex @renbaoshuo/markdown-it-katex # 需要安装这个katex插件

【注意】需要在文章的 Front-matter 添加 katex: true,对应的文章才会加载数学公式渲染引擎

阶段2【弃用】

npm install katex --save

scripts\kika-hexo-katex.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
'use strict';

const katex = require('katex');
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');

// ====================== 可配置常量 ======================
const DEFAULT_OPTIONS = {
every_page: false,
katexVersion: '0.16.9',
katexCdn: 'https://cdn.jsdelivr.net/npm/katex@VERSION/dist/katex.min.css',
katexSri: 'sha256-8lRiR21H4u3tD8sqJ00AWlh1mHJUzsUqirSQPwvE/ow=',
maxCacheSize: 4000,
maxTexLength: 1500,
enableInlineMath: true,
enableBlockMath: true,
renderOpts: {
strict: false,
throwOnError: false,
macros: {},
},
};

// ====================== 缓存系统 ======================
const CACHE_DIR = path.join(process.cwd(), '.cache');
const CACHE_FILE = path.join(CACHE_DIR, 'katex-render-cache.json');
const MAX_DISK_CACHE_ENTRIES = 2000;

// Symbol 私有标记,避免污染 data 对象
const KATEX_STORE = Symbol('hexo-katex-store');

// 控制字符占位符(使用较长且极不可能冲突的标识)
const CODE_FLAG = '\x03HEXOKTX\x04';
const FORMULA_PREFIX = '\x01KX';
const FORMULA_SUFFIX = '\x02';

// ====================== 标准 LRU 缓存 ======================
class LRUCache {
constructor(max) {
this.max = max;
this.cache = new Map();
}
get(key) {
if (!this.cache.has(key)) return undefined;
const val = this.cache.get(key);
this.cache.delete(key);
this.cache.set(key, val);
return val;
}
set(key, val) {
if (this.cache.has(key)) this.cache.delete(key);
else if (this.cache.size >= this.max) {
const oldest = this.cache.keys().next().value;
this.cache.delete(oldest);
}
this.cache.set(key, val);
}
has(key) {
return this.cache.has(key);
}
clear() {
this.cache.clear();
}
}

// ====================== 工具函数 ======================
/** HTML 转义,阻断 XSS */
function htmlEscape(str) {
if (typeof str !== 'string') return '';
return str.replace(/[&<>"']/g, c => ({
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;'
})[c]);
}

/** 拒绝超长公式,防止 DOS 和语法破坏 */
function validateTex(tex, maxLen) {
if (tex.length > maxLen) {
return { valid: false, reason: `公式长度 ${tex.length} 超过最大限制 ${maxLen}` };
}
return { valid: true, tex };
}

/** KaTeX 安全渲染配置 */
function getKatexSafeConfig(displayMode, renderOpts) {
return {
...renderOpts,
displayMode,
trust: (ctx) => {
if (!ctx || !ctx.protocol) return false;
return ['http:', 'https:', 'mailto:'].includes(ctx.protocol);
}
};
}

// 预编译正则,避免重复创建
const REGEX_ESCAPER = /[.*+?^${}()|[\]\\]/g;

// ====================== 主插件 ======================
module.exports = function (hexo) {
const userConfig = hexo.config.katex || {};
const cfg = Object.assign({}, DEFAULT_OPTIONS, userConfig);
cfg.katexCdn = cfg.katexCdn.replace('VERSION', cfg.katexVersion);
const maxTex = cfg.maxTexLength || 1500;
const maxCache = cfg.maxCacheSize || 4000;

// 内存 LRU 缓存
const memCache = new LRUCache(maxCache);

// 磁盘缓存(构建级,非文章级)
let diskCache = {};
let diskDirty = false;
// 预计算渲染选项哈希,避免每次重复 JSON.stringify
const renderOptsHash = JSON.stringify(cfg.renderOpts);

function loadDiskCache() {
try {
if (fs.existsSync(CACHE_FILE)) {
const raw = fs.readFileSync(CACHE_FILE, 'utf8');
return JSON.parse(raw);
}
} catch (err) {
hexo.log.warn(`[hexo-katex] 磁盘缓存加载失败: ${err.message}`);
}
return {};
}

function saveDiskCache() {
if (!diskDirty) return;
try {
if (!fs.existsSync(CACHE_DIR)) {
fs.mkdirSync(CACHE_DIR, { recursive: true });
}
const keys = Object.keys(diskCache);
if (keys.length > MAX_DISK_CACHE_ENTRIES) {
const sorted = keys.sort((a, b) => diskCache[a].timestamp - diskCache[b].timestamp);
for (const k of sorted.slice(0, keys.length - MAX_DISK_CACHE_ENTRIES)) {
delete diskCache[k];
}
}
fs.writeFileSync(CACHE_FILE, JSON.stringify(diskCache, null, 2));
diskDirty = false;
} catch (err) {
hexo.log.warn(`[hexo-katex] 磁盘缓存保存失败: ${err.message}`);
}
}

// 构建开始时:加载磁盘缓存到内存
hexo.extend.filter.register('before_generate', () => {
memCache.clear();
diskCache = loadDiskCache();
let loaded = 0;
for (const [key, val] of Object.entries(diskCache)) {
if (!memCache.has(key)) {
memCache.set(key, val.html);
loaded++;
}
}
hexo.log.info(`[hexo-katex] 缓存已加载,内存 ${loaded} 条`);
});

// 构建结束时:批量保存磁盘缓存
hexo.extend.filter.register('after_generate', () => {
saveDiskCache();
hexo.log.info(`[hexo-katex] 缓存已保存`);
});

// 注入 KaTeX CSS(防御性转义)
hexo.extend.injector.register('head_end', () => {
return `<link rel="stylesheet" href="${htmlEscape(cfg.katexCdn)}" integrity="${htmlEscape(cfg.katexSri)}" crossorigin="anonymous">`;
});

// 判断是否启用公式渲染(增强兼容性)
const isEnable = (data) => {
if (!data || typeof data !== 'object') return false;
if (cfg.every_page === true) return true;
const fm = data.frontmatter || (data.page || {}).frontmatter || {};
return fm.katex === true;
};

// ---------- 第一阶段:预处理 ----------
hexo.extend.filter.register('before_post_render', function (data) {
if (!data || typeof data.content !== 'string') return data;
if (data.draft === true || data.private === true) return data;
if (!isEnable(data)) return data;

let content = data.content;

// 快速短路:无美元符号则直接跳过
if (!/\$/.test(content)) return data;

const codeBlocks = [];
const formulaMap = new Map();
let serial = 0;

// 1. 保护行内代码 `...`
content = content.replace(/`[^`]*?`/g, (match) => {
codeBlocks.push(match);
return CODE_FLAG;
});

// 2. 保护围栏代码块 ```...```
content = content.replace(/```[\s\S]*?```/g, (match) => {
codeBlocks.push(match);
return CODE_FLAG;
});

// 3. 保护缩进代码块(连续缩进行,允许中间存在空行)
const lines = content.split('\n');
const newLines = [];
let i = 0;
while (i < lines.length) {
const line = lines[i];
if (/^(?: {4}|\t)/.test(line) && line.trim() !== '') {
const block = [];
while (i < lines.length) {
const cur = lines[i];
// 当前行为缩进非空行
if (/^(?: {4}|\t)/.test(cur) && cur.trim() !== '') {
block.push(cur);
i++;
}
// 当前行为空行,但下一行仍属于缩进代码块
else if (cur.trim() === '' && i + 1 < lines.length &&
/^(?: {4}|\t)/.test(lines[i + 1]) && lines[i + 1].trim() !== '') {
block.push(cur);
i++;
} else {
break;
}
}
codeBlocks.push(block.join('\n'));
newLines.push(CODE_FLAG);
} else {
newLines.push(line);
i++;
}
}
content = newLines.join('\n');

// 4. 提取块级公式 $$...$$(排除转义的 \$$)
if (cfg.enableBlockMath) {
content = content.replace(/(?<!\\)\$\$([\s\S]*?)(?<!\\)\$\$/g, (match, texRaw) => {
const tex = texRaw.trim();
if (!tex) return match;
const key = `${FORMULA_PREFIX}${serial++}${FORMULA_SUFFIX}`;
formulaMap.set(key, { tex, display: true });
return key;
});
}

// 5. 提取行内公式 $...$(排除换行和转义的 \$)
if (cfg.enableInlineMath) {
content = content.replace(/(?<!\\)\$([^\$\n]+?)(?<!\\)\$/g, (match, texRaw) => {
const tex = texRaw.trim();
if (!tex) return match;
const key = `${FORMULA_PREFIX}${serial++}${FORMULA_SUFFIX}`;
formulaMap.set(key, { tex, display: false });
return key;
});
}

// 6. 还原所有被保护的代码块
let codeIdx = 0;
const escFlag = CODE_FLAG.replace(REGEX_ESCAPER, '\\$&');
content = content.replace(new RegExp(escFlag, 'g'), () => {
if (codeIdx >= codeBlocks.length) {
hexo.log.warn('[hexo-katex] 代码块还原数量不匹配,可能存在占位符冲突');
return '';
}
return codeBlocks[codeIdx++];
});

// 挂载数据到 Symbol 私有属性
if (formulaMap.size > 0) {
data[KATEX_STORE] = { formulaMap };
}
data.content = content;
return data;
});

// ---------- 第二阶段:渲染替换 ----------
hexo.extend.filter.register('after_post_render', function (data) {
const store = data[KATEX_STORE];
if (!store) return data;

const { formulaMap } = store;

// 性能优化:使用统一正则匹配所有占位符,避免为每个公式构建超长分支正则
const prefixEsc = FORMULA_PREFIX.replace(REGEX_ESCAPER, '\\$&');
const suffixEsc = FORMULA_SUFFIX.replace(REGEX_ESCAPER, '\\$&');
const replaceReg = new RegExp(`${prefixEsc}\\d+${suffixEsc}`, 'g');

data.content = data.content.replace(replaceReg, (matched) => {
const item = formulaMap.get(matched);
if (!item) return matched;

const { tex, display } = item;
const validation = validateTex(tex, maxTex);
if (!validation.valid) {
hexo.log.warn(`[hexo-katex] 公式被拒绝:${htmlEscape(tex.slice(0, 100))} | 原因:${validation.reason}`);
return `<span style="color:#e74c3c;font-weight:bold;">[公式过长]</span>`;
}

const safeTex = validation.tex;
const cacheKey = (display ? 'D:' : 'I:') + crypto.createHash('md5').update(safeTex + renderOptsHash).digest('hex');

// 1. 内存缓存
const cached = memCache.get(cacheKey);
if (cached !== undefined) return cached;

// 2. 磁盘缓存
if (diskCache[cacheKey]) {
const html = diskCache[cacheKey].html;
memCache.set(cacheKey, html);
return html;
}

// 3. KaTeX 渲染
try {
const html = katex.renderToString(safeTex, getKatexSafeConfig(display, cfg.renderOpts));
memCache.set(cacheKey, html);
diskCache[cacheKey] = { html, timestamp: Date.now() };
diskDirty = true;
return html;
} catch (err) {
hexo.log.warn(`[hexo-katex] 公式渲染失败:${htmlEscape(safeTex)} | 错误:${htmlEscape(err.message)}`);
return `<span style="color:#e74c3c;font-weight:bold;">[公式错误:${htmlEscape(safeTex)}]</span>`;
}
});

delete data[KATEX_STORE];
return data;
});
};

_config.yml

1
2
katex:
every_page: true # false = 只在 frontmatter 里加 katex: true 的文章启用

阶段3🍊

npm install hexo-filter-katex --save

在站点根目录的 _config.yml 里增加(或合并到已有配置中):

1
2
3
4
5
# 数学公式:在 Markdown 解析前用 KaTeX 渲染,避免被 marked 破坏
katex:
stylesheet_fragment: '<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css" crossorigin="anonymous">'
render_options:
throwOnError: false

说明:

  • stylesheet_fragment:在页面 <head> 里插入 KaTeX 的 CSS,公式才有正确样式;不配的话公式 HTML 在,但可能看起来不对。

  • throwOnError: false:单条公式报错时只影响该条,不会让整站生成失败。

  • 中文不能直接写

    • $...$$$...$$ 数学公式环境中,直接写了中文(”自动适配”),KaTeX/MathJax 在严格模式下会警告——它不认识 Unicode 中文字符在数学模式里。

    • 中文用 \text{} 包起来:

      $\text{内容}$

参考:Hexo 博客中正确显示数学公式 | goodisok 的博客

搜索🔍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# --------------------------------------
# Search 搜索
# --------------------------------------
search:
# 三个选一个,如果不需要搜索功能,留空即可
use: local_search # Choose: algolia_search / local_search / docsearch
placeholder: # 搜索框的提示文字

algolia_search:
hitsPerPage: 6 # 每页搜索结果数量

local_search:
preload: true # 页面加载时预加载搜索数据
top_n_per_article: -1 # 每篇文章显示前 n 条结果,-1 表示全部
unescape: true # 将 HTML 字符串反转义为可读内容
pagination:
enable: false # 是否启用搜索结果分页
hitsPerPage: 6 # 每页搜索结果数量
CDN:

# https://docsearch.algolia.com/
docsearch:
appId:
apiKey:
indexName:
option:

检查是否安装了本地搜索插件:

npm list hexo-generator-search

如果没安装:

npm install hexo-generator-search --save

分享

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# --------------------------------------
# Share System 分享
# --------------------------------------
share:
# Leave it empty if you don't need share
use: sharejs # Choose: sharejs / addtoany

# https://github.com/overtrue/share.js
sharejs:
# sites: wechat,weibo,qq,douban,qzone,facebook,twitter
sites: wechat

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

评论💬

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# --------------------------------------
# Comments System 评论系统
# --------------------------------------
comments:
# Choose: Disqus/Disqusjs/Livere/Gitalk/Valine/Waline/Utterances/Facebook Comments/Twikoo/Giscus/Remark42/Artalk
# 最多两个评论系统,第一个将作为默认显示,用英文逗号分隔
use: Twikoo
text: true # 按钮旁边显示评论系统名称
lazyload: false # 别改,只能为false # 如果设置为 true,评论计数将无效
count: false # 在文章顶部大图显示评论数
card_post_count: false # 首页卡片显示评论数

# Twikoo
# https://github.com/imaegoo/twikoo
twikoo:
envId: https://kika2378.netlify.app/.netlify/functions/twikoo # 环境 ID
region: # 环境地域,默认为 ap-shanghai,如果您的环境地域不是上海,需传此参数
visitor: false # 使用 Twikoo 的访客计数作为页面的访客量
option: # 其他可选配置

https://kika2378.netlify.app/.netlify/functions/twikoo

Twikoo Netlify部署方法及操作流程,给博客网站添加评论功能

Chat Services

在线客服【没改动】

Analysis

网站分析【没改动】

广告【没改动】

Verification

网站验证

# 可参考 https://www.wuxx.top/2024/06/04/website-search/

Beautify / Effect 美化/特效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# --------------------------------------
# Beautify / Effect 美化/特效
# --------------------------------------

# Theme color for customize 自定义主题色
# 注意:颜色值必须用双引号,如 "#000",否则可能会导致错误!

theme_color:
enable: true # 启用主题颜色自定义
main: "#FDB68C" # 主色调 默认"#49B1F5"
paginator: "#d8a067" # 分页器颜色 "#00c4b6""#fadb0e"
# button_hover: "#FF7242" # 按钮悬停
text_selection: "#fe6f0f" # 文本选中背景色 "#00c4b6"
link_color: "#d89f68" # 超链接颜色 "#99a9bf"
a_link_color: "#fe6f0f" # 版权信息链接色,优先于 link_color

# meta_color: "#858585" # 元数据文字色
hr_color: "#FFC6A5FF" # 水平线颜色 "#A4D8FA"
# code_foreground: "#F47466" # 代码文字色
# code_background: "rgba(27, 31, 35, .05)" # 代码背景色
toc_color: "#fe6f0f" # 目录文字色 "#00c4b6"
blockquote_padding_color: "#FDB68C" # 引用框边框色
blockquote_background_color: "#FDB68C" # 引用框背景色 "#49b1f5"
# scrollbar_color: "#49b1f5" # 滚动条颜色
# meta_theme_color_light: "ffffff" # 元数据主题色(浅色)
# meta_theme_color_dark: "#0d0d0d" # 元数据主题色(深色)

# 分类页和标签页 UI
category_ui: # 可选:index - 与首页相同 / default - 与归档页相同
tag_ui: # 可选:index - 与首页相同 / default - 与归档页相同

rounded_corners_ui: true # UI 元素圆角

text_align_justify: false # 拉伸行使每行宽度相等(文字两端对齐)

mask:
header: true # 为页眉添加遮罩
footer: false # 为页脚添加遮罩

# Loading Animation # 加载动画
preloader:
enable: false # 页面打开前有一个过场的小动画,不太建议开启
source: 1 # 1:全屏加载 2:进度条pace (progress bar)
pace_css_url: # pace theme (see https://codebyzach.github.io/pace/)

enter_transitions: true # Page Transition # 页面切换过渡

display_mode: light # 默认显示模式(主题颜色):light / dark

# 美化页面显示
# 文章内容美化 Configuration for beautifying the content of the article
beautify:
enable: true
field: post # 作用范围:site 全站 / post 仅文章
title_prefix_icon: '\f0c1' # 标题前缀图标
title_prefix_icon_color: '#f77f39' # 标题前缀图标颜色

# 全局字体设置 Global font settings
# 不了解请勿修改
font:
global_font_size:
code_font_size:
font_family:
code_font_family:

# 网站标题与副标题字体 Font settings for the site title and site subtitle
blog_title_font:
font_link:
font_family:

# 分割线图标 The setting of divider icon
hr_icon:
enable: true
icon: '\f094' # The unicode value of Font Awesome icon, such as '\3423'
icon_top:

# Typewriter Effect(打字机效果)
# https://github.com/disjukr/activate-power-mode
activate_power_mode:
enable: false # 是否启用打字机效果
colorful: false # 是否启用彩色效果
shake: false # 是否启用震动效果
mobile: false # 是否在移动设备上启用

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

# canvas_ribbon 彩带背景
# See: https://github.com/hustcc/ribbon.js
canvas_ribbon:
enable: false
size: 150 # The size of ribbon
alpha: 0.6 # The opacity of ribbon (0 ~ 1) 不透明度
zIndex: -1
click_to_change: false
mobile: false

# Fluttering Ribbon(飘带)
canvas_fluttering_ribbon:
enable: false
mobile: false

# canvas_nest(线条交织)
# https://github.com/hustcc/canvas-nest.js
canvas_nest:
enable: true
color: '253,114,16' # '0,0,255' # values: (R,G,B).(note: use ',' to separate.)
opacity: 0.9 # The opacity of line (0~1) # 线条的不透明度 (0~1)
zIndex: -1 # 背景的 z-index 属性
count: 99 # 线条数量
mobile: false # 是否在移动设备上启用


# Mouse click effects: fireworks(鼠标点击效果:烟火特效)
fireworks:
enable: false
zIndex: 9999
mobile: false

# Mouse click effects: Heart symbol(鼠标点击效果: 爱心)
click_heart:
enable: false
mobile: false

# Mouse click effects: words(鼠标点击效果: 文字)
clickShowText:
enable: false
text:
- 富强
- 民主
- 文明
- 和谐
- 平等
- 公正
- 法治
- 爱国
- 敬业
- 诚信
- 友善
fontSize: 15px
random: false
mobile: false

大图查看模式

1
2
3
4
5
6
7
8
9
# --------------------------------------
# Lightbox Settings 灯箱设(大图查看模式)
# --------------------------------------

# Choose: fancybox / medium_zoom
# https://github.com/francoischalifour/medium-zoom
# https://fancyapps.com/fancybox/
# Leave it empty if you don't need lightbox
lightbox: fancybox

标签插件

Mermaid

  1. Mermaid
  2. ``Series`可能有用但还没用上
  3. 【其他都没改】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# --------------------------------------
# Tag Plugins settings 标签插件设置
# --------------------------------------

# Series 系列文章
series:
enable: false
orderBy: 'title' # Order by title or date
order: 1 # 排序顺序:1 升序;-1 降序
number: true # 是否显示编号

# Mermaid 图表
# https://github.com/mermaid-js/mermaid
mermaid:
enable: true
code_write: true # 使用代码块编写 Mermaid 图表
theme: # built-in themes: default / forest / dark / neutral
light: default
dark: dark
open_in_new_tab: true # 启用"在新标签页打开"按钮,在单独窗口中查看图表
zoom_pan: true # 启用图表的缩放和平移交互

同时要在_config.yml文件添加 exclude_languages: ['mermaid']

1
2
3
4
5
6
7
8
syntax_highlighter: highlight.js
highlight:
line_number: true
auto_detect: false
tab_replace: ''
wrap: true
hljs: false
exclude_languages: ['mermaid']

其他设置

  1. pjax
  2. snackbar
  3. lazyload
  4. 【其他暂定】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# --------------------------------------
# Other Settings 其他设置
# --------------------------------------

# https://github.com/MoOx/pjax
pjax:
enable: true
exclude: # 排除指定页面不使用 pjax,如 '/music/'
# - /xxxxxx/

# Snackbar - Toast Notification # 弹窗和Toast 通知
# https://github.com/polonel/SnackBar
# position: top-left / top-center / top-right / bottom-left / bottom-center / bottom-right
snackbar:
enable: true
position: bottom-center # 通知位置
bg_light: '#fcc10f' # light mode时弹窗背景色 默认浅蓝色'#49b1f5'
bg_dark: '#1f1f1f' # dark mode时弹窗背景色

# Instant.page prefetch (预加载)
# https://instant.page/
instantpage: false # 当鼠标悬停到链接上超过 65 毫秒时,Instantpage 会对该链接进行预加载,可以提升访问速度。

# Lazyload # 懒加载
# https://github.com/verlok/vanilla-lazyload
lazyload:
enable: true
native: true # 使用浏览器的原生 lazyload 而不是 vanilla-lazyload
field: site # 指定使用 Lazyload 的范围 (site 或 post)
placeholder: /img/friend_404.gif
blur: true # 由虚变实

Inject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# Inject
# Insert the code to head (before '</head>' tag) and the bottom (before '</body>' tag)
inject:
head:
- <link rel="stylesheet" href="/css/custom.css">
bottom:
# 页面加载完成后,浏览器前端 JS 把页面中 `==xxx==` 替换成 mark 标签
- |
<script>
document.addEventListener('DOMContentLoaded', function(){
const article = document.querySelector('#article-container, .article-content, .post-content');
if (!article) return;

// 递归遍历所有文本节点,跳过代码块
function walk(node) {
// 跳过代码相关标签,完全不处理
const skipTags = ['PRE','CODE','SCRIPT','STYLE','SVG'];
if(skipTags.includes(node.tagName)) return;

// 文本节点处理 ==高亮==
if(node.nodeType === 3){
const text = node.textContent;
if(!text.includes('==')) return;
const parent = node.parentNode;
const parts = text.split(/(==[\s\S]*?==)/g);
node.remove();
parts.forEach(part=>{
if(/^==[\s\S]*?==$/.test(part)){
const mark = document.createElement('mark');
mark.textContent = part.slice(2,-2);
parent.appendChild(mark);
}else{
parent.appendChild(document.createTextNode(part));
}
})
return;
}
// 递归子节点
Array.from(node.childNodes).forEach(walk);
}
walk(article);
});
</script>

自定义

css样式

C:\Code\blog1-hexo\source\css\custom.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
/* ============= 顶部图片透明 ============= */
/* 文章页 */
#page-header.post-bg {
background: none !important;
height: 240px !important;
}
/* 分类页、标签页、归档页 */
#page-header.not-home-page {
background: none !important;
height: 30vh !important;
}
#page-header.not-home-page #site-title {
top: -10vh !important;
}


/* ============= 背景半透明 ============= */
/* 文章阅读区域 */
html:not([data-theme='dark']) #post {
background: rgba(255, 255, 255, .94);
}
html:not([data-theme='light']) #post {
background: rgba(0, 0, 0, .7);
}
/* 侧边栏所有卡片 */
html:not([data-theme='dark']) #aside-content .card-widget {
background: rgba(255, 255, 255, .94);
}
html:not([data-theme='light']) #aside-content .card-widget {
background: rgba(0, 0, 0, .7);
}
/* 最近文章卡片 */
html:not([data-theme='dark']) #recent-posts .recent-post-item {
background: rgba(255, 255, 255, .94);
}
html:not([data-theme='light']) #recent-posts .recent-post-item {
background: rgba(0, 0, 0, .7);
}
/* 卡片(分类页、标签页、链接页) */
html:not([data-theme='dark']) #page {
background: rgba(255, 255, 255, .94);
}
html:not([data-theme='light']) #page {
background: rgba(0, 0, 0, .7);
}
/* 归档页卡片 */
html:not([data-theme='dark']) #archive {
background: rgba(255, 255, 255, .94);
}
html:not([data-theme='light']) #archive {
background: rgba(0, 0, 0, .7);
}


/* ============= hexo-blog-encrypt 自定义样式 ============= */
.hbe-input-field-blink {
color: #d89f68 !important;
}
.hbe-input-field-blink:focus {
border-color: #d89f68 !important;
}
.hbe-input-label-content-blink::after {
color: #c97d3a !important;
}
.hbe-input-field {
margin: 6px !important;
}
.hbe.hbe-button {
background-color: #ffea00 !important;
color: #111111 !important;
}
.hbe.hbe-button:hover {
background-color: #ffdd00 !important;
}

然后在 _config.butterfly.yml使用:

1
2
3
inject:
head:
- <link rel="stylesheet" href="/css/custom.css">

==高亮==

Hexo Butterfly ==文字== 不渲染

1
2
3
4
5
inject:
head:
# 头部注入(放css、meta等)
bottom:
# 页面底部注入JS,放这里不会阻塞页面渲染

打开主题配置 _config.butterfly.yml

找到 inject 配置项,没有就直接复制下面全部覆盖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
inject:
head:
bottom:
- |
<script>
document.addEventListener('DOMContentLoaded', function(){
const article = document.querySelector('#article-container, .article-content, .post-content');
if (!article) return;

// 递归遍历所有文本节点,跳过代码块
function walk(node) {
// 跳过代码相关标签,完全不处理
const skipTags = ['PRE','CODE','SCRIPT','STYLE','SVG'];
if(skipTags.includes(node.tagName)) return;

// 文本节点处理 ==高亮==
if(node.nodeType === 3){
const text = node.textContent;
if(!text.includes('==')) return;
const parent = node.parentNode;
const parts = text.split(/(==[\s\S]*?==)/g);
node.remove();
parts.forEach(part=>{
if(/^==[\s\S]*?==$/.test(part)){
const mark = document.createElement('mark');
mark.textContent = part.slice(2,-2);
parent.appendChild(mark);
}else{
parent.appendChild(document.createTextNode(part));
}
})
return;
}
// 递归子节点
Array.from(node.childNodes).forEach(walk);
}
walk(article);
});
</script>

文章加密

1
2
3
4
Hexo butterfly 博客文章加密如何实现?
但是 hexo-blog-encrypt 插件 目录不加密
其他加密方法?
toc修复,不要直接修改主题模板文件

插件 hexo-blog-encrypt

安装

npm install hexo-blog-encrypt --save

项目配置

在hexo的_config.yml文件中添加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 文章加密
encrypt:
message: "请输入密码"
wrong_pass_message: "密码错误,请重新输入"
tags:
- {name: encryptAsDiary, password: passwordA}
- {name: encryptAsWork, password: passwordB}
algorithm: "aes" # 加密算法(默认"aes",可选"rsa")
expire: 1 # 记住密码(默认 7 天,单位:天)
silent: false # 是否禁用日志记录
theme: blink # 主题样式可选 default、blink、shrink、flip、up、surge、wave、xray
kdf:
iterations: 600000

配置项说明:

  • enable: 是否启用加密功能,设置为 true 启用。
  • message: 密码输入框的描述性文字。
  • wrong_pass_message: 密码错误时的提示信息。
  • tags:所有打了 encryptAsDiary 标签的文章,统一用 passwordA 加密。
    好处:不用每篇文章都写密码,打标签就自动加密。支持多个标签不同密码。

主题配色修改:在\source\css\custom.css添加如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* ============= hexo-blog-encrypt 自定义样式 ============= */
.hbe-input-field-blink {
color: #d89f68 !important;
}
.hbe-input-field-blink:focus {
border-color: #d89f68 !important;
}
.hbe-input-label-content-blink::after {
color: #c97d3a !important;
}
.hbe-input-field {
margin: 6px !important;
}
.hbe.hbe-button {
background-color: #ffea00 !important;
color: #111111 !important;
}
.hbe.hbe-button:hover {
background-color: #ffdd00 !important;
}

使用

在需要加密文章的头部添加如下设置:

1
2
3
4
5
---
title: ###
date: XXXX-XX-XX XX:XX:XX
password: ”你的密码”
---

toc?【未用】

把修复 toc 的 pug 写在上面这个根目录路径,更新 butterfly 主题完全不受影响,git 也可以纳入版本管理。

原理:Hexo 渲染模板时,优先读取站点根目录layout,找不到才去读取主题的 layout 目录。

内容还是这份(直接复制到根目录layout/_partial/post/toc.pug):

1
2
3
4
5
6
7
8
9
10
11
12
13
#post-toc-wrap(class=tocClass)
if post.encrypt
- let tocHtml = toc(post.origin, {list_number: theme.toc.number, max_depth: theme.toc.max_depth})
else
- let tocHtml = toc(post.content, {list_number: theme.toc.number, max_depth: theme.toc.max_depth})

if tocHtml
.post-toc-container(style= post.encrypt ? "display:none;" : "")
if theme.toc.style_simple
!= tocHtml
else
span.post-toc-title= __('post.toc')
!= tocHtml

然后 _config.butterfly.yml 的 inject 保留解密 JS:

1
2
3
4
5
6
7
8
9
inject:
bottom:
- <script>
window.addEventListener('hexo-blog-decrypt', function () {
const tocWrap = document.querySelector('.post-toc-container');
if(tocWrap) tocWrap.style.display = '';
if(window.refreshArticle) window.refreshArticle();
})
</script>

思维导图

hexo-markmap插件

npm install hexo-markmap --save

在config.yml中添加这段配置:

1
2
3
4
5
6
7
# 思维导图
hexo_markmap:
darkThemeCssSelector: "[data-theme='dark']" # Butterfly 主题的深色模式标记是 [data-theme='dark']
CDN: 'fastly' # 指定资源CDN: 'fastly', 'jsdelivr', 'unpkg', 'custom'
globalOptions:
colorFreezeLevel: 3 # 冻结颜色级别,此例中前n级节点颜色被固定
color: ['orange'] # 设置颜色数组

使用:

1
2
3
{% markmap %}

{% endmarkmap %}

提前预览:如果想要提前看看,可以用vscode插件Markmap Universe

【设置】【在 settingsjson 中编辑】

1
2
3
4
5
"markmap-universe.globalOptions": {
"color": [
"orange"
]
}

期待上线

20260608

hexo-mindmap-svg 功能全覆盖测试 | Zerl’TA Blog

压缩

静态资源合并压缩,安装压缩插件(减少文本体积):Hexo-minify 是一款 Hexo 压缩插件,它可以压缩 HTML、CSS、JS、Font、Image(jpg,png,gif,webp,svg))

npm install hexo-minify --save

主配置_config.yml

1
2
3
4
5
6
7
# 压缩
minify:
preview: true # 本地预览时是否压缩
image:
enable: false # 压缩图片会失败,不建议开启
font:
enable: true

自动压缩 HTML、CSS、JS,删除多余空格注释

问题

Butterfly一级标题锚点跳转

是 Butterfly 主题配合 hexo-renderer-markdown-it 渲染器的已知 bugmarkdown-it 默认不会为 heading 生成 id 属性,导致 TOC 无法为一级标题创建可点击的锚点。

法1 改 main.js

直接改 main.js 的初始化:

打开 themes/butterfly/source/js/main.js,找到页面初始化的地方(通常有 initAdjust

initAdjust 函数定义的紧后面加上 fixTocH1 的定义,然后在文件最末尾DOMContentLoaded 回调结束前)加上调用。

  1. initAdjust 后面添加函数定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 初始化header
const initAdjust = () => {
adjustMenu(true)
$nav.classList.add('show')
}

// 修复一级标题 TOC 锚点
const fixTocH1 = () => {
document.querySelectorAll('.toc-level-1 > .toc-link').forEach(link => {
if (link.getAttribute('href')) return
const h1 = document.querySelector('#article-container h1')
if (h1?.id) link.setAttribute('href', '#' + h1.id)
})
}
  1. 在文件最末尾(DOMContentLoaded 结束前)添加调用

main.js 的最后面,你应该能看到类似这样的代码:

1
2
initAdjust()
// 可能还有其他 initXxx() 调用

在它后面加上:

1
2
initAdjust()
fixTocH1() // ← 加这一行

改完后:

hexo clean && hexo g && hexo s

然后 F12 控制台验证:

document.querySelector('.toc-level-1 .toc-link').getAttribute('href')

应该输出 "#文章front-matter",点击不再报错。

👍法2 inject 注入

在 Butterfly 主题的配置里塞一段 JavaScript 代码,让它自动跑到每篇文章的页面底部。

1
2
3
4
5
6
inject:           # Butterfly 提供的"注入"功能
bottom: # 把代码插到页面最底部(</body> 标签之前)
- | # YAML 语法:后面跟着多行文本(保留换行)
<script> # 开始写 JS
... # 代码
</script> # 结束 JS

直接在 _config.butterfly.yml 里写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
inject:
bottom:
# 修复 markdown-it 渲染器下 TOC 一级标题锚点丢失的问题
- |
<script>
document.addEventListener('DOMContentLoaded', function() {
const h1 = document.querySelector('#article-container h1');

// h1 无 id 时,用纯文本生成一个
if (h1 && !h1.id) {
h1.id = h1.textContent.trim()
.replace(/\s+/g, '-')
.replace(/[?#&"'<>]/g, '') || 'h1';
}

// 补全 TOC 一级标题链接
document.querySelectorAll('.toc-level-1 > .toc-link').forEach(link => {
if (!link.getAttribute('href') && h1?.id) {
link.setAttribute('href', '#' + h1.id);
}
});
});
</script>

其他尝试

插件

方案一:安装 markdown-it-named-headings 插件(治本)

npm install markdown-it-named-headings --save

然后修改 node_modules/hexo-renderer-markdown-it/lib/renderer.js,在文件中添加:

parser.use(require('markdown-it-named-headings'))

具体加在哪里?

打开文件后,找到这段代码 this.parser = new MarkdownIt(preset, render);(大约在第 30 行附近),

在这一行的下面,插入上述代码,修改后:

1
2
3
4
5
6
7
8
9
10
11
this.parser = new MarkdownIt(preset, render);

this.parser.use(require('markdown-it-named-headings'));

if (enable_rules) {
this.parser.enable(enable_rules);
}

if (disable_rules) {
this.parser.disable(disable_rules);
}

重新生成部署:

hexo clean && hexo g && hexo s

⚠️ 修改 node_modules 在重新安装依赖后会失效,建议配合 patch-package 持久化修改。

方案二:避免在正文中使用 # 一级标题(推荐)

这是 Butterfly 主题的官方建议

  • 文章的 Front-matter title 已经渲染为页面唯一的 <h1>
  • 正文中再用 # 标题 会造成多个 h1,不利于 SEO
  • 正文标题建议从 ## 二级标题 开始写

这样无需任何修改,TOC 就能正常工作,且符合语义化规范。

patch-package持久化

node_modules 是 npm 安装依赖时自动生成的文件夹,通常被 .gitignore 忽略,不会提交到 Git 仓库里。

这意味着:

为什么修改会失效?

假设你今天手动改了 node_modules/hexo-renderer-markdown-it/lib/renderer.js,但过几天:

  1. 你换电脑,重新 npm install → 文件被重新下载,你的修改没了
  2. 同事克隆项目运行 npm install → 他的环境里没有你的修改
  3. 你升级了某个依赖 → 整个 node_modules 被刷新,修改被覆盖

所以直接改 node_modules 就像在水上写字,无法持久保存

patch-package 是什么?

patch-package 是一个 npm 工具,它能把你对 node_modules 的修改保存成一个 .patch 补丁文件,这个文件放在项目根目录的 patches/ 文件夹里,可以提交到 Git

之后每次执行 npm install(或 yarn install)后,它会自动把补丁重新应用到对应的包上。

具体怎么用?

  1. 修改 node_modules 里的文件

按之前的方案,修改:

node_modules/hexo-renderer-markdown-it/lib/renderer.js

添加 parser.use(require('markdown-it-named-headings'))

  1. 安装 patch-package
npm install patch-package --save-dev
  1. 生成补丁文件
npx patch-package hexo-renderer-markdown-it

执行后,项目根目录会出现:

1
2
patches/
└── hexo-renderer-markdown-it+6.0.0.patch
  1. 把补丁提交到 Git
1
2
git add patches/
git commit -m "fix: 修复 markdown-it 一级标题锚点问题"
  1. 设置自动应用

package.json 里添加 postinstall 脚本:

1
2
3
4
5
{
"scripts": {
"postinstall": "patch-package"
}
}

这样以后任何人运行 npm install 时,补丁都会自动打上去

图片路径思考

私密性

如果会用到草稿

  • 文章专属图片(开启 post_asset_folder: true ) :如果图片存放在 source/_drafts/草稿名/ 专属目录下,执行常规的 hexo generate 时,这些图片 不会 被生成到 public 文件夹中。只有在使用 hexo generate –draft 渲染草稿,或者将草稿发布为正式文章后,图片才会被打包。
  • 全局静态资源图片 :如果图片直接存放在 source/images/ 等全局公共目录中,Hexo 会将其视为全局静态文件,无论是否仅被草稿引用,每次生成时 都会 将其直接复制到 public 文件夹中。

调试不顺利

Windows 彻底清空依赖重装

cmd 依次执行:

1
2
3
rmdir /s /q node_modules
del package-lock.json
npm install

再次审计

npm audit