Hexo 包管理器 如何确定hexo的包管理器? 方法1:
在终端运行
ls | Where-Object { $_ .Name -match "package-lock|yarn.lock|pnpm-lock" }
方法2:
更简单的方法 :直接看文件是否存在
1 2 3 Test-Path package-lock .json Test-Path yarn.lockTest-Path pnpm-lock .yaml
哪个返回 True,就是哪个包管理器。
【本博客】看到 package-lock.json → 锁定使用 npm ,不要混用其他包管理器,避免 lock 文件冲突。
依赖
npm install xxx --save → 安装生产依赖
npm uninstall xxx --save → 移除生产依赖
如果不带 --save,只是删掉 node_modules 里文件,package.json 还会保留记录
不带 --save 的 npm uninstall xxx: 只删除 node_modules,不会改 package.json 于是就变成:
✅ node_modules 里面文件没了 ❌ package.json 还保留这条依赖记录
这种包,npm ls 会标记成 extraneous
npm ls --depth=0 | findstr "extraneous"
含义:
npm ls --depth=0 →列出所有顶层直接依赖
| 管道:把前面命令输出传给后面
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 title: kika-秋 subtitle: '' description: '品味是最高判断力' keywords: author: Kika language: zh-CN timezone: Asia/Shanghai url: https://kkkika.github.io/ permalink: :year/:month/:day/:title/ permalink_defaults: pretty_urls: trailing_index: true trailing_html: true source_dir: source public_dir: public tag_dir: tags archive_dir: archives category_dir: categories code_dir: downloads/code i18n_dir: :lang skip_render:
文章/写作设置 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 new_post_name: :title.md default_layout: post titlecase: false external_link: enable: true field: site exclude: '' filename_case: 0 render_drafts: false post_asset_folder: false relative_link: false future: true syntax_highlighter: 'highlight.js' highlight: auto_detect: false line_number: true line_threshold: 1 tab_replace: "" exclude_languages: ['mermaid' ] wrap: true 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 index_generator: path: '' per_page: 12 order_by: -date meta_generator: true date_format: YYYY-MM-DD time_format: HH:mm:ss updated_option: 'mtime' 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: ignore: theme: butterfly 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 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']" CDN: 'fastly' globalOptions: colorFreezeLevel: 3 color: ['orange' ] encrypt: message: "请输入密码" wrong_pass_message: "密码错误,请重新输入" tags: - {name: password , password: 1111 } algorithm: "aes" expire: 1 silent: false theme: blink 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 nav: logo: /img/icon.jpg display_title: true fixed: false menu: 主页: / || fas fa-home 归档: /archives/ || fa fa-folder-open 生活 || fas fa-sun fa-spin: 相册: /photos/ || fa fa-image 音乐: /music/ || fa fa-headphones 快速链接: /links/ || fa fa-link
代码块设置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 code_blocks: theme: darker macStyle: true height_limit: 300 word_wrap: true copy: true language: true shrink: false 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 favicon: /img/icon.jpg avatar: img: /img/icon.jpg effect: false disable_top_img: false default_top_img: /img/default_top.jpg index_img: /img/background.jpg archive_img: footer_img: transparent background: /img/background.jpg cover: index_enable: false aside_enable: true archives_enable: true position: both default_cover: /img/default_cover.jpg error_img: flink: /img/friend_404.gif post_page: /img/404.jpg error_404: enable: true subtitle: '页面不存在' background: /img/error-page.png
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 post_meta: page: date_type: both date_format: date categories: true tags: true label: true post: position: center date_type: both date_format: date 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_site_info_top: index_top_img_height: 400px subtitle: enable: true effect: true loop: true typed_option: source: false sub: - '从提问到输出' index_layout: 6 index_post_content: method: 2 length: 500
文章设置 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 toc: post: true page: true number: true expand: false style_simple: true scroll_percent: true post_copyright: enable: true decode: true author_href: https://kkkika.github.io/ license: CC BY-NC-SA 4.0 license_url: https://creativecommons.org/licenses/by-nc-sa/4.0/ reward: enable: false text: "请喝咖啡~" QR_code: - img: /img_reward/wechat.png text: 微信 - img: /img_reward/alipay.png text: 支付宝 post_edit: enable: false url: related_post: enable: true limit: 6 date_type: updated post_pagination: 1 noticeOutdate: enable: false style: flat limit_day: 365 position: top 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: nav: owner: enable: true since: 2024 copyright: enable: true version: true custom_text:
侧边栏设置 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: enable: true hide: false button: false mobile: false position: left 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 sort: date sort_order: card_newest_comments: enable: true sort_order: limit: 3 storage: 10 avatar: true card_categories: enable: true limit: 6 expand: none sort_order: card_tags: enable: true limit: 0 color: false orderby: random order: 1 sort_order: card_archives: enable: false type: monthly format: MMMM YYYY order: -1 limit: 8 sort_order: card_post_series: enable: true series_title: false orderBy: 'date' order: -1 card_webinfo: enable: true post_count: true last_push_date: true sort_order:
右下角按钮 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 rightside_bottom: translate: enable: false default: 繁 defaultEncoding: 2 translateDelay: 0 msgToTraditionalChinese: '繁' msgToSimplifiedChinese: '簡' readmode: true darkmode: enable: true button: true autoChangeMode: 1 start: end: rightside_scroll_percent: true rightside_item_order: enable: true show: readmode,darkmode,comment 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 anchor: auto_update: true click_to_scroll: true photofigcaption: false copy: enable: true copyright: enable: true limit_count: 150 wordcount: enable: false post_wordcount: true min2read: true total_wordcount: true 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: use: katex per_page: false hide_scrollbar: false mathjax: enableMenu: true tags: none katex: copy_tex: true
同时要在_config.yml文件配置:
1 2 3 4 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
【注意】需要在文章的 Front-matter 添加 katex: true,对应的文章才会加载数学公式渲染引擎
阶段2【弃用】
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 ;const KATEX_STORE = Symbol ('hexo-katex-store' );const CODE_FLAG = '\x03HEXOKTX\x04' ;const FORMULA_PREFIX = '\x01KX' ;const FORMULA_SUFFIX = '\x02' ;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 (); } } function htmlEscape (str ) { if (typeof str !== 'string' ) return '' ; return str.replace (/[&<>"']/g , c => ({ '&' : '&' , '<' : '<' , '>' : '>' , '"' : '"' , "'" : ''' })[c]); } function validateTex (tex, maxLen ) { if (tex.length > maxLen) { return { valid : false , reason : `公式长度 ${tex.length} 超过最大限制 ${maxLen} ` }; } return { valid : true , tex }; } 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 ; const memCache = new LRUCache (maxCache); let diskCache = {}; let diskDirty = false ; 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] 缓存已保存` ); }); 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 ; content = content.replace (/`[^`]*?`/g , (match ) => { codeBlocks.push (match); return CODE_FLAG ; }); content = content.replace (/```[\s\S]*?```/g , (match ) => { codeBlocks.push (match); return CODE_FLAG ; }); 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' ); 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; }); } 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; }); } 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++]; }); 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' ); const cached = memCache.get (cacheKey); if (cached !== undefined ) return cached; if (diskCache[cacheKey]) { const html = diskCache[cacheKey].html ; memCache.set (cacheKey, html); return html; } 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
阶段3🍊 npm install hexo-filter-katex --save
在站点根目录的 _config.yml 里增加(或合并到已有配置中):
1 2 3 4 5 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
说明:
参考: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: use: local_search placeholder: algolia_search: hitsPerPage: 6 local_search: preload: true top_n_per_article: -1 unescape: true pagination: enable: false hitsPerPage: 6 CDN: 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: use: sharejs sharejs: sites: wechat addtoany: item: wechat,copy_link
评论💬 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 comments: use: Twikoo text: true lazyload: false count: false card_post_count: false twikoo: envId: https://kika2378.netlify.app/.netlify/functions/twikoo region: visitor: false option:
https://kika2378.netlify.app/.netlify/functions/twikoo
Twikoo Netlify部署方法及操作流程,给博客网站添加评论功能
Chat Services 在线客服【没改动】
Analysis 网站分析【没改动】
Advertisement 广告【没改动】
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 theme_color: enable: true main: "#FDB68C" paginator: "#d8a067" text_selection: "#fe6f0f" link_color: "#d89f68" a_link_color: "#fe6f0f" hr_color: "#FFC6A5FF" toc_color: "#fe6f0f" blockquote_padding_color: "#FDB68C" blockquote_background_color: "#FDB68C" category_ui: tag_ui: rounded_corners_ui: true text_align_justify: false mask: header: true footer: false preloader: enable: false source: 1 pace_css_url: enter_transitions: true display_mode: light beautify: enable: true field: post title_prefix_icon: '\f0c1' title_prefix_icon_color: '#f77f39' font: global_font_size: code_font_size: font_family: code_font_family: blog_title_font: font_link: font_family: hr_icon: enable: true icon: '\f094' icon_top: activate_power_mode: enable: false colorful: false shake: false mobile: false canvas_ribbon: enable: false size: 150 alpha: 0.6 zIndex: -1 click_to_change: false mobile: false canvas_fluttering_ribbon: enable: false mobile: false canvas_nest: enable: true color: '253,114,16' opacity: 0.9 zIndex: -1 count: 99 mobile: false fireworks: enable: false zIndex: 9999 mobile: false click_heart: enable: false mobile: false clickShowText: enable: false text: - 富强 - 民主 - 文明 - 和谐 - 平等 - 公正 - 法治 - 爱国 - 敬业 - 诚信 - 友善 fontSize: 15px random: false mobile: false
大图查看模式 1 2 3 4 5 6 7 8 9 lightbox: fancybox
标签插件 Mermaid
Mermaid
``Series`可能有用但还没用上
【其他都没改】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 series: enable: false orderBy: 'title' order: 1 number: true mermaid: enable: true code_write: true theme: 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' ]
其他设置
pjax
snackbar
lazyload
【其他暂定】
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 pjax: enable: true exclude: snackbar: enable: true position: bottom-center bg_light: '#fcc10f' bg_dark: '#1f1f1f' instantpage: false lazyload: enable: true native: true field: site 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: head: - <link rel="stylesheet" href="/css/custom.css"> 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>
自定义 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 ); } .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: bottom:
打开主题配置 _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" expire: 1 silent: false theme: blink 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 .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']" CDN: 'fastly' globalOptions: colorFreezeLevel: 3 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 渲染器的已知 bug :markdown-it 默认不会为 heading 生成 id 属性,导致 TOC 无法为一级标题创建可点击的锚点。
法1 改 main.js 直接改 main.js 的初始化:
打开 themes/butterfly/source/js/main.js,找到页面初始化的地方(通常有 initAdjust )
在 initAdjust 函数定义的紧后面 加上 fixTocH1 的定义,然后在文件最末尾 (DOMContentLoaded 回调结束前)加上调用。
在 initAdjust 后面添加函数定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const initAdjust = ( ) => { adjustMenu (true ) $nav.classList .add ('show' ) } 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 ) }) }
在文件最末尾(DOMContentLoaded 结束前)添加调用
在 main.js 的最后面,你应该能看到类似这样的代码:
在它后面加上:
改完后:
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: bottom: - | <script> ... </script>
直接在 _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: - | <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,但过几天:
你换电脑,重新 npm install → 文件被重新下载,你的修改没了
同事克隆项目运行 npm install → 他的环境里没有 你的修改
你升级了某个依赖 → 整个 node_modules 被刷新,修改被覆盖
所以直接改 node_modules 就像在水上写字,无法持久保存 。
patch-package 是什么?
patch-package 是一个 npm 工具,它能把你对 node_modules 的修改保存成一个 .patch 补丁文件 ,这个文件放在项目根目录的 patches/ 文件夹里,可以提交到 Git 。
之后每次执行 npm install(或 yarn install)后,它会自动把补丁重新应用到对应的包上。
具体怎么用?
修改 node_modules 里的文件
按之前的方案,修改:
node_modules/hexo-renderer-markdown-it/lib/renderer.js
添加 parser.use(require('markdown-it-named-headings'))
安装 patch-package
npm install patch-package --save-dev
生成补丁文件
npx patch-package hexo-renderer-markdown-it
执行后,项目根目录会出现:
1 2 patches/ └── hexo-renderer-markdown-it+6.0.0.patch
把补丁提交到 Git
1 2 git add patches/ git commit -m "fix: 修复 markdown-it 一级标题锚点问题"
设置自动应用
在 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_modulesdel package-lock.json npm install
再次审计