3.2.43: 修代码块重影与语言识别,含 4 个静默 bug (#12)

排查结论:不是插件冲突,对手是主题自己的 kratos.css。

现象一「重影/阴影」的真实成因是 CSS 优先级 + 行高错位:
kratos.css 的 .content code(0,4,1) / .content pre code(0,4,2) 抢走了
border-radius:4px、color:#333、font-size:13px。字号被顶到 13px 后
行高算成 21.06px,而行号槽是 12.5px/20.25px,每行错开 0.81px,
30 行累计 24px —— 视觉上就是「两层东西叠在一起」。

现象二「多一个 copy 按钮」未能复现:2 个页面实测每张卡恰好 1 个
.cb-copy;Prism.plugins 只有 autoloader,copy-to-clipboard 未注册;
Enlighter 插件目录不存在。已请用户指认页面。

修复:
- highlight.css 的选择器提到与原主题同级 (0,4,2),靠出现更晚取胜,
  不用 !important。
- 顺带修掉 4 个静默 bug(都不报错、页面看着正常):
  1. header.php 从未调 body_class(),裸 <body> ⇒ 所有挂 body_class
     过滤器的类全部静默失效,包括行号开关 opt-ln(选项已开却不显示)。
  2. 79 个代码块的语言写在 <pre lang=> 或 data-enlighter-language 上,
     只读 <code> class 必然读不到 ⇒ 标签显示「—」。
  3. 白名单 4 项指向未打包语言(powershell/dos/dockerfile/nginx)。
     hljs 遇到不认识的语言不抛错,只 warn 一句就退回不高亮 ⇒
     整块白板。站内这 4 种用量均为 0,属潜伏 bug。
  4. hljs 把 plaintext 块改写成 language-undefined;highlightAuto
     对纯文本返回字符串 'undefined',if (r.language) 会误当真值。

顺带收敛:yang_hl_languages() 原本手写第二份清单,与 yang_hl_norm_lang()
里的表互相漂移,改为从 yang_hl_known_map() 单一来源派生。

验证:
- 单测 44 + 24 断言全通过
- 反向验证 10 组注入全部变红(含专门注入「恢复手写清单」验证新判据)
- 全站 284 块直连数据库校验:568 通过 / 0 失败,
  语言覆盖率 71.8% → 98.6%,残留与污染全 0
- 剩余 4 块显示「—」是正确降级(jsp×2、latex×2 未打包进 hljs)
- 夹具对照:新版 0 失败 vs 全旧版 10 失败
This commit is contained in:
2026-10-26 11:00:47 +08:00
parent da0d9ae4a1
commit 8f8cffd69e
5 changed files with 304 additions and 52 deletions
+30
View File
@@ -152,15 +152,45 @@
.cb .cb-body > pre { flex: 1 1 auto; min-width: 0; }
/* 🔴 卡片里的 code 必须彻底摆脱原主题 `.content code` 的影响。
*
* 原主题 kratos.css 有两条规则(实测优先级):
* `.k-main .details .article .content code` → (0,4,1)
* margin:0 3px; padding:2px 4px; border-radius:4px;
* background-color:#eff0f1; color:#333; font-size:13px
* `.k-main .details .article .content pre code` → (0,4,2)
* margin:0; padding:0; background-color:transparent
*
* 后者只救了 margin/padding/background。**border-radius、color、font-size
* 全都漏了**,于是:
* · border-radius:4px → 代码区里嵌一块 4px 圆角,和卡片 8px 圆角打架,
* 视觉上就是用户说的「有阴影/重影」
* · font-size:13px → 代码字号被顶大,行高跟着从 20.25px 变 21.06px,
* 而行号槽用的是 12.5px/20.25px ⇒ 每行错开 0.81px,
* 30 行累计错 24px,行号与代码彻底对不上
* · color:#333 → 把 hljs 主题给 <pre> 的基色(.hljs{color:#000})
* 覆盖成固定的深灰。深色配色下就是深灰压深底,看不清
*
* 修法:选择器强度提到与 `.content pre code` 同级的 (0,4,2) ——
* 靠**出现更晚**取胜,不靠 !important(!important 会让用户/插件再也没法覆盖)。
* 显式写 border-radius:0 而不是依赖"没声明就继承",因为 border-radius
* 不继承、初始值是 0,但原主题那条规则会直接命中 code。 */
.k-main .details .article .content .cb pre code,
.comment-body .cb pre code,
.cb pre code {
display: block;
margin: 0;
padding: 0;
border: 0;
border-radius: 0;
background: transparent;
font-size: inherit;
line-height: inherit;
color: inherit;
font-family: inherit;
/* 原主题给了 word-break:inherit,pre 上是 normal,这里跟住 */
word-break: inherit;
overflow-wrap: inherit;
}
/* 深色配色必须让卡片自带深色底与深色顶条。
+66 -2
View File
@@ -34,6 +34,60 @@
if (typeof hljs.highlightAll === 'function') {
hljs.highlightAll();
}
fixPlaintextClass();
return true;
}
/* ── 修掉 hljs 留下的 language-undefined ──────────────────
* 🔴 实测(真实浏览器):hljs 11.12.0 的 highlightElement() 处理
* `<code class="hljs language-plaintext">` 之后,会把 class 改写成
* **`hljs language-undefined`**。
*
* 原因是 plaintext 的定义只有 {name, aliases, disableAutodetect,
* rawDefinition},**没有 `language` 字段**,而 highlightElement
* 内部用 `result.language` 拼了 className —— 于是拼出字面量 "undefined"。
*
* 后果不是报错(页面一切正常、颜色也对,因为 plaintext 本来就不上色),
* 但 class 被污染成 `language-undefined`:
* · 任何人按 `[class*="language-"]` 统计语言都会多数出一个"undefined"
* · 自动化判据会被它误导(本项目的探针就吃到这个假数据)
* 所以这里显式改回 language-plaintext。
*
* 判据必须直接查 class 字面量,不能只看"有没有颜色"——
* plaintext 本来就没颜色,看颜色永远看不出这个问题。 */
function fixPlaintextClass () {
var codes = document.querySelectorAll('code.language-undefined');
for (var i = 0; i < codes.length; i++) {
var cb = codes[i].closest ? codes[i].closest('.cb') : null;
// 只修我们自己卡片里、且 data-lang 确实是 plaintext 的
if (!cb) { continue; }
if (cb.getAttribute('data-lang') !== 'plaintext') { continue; }
codes[i].className = 'hljs language-plaintext';
}
}
/* 判断 highlightAuto 的返回是不是"真的识别出来了"
*
* 🔴 绝不能用 `if (r && r.language)`:
* 实测 hljs 11.12.0 对纯文本/中文内容的 highlightAuto 返回
* `{language: 'undefined', relevance: 0}` —— 注意是**字符串**
* 'undefined',不是 undefined 值。用真值判断会把它当成功,
* 于是 class 写成 `language-undefined`、标签写成 labelOf('undefined')
* 再被 toUpperCase() 变成「UNDEFINED」,直接显示在页面上。
*
* 另有一个真实的正确性隐患:英文短句会被猜成 sql(relevance 只有 1)。
* relevance 太低说明"没有可信匹配",这种也应该当作没认出来。 */
function autoOk (r) {
if (!r || !r.value) { return false; }
var lang = r.language;
if (!lang || lang === 'undefined' || lang === 'null') { return false; }
if (typeof lang !== 'string') { return false; }
// relevance 为 0 = 完全没有命中任何语法特征
if (!r.relevance || r.relevance <= 0) { return false; }
// 语言必须在 hljs 里真的注册过,否则写进 class 就是白板
if (typeof hljs.getLanguage === 'function' && !hljs.getLanguage(lang)) {
return false;
}
return true;
}
@@ -69,7 +123,7 @@
if (on) {
var r = hljs.highlightAuto(src);
if (r && r.language) {
if (autoOk(r)) {
code.innerHTML = r.value;
code.className = 'hljs language-' + r.language;
if (tag) {
@@ -111,9 +165,19 @@
}
})(boxes[i]);
}
// runAuto 里也会重写 code.className,所以这里必须再修一次。
// (只放在 paint() 里不够:runAuto 是 DOMContentLoaded 之后又跑一遍的,
// 会把 paint() 刚修好的 class 覆盖掉。)
fixPlaintextClass();
}
/* 语言名 → 显示名 */
/* 语言名 → 显示名
*
* 🔴 表里只能放**确实打包进 highlight.min.js 的语言**。
* 放进来但没打包 → hljs 认不出 → 代码块静默白板(只 console.warn,
* 不抛错)。所以 dos / dockerfile / nginx / powershell 已从映射链路
* 与语言白名单里移除,这里保留文案只是为了 highlightAuto 万一返回
* 它们时标签不是全大写,不影响上色。 */
function labelOf (lang) {
var M = {
bash: 'Bash', c: 'C', cpp: 'C++', csharp: 'C#', css: 'CSS',