Imported from zhongweigu/MyBlogRepository (
AGENTS.md). Install upstream withnpx skills add zhongweigu/MyBlogRepository. Copyright stays with the author.
仓库说明 / 工作约定
这是一个 Hexo 7 + Butterfly 主题的个人博客,文章放在 source/_posts/*.md,站点根目录就是本仓库。
画 mermaid 导图的既定流程
用户经常会给一张思维导图的截图,要求「生成对应的 mermaid 导图」并追加到某篇文章里。按下面这套做。
但别默认所有图都要转 mermaid:思维导图才走这套流程,字段框、指针链这类结构示意图直接贴原图更好——判断标准和方法见第 14 节。
1. 用主题的标签写法,不要用代码块围栏
{% mermaid %}
%%{init: {...}}%%
graph LR
...
{% endmermaid %}
原因:Butterfly 的渲染脚本查找的是 pre > code.mermaid,而本站 _config.yml 里 markdown.render.langPrefix 是 language-,代码围栏生成出来的是 class="language-mermaid",选择器匹配不上,图不会渲染(只会显示成代码块)。
2. init 模板照抄
%%{init: {
"theme": "base",
"themeVariables": {
"primaryColor": "#5b9bd5",
"primaryTextColor": "#ffffff",
"primaryBorderColor": "#41719c",
"lineColor": "#41719c",
"secondaryColor": "#ffffff",
"tertiaryColor": "#e2f0d9",
"fontSize": "16px",
"fontFamily": "-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif"
},
"flowchart": {
"nodeSpacing": 8,
"rankSpacing": 26,
"curve": "basis",
"htmlLabels": true,
"subGraphTitleMargin": { "top": 0, "bottom": 14 },
"padding": 4,
"useMaxWidth": false,
"wrappingWidth": 1000
}
}}%%
-
fontFamily里的双引号必须转义成\"。写成单引号会让整段%%{init}%%静默失效——主题色全丢,节点变成 mermaid 默认的淡紫色,且不报错。 -
fontFamily要与站点字体对齐,否则「量字宽」和「实际渲染」用两套字体,尺寸会算错。 -
节点高度靠
flowchart.padding控制(默认 15)。用户觉得图「太高、太空」时就是它。实测:padding: 15单行节点高 54px,padding: 8→ 40px,padding: 4→ 32px,padding: 2→ 28px(2 已经明显局促,不建议)。当前用 4,配nodeSpacing: 8。 -
注意这样改必须走 init 配置:mermaid 在渲染时就把节点尺寸算死了,事后用 CSS 调
line-height/p{margin}完全无效(实测四种 CSS 方案尺寸一模一样)。 -
useMaxWidth: false:让图按原始尺寸渲染,不要随容器缩放。 默认(true)时 mermaid 给 SVG 加width="100%" max-width:<原始宽>px,图越复杂原始宽越大、被缩得越小,于是「复杂图的字看着比简单图小」。设成 false 后 SVG 固定为原始宽,不同复杂度的图字号一致,宽图靠容器横向滚动承接。 配套 CSS 已写在_config.butterfly.yml的inject.head里:.mermaid-wrap{overflow-x:auto;overflow-y:hidden;padding-bottom:6px}+.mermaid-wrap > svg{display:block;height:auto}(display:block是必须的——SVG 是 inline 时会被text-align:center居中,横向滚动会出现「左边滚不到」的问题)。 ⚠ 改完_config.butterfly.yml必须npx hexo clean再 generate:public/是增量缓存,只改配置不会让已生成的页面失效(2026-10-12 加.edgeLabel那条时被坑过一次——生成的页面里根本没有新 CSS)。 -
标签行距同样写在那段注入 CSS 里:
.nodeLabel,.nodeLabel p,.nodeLabel span{line-height:1.8;margin:0}。注意必须不加作用域,因为 mermaid 量文字尺寸用的临时元素挂在 body 上,加了.mermaid-wrap前缀就影响不到它(实测无效)。 -
每行字数的限制(≤11 汉字)来自「量出来的宽度 < 实际渲染宽度」这个误差,跟节点高矮无关,所以
padding调小并不代表每行能放更多字(反而余量更小)。这条规则不要放松。
3. 换行:机制已查清(2026-10-11)
折行阈值由 flowchart.wrappingWidth 决定,与 padding 无关。 实测(同一批节点,只改 padding:4 / 6 / 8 / 10 / 15):
- 五种 padding 下「不折行的最大单行长度」全都是 12 个汉字。
默认 wrappingWidth: 200 ≈ 12 个汉字(16px 字号)。mermaid 在排版阶段就按这个宽度把标签切成行,然后按切完的行算框宽。用户浏览器里标签不会被再折一次(整行渲染),所以行一旦超过 200px,渲染宽度就超出框 → 被裁;我这边的浏览器会自动折一次,反而把这个 bug 掩盖了。所以**「本地看着正常」不代表没问题**,真正的判据是「我手写的每一行有没有在本地被再折一次」。
实测不同 wrappingWidth 下的上限:
| wrappingWidth | 200(默认) | 320 | 400 | 560 |
|---|---|---|---|---|
| 不折行的最大单行长度 | 12 字 | 20 字 | 24 字 | 32 字 |
现在的做法:"wrappingWidth": 1000 + 仍然手写 <br> 断行,两者配合才安全:
wrappingWidth调大 → mermaid 不再自作主张切行,框严格按我写的行长算宽;- 手写
<br>→ 在「浏览器不折行」的环境里换行位置也一定正确。
行长的真正判据不是字数,而是「本地渲染时这一行有没有被再折一次」:没被折,说明文字宽度没超出框,用户那边(同机同字体)也不会裁;被折了就说明超了,必须回退或加大 padding。字数只是经验参考(16px 字号下,18 字以内一直安全;实测 30 字的单行也能过,只要不折)。
简单/层级浅的图要主动把行写长:用户明确反馈过「简单的图右半空一大块」。这种情况把叶子标签合并成单行、让框往右铺满正文栏(例如「双链表」那张:7 条要点全部合成单行,图宽从约 700px 变成铺满约 950px)。别指望动 padding 来加宽——它只影响框与文字的留白,不影响折行。
排版标准(用户 2026-10-11 明确):每个标签都能一行就一行,顶到放不下才断——是贪心填满,不是按语义断句。安全单行宽度实测约 ≤500px(16px 字号下约 30 个汉字);合并后超过 500px 才回退断行。
工具:临时目录 reflow.js —— 先把标签里的 <br> 全部合并,再用 500px 贪心重排(跳过含内联 <span> 或 {} 代码的标签,别打乱代码和按词上色)。2026-10-11 用它一次性把 9 张图里 40 条标签合并成单行,各图宽度普遍从约 700px 铺到约 950px,右半的空白基本消失。
⚠️ 合并完必须本地渲染确认没有一行被再折(有一条被折就说明那一行超了 500px,要回退断行)。另外注意:合并后会出「两个短句被硬拼成一句」的情况(例如标题里的「…+ 运算即定义数据结构」「设计一个好的数据结构设计一个好的算法」),要手动补上分隔符(「,」或「 / 」)。
归档两条已实测无效的路,别再试:给单张图设
wrappingWidth: 176(只会让框更窄、更早裁字)、在 init 里加"securityLevel": "loose"(对折行没有任何影响)。
4. 特殊字符
<要写成#lt;,"要写成#quot;,否则会被当成 HTML 标签解析。- 下标/上标直接用 Unicode 字符(
₂²³ⁿ),不要用<sub>/<sup>。 - 标签最末尾不要写
;:mermaid 把它当语句分隔符,渲染出来那个分号会消失(2026-10-11 踩的)。试过四种写法都不行:#59;(实体形式)、; (后面垫个空格)、把分号单独包进<span>、分号后加零宽空格。分号写在标签中间是正常的(例如data;</span><br>…,后面还有内容就没事)——所以代码片段要么干脆不写结尾分号,要么让分号后面还跟着别的内容。
5. 「旁注气泡」用 subgraph 表示
原图里那种绿色的旁注气泡,用 subgraph 表达:标题就是注释文字,框住它注释的分支。
subgraph N1["标题就是注释内容"]
NodeId
end
style N1 fill:#f4faf0,stroke:#70ad47,color:#000000,font-size:12px
- 框太窄时 mermaid 会把标题压掉、或者让节点盖住标题 → 必须把框开得够宽(把整条分支包进来,而不是只包一个节点)。
- 注释框标题统一
font-size:12px(注意:这条会引出下面 5.1 那个 200px 的坑)。
5.1 标题宽度:mermaid 写死 200px,已用 CSS 解除(2026-10-11)
症状:注释框标题「显示不全」——文字在框边缘被硬生生截断(不是换行,是右边少了一截)。
机制(读 mermaid@11.4.1 源码 + 实测 DOM 确认):subgraph 标题由 createText(..., width) 生成,width 默认硬编码 200,style N1 ... 里给的 font-size 是量完尺寸之后才套上去的(et(e) → m.labelStyle,在 C.attr('width', ...) 之后)。也就是说:
- mermaid 先按**默认字号(约 18~20px)**量标题宽度,超 200px 就把 div 改成
display:table; white-space:break-spaces; width:200px(折行分支),否则保持display:table-cell; white-space:nowrap; max-width:200px(不折分支); - 量完才把标题改成 12px。于是这个 200px 的上限是按「18~20px 的标题」定的,按 12px 显示时早就不够用了;
- 折行分支下多出来的行会被节点盖住,不折分支下文字直接溢出 200px 被
foreignObject裁掉。同一份 markdown 在不同浏览器/缩放比下走哪个分支都可能不一样,所以用户那边看到截断、我本地却是两行——这正是「本地正常不代表没问题」的又一处。
解法(已写进 _config.butterfly.yml 的 inject.head,全站生效,改动只有这一行 CSS):
.cluster-label foreignObject > div{max-width:none !important;display:table !important;margin:0 auto !important}
max-width:none→ mermaid 量到的宽度就是文字真实宽度,永远不会等于 200,折行分支再也不会被触发(不再有折行/裁字的二选一);display:table+margin:0 auto→ 让文字在foreignObject里水平居中。少了这句文字会偏左(FO宽-文字宽)/2;(实测基线里「注意审题:带头否?」就偏左了 30 多像素)- 选择器绝对不能加
.mermaid-wrap前缀:量尺寸时标签还在 mermaid 的临时容器(挂在body下)里,加前缀匹配不到,量完再套上就晚了。 - 副作用:框会按「18~20px 量出来的标题宽」撑宽一点点(比文字实际需要的宽),所以标题两侧留白对称,看着正常。
有了这条 CSS 之后,注释框标题仍然建议 ≤ 20 个汉字:标题现在是单行铺开,太长会把框撑得很宽,整张图缩小得更多。
验证方式(可复用):npx hexo generate 之后,把 public/<文章路径>/index.html 复制一份、在 </body> 前插一段测量脚本,起 python -m http.server 指向 public/,再用 headless Chrome --dump-dom 读结果。每条标题要看的指标:offCenter(文字中心 − 框中心,应为 0)、divH(一行应为 1 个行高)、以及 foW 与 textW。截图渲染注意 --no-proxy-server、--virtual-time-budget=30000,窗口给足高度。
6. 配色约定
| 用途 | 填充 | 描边 |
|---|---|---|
| 根节点 / 主题色 | #5b9bd5 |
#41719c |
| 普通节点 | #fff |
#5b9bd5 |
| 原图自带的分组框(如「非线性结构」) | #e2f0d9 |
#5b9bd5 |
| 额外加的注释框 | #f4faf0(更淡) |
#70ad47 |
| 重点 | 白底红框,或深红底白字 | #c00000 |
7. mermaid 版本已锁定
_config.butterfly.yml 里 CDN.option.mermaid 锁定为 11.4.1,不要去掉。CDN 上的 mermaid/dist/mermaid.min.js 是 "latest",会随时间变化,换行/排版行为会跟着变,导致「我这里正常、用户那里被裁」这种玄学问题。
8. 生成后必须本地验证
npx hexo generate
# 用 python -m http.server 起静态服务指向 public/,再 headless Chrome 截图
npx hexo clean
放大看截图,重点检查:有没有非预期的折行、有没有文字被裁、subgraph 标题是否完整。
这台机器的坑:系统代理(Clash,
127.0.0.1:7890)会把127.0.0.1的本地请求也接管,导致 headless Chrome 截出来一片空白。本地截图一律加--no-proxy-server(实测加了就正常)。同理,如果截图空白或页面内容异常短,先怀疑「mermaid 根本没跑起来」(CDN 请求被代理拦了),而不是图本身有问题——把待测那张图的源码单独抽出来渲染一遍就能区分。
9. 提交与推送
- 改完直接 commit + push,不用再问(用户 2026-10-11 明确要求「以后记得自动推送」)。
origin=git@github.com:zhongweigu/MyBlogRepository.git,分支main。 - 推送前跑一次
npx hexo generate确认无报错,推送后npx hexo clean。 - 只提交本次任务相关的文件;工作区里用户自己的未提交改动不要顺手带上(除非用户说「一并推送」)。
- 但用户自己会在工作区手动改
.md(2026-10-14 明确:遇到改过的内容要一起推送)。所以每次提交前先git status/git diff扫一眼该文件:既有我这次的改动、也有他手动删改的内容时,两样一起 commit + push(上面那条「不要顺手带上」指的是与本任务无关的其他文件,不是指他对手头这篇文章的直接编辑)。 - 临时文件(截图、生成脚本、
public/、db.json)用完清理掉。
10. 图后的补充说明用引用块
某张图的内容如果容易被误读、或涉及原图没展开的背景,就在该图正下方补一行 Markdown 引用块:
> 注:一句话说清楚。
- 用
>引用格式,与图之间空一行; - 一句话讲完,不要展开成段落(要展开就写进正文);
- 只给真正需要澄清的地方加,不要每张图都加。
首次这么用的例子:2026-10-11「顺序表」那张图下的补注——「物理上也相邻」在引入分页/虚拟存储后的准确含义(指的是地址空间连续,不是内存物理页框连续)。
11. 标签内按词上色(代码高亮那种)
mermaid 只能给整个节点配色。要让标签里某几个词有颜色(比如代码块的关键字/类型/变量,或强调某个词),在标签里直接写内联 span,并且必须带 !important:
C1["<span style='color:#1f4e79 !important'>typedef</span> <span style='color:#0f7b6c !important'>LNode</span>{"]
- 必须加
!important:mermaid 生成的<style>用!important把标签颜色钉死了,不加就被压回黑色(实测:HTML 保留下来了,但颜色不生效)。 - 属性用单引号(mermaid 标签本身是双引号括起来的)。
- 实测不行的两种写法:
<font color="...">(表现属性优先级最低)、只写class靠外部 CSS(选择器拼不过 mermaid 的!important)。 - 约定配色:关键字
typedef→#1f4e79(深蓝);struct之类 →#2e7d32(绿);类型名(LNode/ElemType/LinkList)→#0f7b6c(青绿);变量名(data/next)→#7a3e9d(紫)。 - 首次用例:2026-10-11「单链表的定义」那张图里的
typedef struct LNode{...}代码节点。
12. 按层级配色(层次感)
所有节点都一个白底蓝框会显得「没有层次」。每张图按深度分配样式(根节点算第 1 层,lvlN 里的 N 是「根下面第 N 级」):
| 层级 | classDef |
|---|---|
| 根节点(第 1 层) | root:fill:#5b9bd5,stroke:#41719c,color:#fff,font-weight:bold |
| 第 2 层 | lvl1:fill:#9dc3e6,stroke:#2e6da4,stroke-width:2px,color:#0d2f4d |
| 第 3 层 | lvl2d / lvl2l:白底 或 #dbe8f6 底,边 #1f4e79 或 #c3d9ef |
| 第 4 层 | lvl3d / lvl3l:同上(stroke-width:2px) |
| 第 5 层及更深 | lvl4d / lvl4l:同上(stroke-width:1.5px) |
| 重点 | key:白底红框,覆盖层级色 |
第 3 层起,每一层各自深浅交替(用户 2026-10-11 定的规则,试了三档后选了「底色交替」这一档):
- 分组方式:把该层的节点按「父节点」分成若干组(同一个父节点的兄弟是一组),组与组之间一深一浅交替——第 1 组深(
d)、第 2 组浅(l)、第 3 组深……组的先后 = 父节点在源码里出现的先后(也就是图上自上而下的顺序)。 - 每层各算各的:第 3 层按「二级分支」分组交替;第 4 层重新按「第 3 层的父节点」分组交替;第 5 层同理。父节点自己是什么底色不影响子节点的深浅——踩过:第一版让整条分支同色(第 4 层跟着分支走),用户指出「第四层也是交错的,父节点什么底色无所谓」。实际效果例如「单链表的操作」图中:插入的孩子是深底、删除的孩子是浅底、按位查找的孩子又深、Key 的孩子又浅。
- 分组的先后必须按「渲染出来」的上下顺序排,不能按源码里
-->的书写顺序(2026-10-11 第二次踩):mermaid 的排版顺序不总等于书写顺序——「队列的顺序实现」那张里,带旁注框的「重要考点」支被排到了整张图最上面,于是按源码顺序算出来的深浅是(实现思想深、重要考点浅、分析思路深),画出来却是(重要考点浅、实现思想深、分析思路深),中间两组撞成同色,被用户一眼看出「中间忘了颜色交错」。做法:先渲染一遍,从真实页面读每个节点的 y 坐标,按 y 排序得到每层的分组顺序,再交替配色(脚本tmp-geo.js:注入测量脚本 → headless Chrome 取每个节点的 y → 按位置分组 → 写回 class)。 - 具体配色:深 =
fill:#dbe8f6,stroke:#1f4e79;浅 =fill:#f6fafe,stroke:#c3d9ef;描边宽度沿用该层原本的宽度(第 3、4 层 2px,第 5 层 1.5px)。 - 只有根节点和第 2 层不参与交替,保持原样。
- 红框(
key)节点照常在最后单独class … key;,它的fill:#fff会盖掉深浅底,红框不受影响。
字号也按层级递推(以该图的 themeVariables.fontSize 为基准,实测各图基准是 14 或 16):根 +4px、第 2 层 +3px、第 3 层 = 基准、第 4 层 −1px、第 5 层 −2px。例如基准 16 时是 20/19/16/15/14。
写法:每个图的 classDef 区加上需要的 lvlNd/lvlNl,再 class <该层所有节点id> lvlNd; 这样分组。
- 一层给实色底、往下逐级变白、边线逐级变浅。实测更细的渐变(例如一层只给
#bdd7ee)肉眼几乎看不出差别,必须拉开对比度。 - 深浅差异要够大:相邻两组的区别如果只靠边框色(深
#2e6da4/ 浅#9dc3e6)区分,用户反馈「看起来不太明显」;现在改成的「底色交替」才够明显。 - 字号分级安全、加粗分级不安全:mermaid 量宽度时会用节点自己配的
font-size(所以字号分级不会裁字,实测过),但font-weight:bold加粗后实际渲染更宽而不计入测量,长标签会裁字——所以层次一律用字号 + 颜色表达,不要用加粗。 - 红框节点(
key)同时挂层级 class 和 key,这样它保留自己那层的字号,只是边框红色。classDef里key必须排在 lvl1~lvl4 之后,否则覆盖不掉颜色。 ⚠ 必须分成两条class语句写:class S1 lvl1;再加class S1 key;。写成一条class S1 lvl1,key;时,mermaid 会把lvl1,key当成一个类名加在节点上(DOM 里就是class="node default lvl1,key"),两个 classDef 都匹配不上,节点会掉回主题默认样式(主题蓝底#5b9bd5+ 默认字号),且不报任何错——2026-10-12 在「红黑树」那张图上踩的,靠「主题蓝出现了 2 次」才发现。 classDef/class的类名不能叫end——end是 mermaid 的保留字(用来结束 subgraph),叫这个名字整张图会直接渲染失败(页面上只剩一个空壳.mermaid-wrap,我这边看到的现象是校验脚本报图N: NO_SVG)。2026-10-12 画红黑树插入流程图时用了classDef end,改成done就好了。- 节点一多手写 class 容易漏(踩过:漏写一个节点,它掉了 mermaid 默认蓝底)。用脚本从边表算深度后自动分配;副本见临时目录的
assign-levels.js(2026-10-11 用它一次性改了全部 8 张图)和band-fills.js(同日用它把全部 10 张图改成深浅交替;脚本从-->边表算层级,逐层按父节点分组、组间奇偶交替,保留各图原有字号)。 - 验证时看「填充统计」里有没有
none:逐图统计节点实际 fill 值时,如果出现none,说明有节点没被分到任何层级 class(它的 rect 上就没有内联 fill,掉回 mermaid 默认样式)。这个信号比肉眼看图灵得多——2026-10-11 就是靠它发现「树」那张漏写了一个Basic1。 - 主题蓝
rgb(91, 155, 213)每张图应当恰好出现 1 次(只有根节点是这个色)。出现 2 次就说明有节点丢了 class、掉回主题默认色——none那条检查抓不到这种(它是有 fill 的,只是 fill 是主题色)。2026-10-12 加的这条检查(tmp-verify.js输出THEME_BLUE=n),当天就靠它接了「红黑树」那张的关键节点。注意统计 fill 要用.label-container取形状,不能用rect——菱形判断框(id{"…"})画的是polygon,用rect会取到 label 组里那个空 rect,它继承了.node rect{fill:主题色},会误报成主题蓝。 - 交替配色也要机器核一遍:把源文件里的边表和
class分配解析出来,再配合浏览器里每个节点的实际 y 坐标,按「父节点分组 → 组序按渲染后的上下位置 → 应当 d/l/d… 交替」逐层核对(tmp-verify.js里已加这条检查,输出BAND L3: …和*** 交替配色问题: …)。只看挂了层级 class 的节点,纯key(红框白底)节点不参与深浅判断、只提示。2026-10-11 就是靠它逮到「最小生成树」那张把定义组和 Prim 组写成了同一个深浅(用户一眼看出来的),顺手把全部 49 张图过了一遍确认只有这一处。
13. 追加新图
- 新图按教材顺序追加到文章末尾。文章已按王道的大章节拆好:
# 二、线性表(下含## 顺序表/## 链表)、# 三、栈、队列和数组(下含## 栈/## 队列/## 栈和队列的应用)。新内容要放进对应小节末尾,需要时再加新标题(Sidebar 目录会自动带上,加完值得回头确认目录层级没串)。 - 标题是三级结构(2026-10-11 全篇补过一次,51 处;侧边目录从 24 条涨到 75 条):
#章(一、绪论 / 二、线性表 / …)→##节(王道的大节,如## 顺序表、## 图的存储及基本操作)→###小节。每个主题(一张图 + 它下面的注释,或者一段代码、一张表格)前面都配一个### 标题,标题直接取自课件的说法(如### 邻接矩阵法、### 深度优先遍历(DFS)、### 哈夫曼树与哈夫曼编码)。两种情况要留意:① 章里原本没有##的(一、绪论 / 四、串 / 六、图的第一节),先补##再补###;② 一层只有一个内容的不再往下分(例如## 特殊矩阵的压缩存储、## 线索二叉树就只有一节内容,保持##即可)。补完用Select-String '^#{1,3} '把整棵树列出来,确认没有跳级、没有把内容套错层。 - 长代码用真正的代码块(
```c…```),不要塞进 mermaid 节点:站点会给代码卡片加语言标签、行号和高亮,效果比节点里逐词上色好得多(首次用例:2026-10-11「栈在括号匹配中的应用」里的bracketCheck)。短到能和图并排看的片段(一行 typedef、两三个变量的结构体)才继续用 mermaid 节点的逐词上色。 apply_patch的锚点必须唯一:同一篇文章里style A1 …、class Root root;、"padding": 4,这类行会在多张图里重复(注释框 id 都是A1)。锚到这种行会把新图插到中间——踩过:把「双链表」插到了「单链表的定义」和「单链表的操作」之间。稳妥做法是锚在最后一块独有的行(比如前一版 classDef 里带唯一节点 id 的那行),或者直接用脚本按Root["…"]重排。- 追加完用
Select-String 'Root\["'列一遍所有图的根节点,确认顺序。
14. 先选表现方式:不是所有内容都要画 mermaid(2026-10-11)
用户 2026-10-11 明确:「发给你的图不一定要生成 mermaid,也可以直接贴上去(仅限那种非思维导图类的),选择合适的即可」。所以拿到图先判断类型:
- 思维导图(有层级、分支、括号缩进那种)→ 走 1~13 的流程转成 mermaid。
- 非思维导图类 → 直接贴原图。典型是「字段方框 + 指针链」的结构示意图(十字链表、邻接多重表那种)、带旁注箭头的课件图、代码截图:这类图的信息主要在「框怎么排、箭头怎么连」上,转成 mermaid 只会失真或难读。带权图/带环图的例题图也算这一类——2026-10-11 试过把 Dijkstra 例题那张(5 个顶点、10 条弧、还有回路)画成 mermaid,结果 dagre 把它排成一团、边上的权值标签也挤没了,最后还是贴课件原图最清楚。判断标准很简单:要"画得像课本那样"的图,就贴原图;只是"分个层级"的,才画导图。
- 文字型清单(函数/接口清单、参数说明、代价对照那种)→ Markdown 表格。一行一个条目、两列对齐,比 mermaid 的节点框清楚得多,塞进 mermaid 属于形式大于内容——踩过:2026-10-11「图的基本操作」那十个函数先画成了导图,用户直接指出「用表格更好,mermaid 在这里不合适」。判断口诀:内容是一张表就画表格,是一棵树才画导图。
踩过的坑:十字链表/邻接多重表那两张,我先按「链的走向」硬画了流程图(顶点结点 → 弧结点,连线标 tlink/hlink),结果既不如课件原图直观,又撞上一个新坑——主题把边标签的文字染成了白色(primaryTextColor: #ffffff 生成出 #mermaid-N span{color:#ffffff},而边标签底色又是白的,等于白字白底「看不见」)。绕了一圈才想到:这种图本来就不该转,用户随后直接给了课件原图,我画的两张删掉就完事了。
边标签颜色的问题 2026-10-12 已经修掉了:在 _config.butterfly.yml 的 inject.head 那条 mermaid CSS 末尾加了 .edgeLabel,.edgeLabel span,.edgeLabel p{color:#1f4e79 !important}(mermaid 自己的规则没有 !important,所以能盖住)。所以画流程图(流程、判定分支这种,标注边)现在没问题了——2026-10-12 红黑树插入那张流程图就是首个用例。
贴图的方法(跟仓库里其他帖子一致;_config.yml 里 post_asset_folder: true):
- 图片存到与帖子同名的目录下,例如
source/_posts/408数据结构/cross-linked-list.png(文件名用可读的英文短横线名,比老图那种时间戳名好认);正文里就一行 markdown 引用,路径要带上这个目录名:,放在对应 mermaid 图/注释的下方。 - 渲染时 hexo 会把它改写成
/2026/10/10/408数据结构/index/xxx.png。必须用 markdown 图片语法,别写裸<img src="408数据结构/xxx.png">——裸标签不走资源改写,路径会 404。 - 贴完先
hexo clean再hexo generate:新加的帖子资源可能被旧的db.json记住错误关联,图片会被生成到别的帖子的目录下(踩过:图片进了public/2025/08/28/C++/index/据结构/,路径里的408数据结构还被截成了据结构),正文 URL 直接 404;clean掉数据库重新生成就正常了。 - 验证:除了第 8 节的 mermaid 检查,贴的图再确认一遍——
public/2026/10/10/408数据结构/index/xxx.png确实存在,本地服务里GET返回 200 且字节数与原图一致。
15. 排版:能用哪些样式(2026-10-13 实测)
用户 2026-10-13 定了条规矩:没有明确指定小标题时,不要自行加 # / ## / ### 标题(否则会污染侧边目录、抢了用户对大纲的控制权)。需要强调或分隔时改用别的书写样式,实测这些都能用:
- 行内 HTML 全部原样输出(下面两个渲染器都认,见第 16 节):
<u>/<mark>/<b><strong>/<i><em>/<kbd>/<sub><sup>/<small>/<del><s>/<font color=...>/<abbr title=...>/<code>/<details><summary>/<div style=...>/<center>/<p style=...>/<br>/<hr>/<span id=...>(锚点)/<span style=...>(内联样式:背景色、边框、字号等)。 ---单独一行 →<hr>分割线,用来隔开同一节里的子话题。- 行内数学
$...$、独立公式$$...$$(现在可以随便写,机制见第 17 节)。 - 折叠块(习题答案、解析这类想藏起来的内容)用主题自带标签:
{% hideToggle 按钮文字 %}…{% endhideToggle %}→ 原生<details>,默认折叠、点开才显示;{% hideBlock 按钮文字 %}…{% endhideBlock %}是点击展开的 div;行内的用{% hideInline 内容,按钮文字 %}。内容照常按 markdown 渲染(公式、表格、代码块都能放;实测 MathJax 在折叠块里也正常排版)。首次用例:2026-10-13「习题训练」四题的答案解析。 - 表格单元格换行用
<br>(markdown 表格里不能写真换行)。列数多、又有一列内容特别长时,短的那一列会被浏览器压成「一字一行」(踩过:2026-10-13「线性表的基本操作」那张 4 列表,第一列按位查找被压成竖排)——给那一列的单元格套<span style="white-space:nowrap">…</span>就能撑开,不要为这个去改全站 CSS(会让别的表也变宽)。
==高亮== 现在可以放心用(渲染器已固定为 markdown-it,见第 16 节);::: tips / ::: warning 这类容器也行。
当前做法(408考点强化-数据结构.md 2026-10-13 起):子话题之间用 --- 分隔,子话题名写成一行 <u>例题:【2011】</u>——既分明,又因为不是标题而不会进目录。
16. markdown 渲染器:已锁定为 markdown-it(2026-10-13 修复)
踩过的坑:package.json 里同时装了 hexo-renderer-kramed 和 hexo-renderer-markdown-it,两个都注册 .md。Hexo 加载插件是并发的(node_modules/hexo/dist/hexo/load_plugins.js 里用 bluebird.map,没限并发),谁最后注册谁生效,顺序不稳定——实测连续 7 次 hexo clean; hexo generate:4 次走 markdown-it、3 次走 kramed。后果是同一份源码两次构建的 HTML 可能不同:==高亮==、::: tips 容器、脚注这些 markdown-it 插件时好时坏,而 $$ 公式在 markdown-it 下又会被当普通文本拆坏。
修法:把 hexo-renderer-kramed 从 package.json 的 dependencies 里删掉即可——Hexo 只按 package.json 的依赖列表加载插件,node_modules 里的残留不影响;下次 npm install 会顺手清掉它。现在恒定走 markdown-it,_config.yml 里 markdown.plugins 配的那批插件(==高亮==、::: tips/success/warning/danger、~下标~、^上标^、脚注、deflist、emoji …)都稳定可用。
17. 数学公式由 scripts/math-guard.js 兜底(2026-10-13 修复)
markdown-it 本身不认识数学公式,会把 $...$ / $$...$$ 当普通文本处理,出三类问题:
- 「反斜杠 + 标点」被转义吃掉:
\,→,、\;→;、\%→%、\{→{(2026-10-13 用户看到2^{,t+1}这种「多余的逗号」就是这么来的); _*^~被强调 /markdown-it-sub/markdown-it-sup抢走(p_k^2、\sum_{k=1}^{K}会被拆出<sup>);- 多行
$$...$$的换行被插成<br>,公式被拆成多个文本节点,MathJax 认不出来。
做法:站点根目录的 scripts/math-guard.js 往 markdown-it 里挂了一条行内规则(经由 markdown-it:renderer 过滤器注入——hexo-renderer-markdown-it 每次渲染前都会 execFilterSync 这个过滤器),遇到 $...$ / $$...$$ 就整段吞掉,原样吐成一个「原始 HTML」token(只做 HTML 转义:& < > " 换实体,其余字符不动)。这段内容于是完全不经过 markdown:不会被转义、不会被 sub/sup 抢走、也不会被插 <br>;定界符照旧,浏览器里仍由主题的 MathJax 按 $...$ / $$...$$ 渲染。
也试过(并且能跑通)把它们转成
<script type="math/tex">(主题mathjax.pug里有专门处理这种写法的findScript),但那条路一旦 MathJax 脚本没加载成功,公式会整段消失、页面只剩空白;保留文本定界符最差也能看到$x^2$原文,更稳。
现在写公式可以放心用:\, \; \% \{ \}、^ _ ~、\\(aligned 里的换行)、跨行 $$...$$、引用块里的 > $$,全都原样送进 MathJax。围栏代码块和行内代码里的 $ 不会被当成公式(markdown-it 的块级/行内代码规则先一步把它们吃掉了)。
注意:
- 定界符要成对;落单的
$$认不出来,会原样显示成文字(COA-Review.md里原来有一处空公式,页面上露出两个字面$$,2026-10-13 已清掉)。 - 行内公式
$...$的内容两侧不能有空格,否则不认——这是为了少误伤「$100 和 $200」这类写法。 - 不要再往公式里塞 HTML 实体(
\之类)去绕转义:现在不需要,而且会被当字面文本显示出来。 - 公式编号:
_config.butterfly.yml里math.mathjax.tags由all改成了ams(2026-10-13)——现在只有\begin{equation}/\begin{align}这类 AMS 环境才编号,普通的$$...$$不再显示编号。原因是侧边那个第三方「AI摘要」插件(ai.tianli0.top的tianli_gpt)会 clone 正文,MathJax 会把克隆出来的副本也一起编号,导致编号看着像从 (3) 开始;改用ams后这个现象就没了。
18. 行文:能省则省(2026-10-14)
用户 2026-10-14 明确:别写"显而易见、读者自己能推出来"的解释。被点名删掉的是这段:"两条规律能帮你少背一半:① 和 ② 的结构其实一模一样……③ 最省心"。同类还有「对着入队那两行读一遍就清楚了」这种替读者读代码的话。
规则:
- 表格、公式、代码、结论该给给足,但不要在它们旁边再补一段"帮你理解"的总结。
- 正确做法写完就收,不必再解释"为什么另一种写法不行"(除非那正是考点)。
- 需要展开解释时,用户会自己开口问(他会直接说"这里要讲清楚"),到那时再写足。
- 这条同样适用于最终回复:少铺垫、少复述,先给结论。
