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:
+198
-48
@@ -90,11 +90,6 @@ function yang_hl_lang_map()
|
||||
'shell' => 'bash',
|
||||
'console' => 'bash',
|
||||
'terminal' => 'bash',
|
||||
'ps' => 'powershell',
|
||||
'ps1' => 'powershell',
|
||||
'bat' => 'dos',
|
||||
'batch' => 'dos',
|
||||
'cmd' => 'dos',
|
||||
'c++' => 'cpp',
|
||||
'cs' => 'csharp',
|
||||
'c#' => 'csharp',
|
||||
@@ -102,12 +97,22 @@ function yang_hl_lang_map()
|
||||
'rs' => 'rust',
|
||||
'rb' => 'ruby',
|
||||
'kt' => 'kotlin',
|
||||
'objectivec' => 'objectivec',
|
||||
'objc' => 'objectivec',
|
||||
'dockerfile' => 'dockerfile',
|
||||
'docker' => 'dockerfile',
|
||||
'nginx' => 'nginx',
|
||||
'sql' => 'sql',
|
||||
|
||||
/* 🔴 下面这几组**故意不映射**,因为目标语言没有打包进
|
||||
* assets/hljs/highlight.min.js:
|
||||
* powershell / dos / dockerfile / nginx —— 实测 listLanguages()
|
||||
* 的 36 项里一个都没有。
|
||||
*
|
||||
* 之前它们被映射过去,结果 hljs 找不到语言后**静默退回不高亮**
|
||||
* (只在控制台 warn 一句,不抛错),代码块变成纯白板 ——
|
||||
* 页面不报错、脚本无异常、CSS 全对,几乎不可能被发现。
|
||||
*
|
||||
* 站内当前这几种写法使用次数都是 0,所以一直没暴露。
|
||||
* 真要支持它们,正确做法是往 highlight.min.js 里补打包对应语言,
|
||||
* 而不是在这里"映射成名字"。现在不映射 = 退回自动识别
|
||||
* (标签打虚线,明确标示"这是猜的"),比"假装标了、实际没色"诚实。 */
|
||||
);
|
||||
}
|
||||
|
||||
@@ -139,19 +144,8 @@ function yang_hl_norm_lang($raw)
|
||||
return $map[$raw];
|
||||
}
|
||||
|
||||
// hljs 官方的语言名直接用
|
||||
static $known = null;
|
||||
if ($known === null) {
|
||||
// 与 assets/js/hljs/highlight.min.js 打包的语言一致(36 种)
|
||||
$known = array_flip(array(
|
||||
'bash', 'c', 'cpp', 'csharp', 'css', 'diff', 'go', 'graphql',
|
||||
'ini', 'java', 'javascript', 'json', 'kotlin', 'less', 'lua',
|
||||
'makefile', 'markdown', 'objectivec', 'perl', 'php',
|
||||
'plaintext', 'powershell', 'python', 'r', 'ruby', 'rust',
|
||||
'scss', 'shell', 'sql', 'swift', 'typescript', 'xml', 'yaml',
|
||||
'dos', 'dockerfile', 'nginx',
|
||||
));
|
||||
}
|
||||
// hljs 官方的语言名直接用(表的正文与理由见 yang_hl_known_map())
|
||||
$known = yang_hl_known_map();
|
||||
return isset($known[$raw]) ? $raw : '';
|
||||
}
|
||||
|
||||
@@ -210,18 +204,67 @@ function yang_hl_label($lang)
|
||||
/**
|
||||
* 允许高亮的语言白名单(与打包进 hljs 的一致)
|
||||
*
|
||||
* 🔴 这里**不能再手写一份清单**。
|
||||
*
|
||||
* 3.2.43 之前这个函数手写了 36 项,与 yang_hl_norm_lang() 里那张
|
||||
* `$known` 表是**两份互相独立的真相**:`$known` 已经按实测校正过
|
||||
* (剔掉没打包的 powershell/dos/dockerfile/nginx,补上 php-template/
|
||||
* python-repl/vbnet/wasm),而这里还留着旧内容。
|
||||
*
|
||||
* 两份清单不一致的后果是静默的:hljs 遇到不认识的语言不抛错,
|
||||
* 只 console.warn 一句就退回不高亮 —— 代码块整块变白板。
|
||||
*
|
||||
* 改为从 `$known` 单一来源派生,以后只维护一张表。
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function yang_hl_languages()
|
||||
{
|
||||
return array_keys(array_flip(array(
|
||||
'bash', 'c', 'cpp', 'csharp', 'css', 'diff', 'go', 'graphql',
|
||||
'ini', 'java', 'javascript', 'json', 'kotlin', 'less', 'lua',
|
||||
'makefile', 'markdown', 'objectivec', 'perl', 'php',
|
||||
'plaintext', 'powershell', 'python', 'r', 'ruby', 'rust',
|
||||
'scss', 'sql', 'swift', 'typescript', 'xml', 'yaml',
|
||||
'dos', 'dockerfile', 'nginx',
|
||||
)));
|
||||
return array_keys(yang_hl_known_map());
|
||||
}
|
||||
|
||||
/**
|
||||
* `$known` 白名单的表本体(唯一真相源)
|
||||
*
|
||||
* 🔴 这张表必须与 assets/hljs/highlight.min.js **实际打包**的语言逐字一致。
|
||||
*
|
||||
* 之前它写着 36 项,但其中 powershell / dos / dockerfile / nginx
|
||||
* **根本没有打包**(实测 hljs.listLanguages() 36 项里没有这四个),
|
||||
* 同时漏掉了实际打包进来的 php-template / python-repl / vbnet / wasm。
|
||||
*
|
||||
* 后果是静默的:hljs 遇到不认识的语言**不抛错**,只 console.warn 一句
|
||||
* "Could not find the language 'xxx'" 然后退回不高亮 ——
|
||||
* 代码块整块变成白板,没有红色报错、没有脚本异常、页面一切正常。
|
||||
* 站内当前这 4 种语言的使用次数都是 0,所以尚未触发;
|
||||
* 但只要以后写一篇 PowerShell 文章,就会出现"代码块没有颜色"
|
||||
* 且完全查不出原因的情况。
|
||||
*
|
||||
* 现按实测结果校正(下面 36 项 = node 里 hljs.listLanguages() 的原始输出):
|
||||
* bash c cpp csharp css diff go graphql ini java javascript json
|
||||
* kotlin less lua makefile markdown objectivec perl php php-template
|
||||
* plaintext python python-repl r ruby rust scss shell sql swift
|
||||
* typescript vbnet wasm xml yaml
|
||||
*
|
||||
* ⚠️ 未打包的语言(powershell/dos/dockerfile/nginx/latex/jsp 等)
|
||||
* 一律不放进这张表 —— 放进来才会造成白板。它们会返回空串,
|
||||
* 由前端 highlightAuto 兜底(标签打虚线,明确表示"这是猜的")。
|
||||
*
|
||||
* @return array 语言名 => 下标
|
||||
*/
|
||||
function yang_hl_known_map()
|
||||
{
|
||||
static $known = null;
|
||||
if ($known === null) {
|
||||
$known = array_flip(array(
|
||||
'bash', 'c', 'cpp', 'csharp', 'css', 'diff', 'go', 'graphql',
|
||||
'ini', 'java', 'javascript', 'json', 'kotlin', 'less', 'lua',
|
||||
'makefile', 'markdown', 'objectivec', 'perl', 'php',
|
||||
'php-template', 'plaintext', 'python', 'python-repl', 'r',
|
||||
'ruby', 'rust', 'scss', 'shell', 'sql', 'swift', 'typescript',
|
||||
'vbnet', 'wasm', 'xml', 'yaml',
|
||||
));
|
||||
}
|
||||
return $known;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -260,14 +303,112 @@ function yang_hl_pick_lang_from_class($class)
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 `<pre>` 自身的属性里取语言
|
||||
*
|
||||
* 🔴 为什么必须单独做这一件事:站内 284 个代码块,语言标记的载体有四种,
|
||||
* 之前的 `yang_hl_pick_lang_from_class()` **只认第一种**:
|
||||
*
|
||||
* ① `<pre><code class="language-x">` 204 个 ← 认到了
|
||||
* ② `<pre lang="x">` 22 个 ← 漏
|
||||
* ③ `<pre class="EnlighterJSRAW"
|
||||
* data-enlighter-language="x">` 57 个 ← 漏
|
||||
* ④ 真无标记 1 个
|
||||
*
|
||||
* ②③ 合计 79 个(占 27.8%,涉及 26 篇文章)判不出语言 ⇒ 标签显示「—」,
|
||||
* 而实际语言一直明明白白写在属性里。这不是"覆盖不到",是**读错了载体**。
|
||||
*
|
||||
* ⚠️ 上一轮我把这 79 个记成「裸 pre,无 code 子元素」,只统计了 `<code>`
|
||||
* 子元素、没查 `<pre>` 自己的属性,所以得出"需要自动识别"的错误结论。
|
||||
* 教训:判据要把所有可能的承载位置都列出来,不能只认一种形态。
|
||||
*
|
||||
* Enlighter 的取值语义(官方文档):
|
||||
* generic / raw → 不高亮,等价于纯文本(28 + 8 = 36 个块)
|
||||
* 其余值就是语言名(bash 17、latex 2、json 1、php 1)
|
||||
* 所以 generic/raw 映射到 plaintext 是**语义正确**,不是猜。
|
||||
*
|
||||
* @param string $attrs `<pre ...>` 里的属性原文(不含 <pre 和 >)
|
||||
* @return string 规范化后的语言名;取不到返回空串
|
||||
*/
|
||||
function yang_hl_pick_lang_from_attrs($attrs)
|
||||
{
|
||||
$attrs = (string) $attrs;
|
||||
if (trim($attrs) === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
// ② Enlighter 数据属性
|
||||
if (preg_match('/data-enlighter-language\s*=\s*["\']([^"\']*)["\']/i', $attrs, $m)) {
|
||||
$raw = strtolower(trim($m[1]));
|
||||
// Enlighter 这两个值表示"不要高亮"
|
||||
if ($raw === 'generic' || $raw === 'raw') {
|
||||
return 'plaintext';
|
||||
}
|
||||
$lang = yang_hl_norm_lang($raw);
|
||||
if ($lang !== '') {
|
||||
return $lang;
|
||||
}
|
||||
}
|
||||
|
||||
// ② lang 属性
|
||||
if (preg_match('/(?:^|\s)lang\s*=\s*["\']([^"\']*)["\']/i', $attrs, $m)) {
|
||||
$lang = yang_hl_norm_lang($m[1]);
|
||||
if ($lang !== '') {
|
||||
return $lang;
|
||||
}
|
||||
}
|
||||
/* 🔴 容错:属性值的闭合引号缺失。
|
||||
*
|
||||
* 站内实测存在这种脏数据(十几年前手工编辑留下的):
|
||||
* <pre lang="javascript> ← 少了结尾的引号
|
||||
* 上面的正则要求两个引号成对,遇到它就整个匹配不上 ⇒ 语言丢失。
|
||||
*
|
||||
* 修法只放宽"结尾引号可有可无",起始引号仍必须存在,
|
||||
* 且值限定在「非空白、非引号、非 >」范围内 ——
|
||||
* 这样不会把 `lang="a" class="b"` 里的后半段吞进来。
|
||||
*
|
||||
* ⚠️ 绝不能改成 `lang\s*=\s*(\S+)` 这种"取到空白为止"的写法:
|
||||
* 它会把行内 style 之类的内容也当语言名,属于用更宽的错盖住窄的错。 */
|
||||
if (preg_match('/(?:^|\s)lang\s*=\s*["\']([^\s"\'>]+)/i', $attrs, $m)) {
|
||||
$lang = yang_hl_norm_lang($m[1]);
|
||||
if ($lang !== '') {
|
||||
return $lang;
|
||||
}
|
||||
}
|
||||
|
||||
// ③ class(可能是 language-x,也可能是 EnlighterJSRAW / editor-md-pre 这种纯样式类)
|
||||
if (preg_match('/\bclass\s*=\s*["\']([^"\']*)["\']/i', $attrs, $m)) {
|
||||
$cls = preg_replace('/\b(editor-md-pre|EnlighterJSRAW)\b/i', '', $m[1]);
|
||||
$lang = yang_hl_pick_lang_from_class($cls);
|
||||
if ($lang !== '') {
|
||||
return $lang;
|
||||
}
|
||||
// 退一步:整串当语言名试(`<pre class="php">`)
|
||||
$probe = trim(preg_replace('/\s+/', '', $cls));
|
||||
if ($probe !== '') {
|
||||
$lang = yang_hl_norm_lang($probe);
|
||||
if ($lang !== '') {
|
||||
return $lang;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
/**
|
||||
* 把一个 <pre> 片段规范化成"代码块卡片"的结构
|
||||
*
|
||||
* 输入可能是四种形态中的任意一种(站内都实测存在):
|
||||
* A. <pre><code class="language-python">…</code></pre> ← 标准,204 个
|
||||
* B. <pre class="language-python"><code>…</code></pre> ← 类在 pre 上
|
||||
* C. <pre><code>…</code></pre> ← 无标记,需自动识别
|
||||
* D. <pre>…</pre> ← 裸 pre,80 个,必须处理
|
||||
* 输入可能是四种形态中的任意一种(站内都实测存在,数字是 284 个块的真实分布):
|
||||
* A. <pre><code class="language-python">…</code></pre> 204 个
|
||||
* B. <pre lang="python"><code>…</code></pre> 22 个
|
||||
* C. <pre class="EnlighterJSRAW"
|
||||
* data-enlighter-language="python">…</pre> 57 个
|
||||
* D. <pre>…</pre> 1 个
|
||||
*
|
||||
* 🔴 B/C 的语言都在 **pre 自己的属性**上,不在 code 子元素的 class 上。
|
||||
* 早先只读 code 的 class,导致这 79 个块(27.8%,26 篇文章)的语言标签
|
||||
* 全部退化成「—」,同时被前端 highlightAuto 乱猜(准确率实测仅 22%)。
|
||||
*
|
||||
* 输出统一是:
|
||||
* <figure class="cb" data-lang="…">
|
||||
@@ -291,9 +432,10 @@ function yang_hl_pick_lang_from_class($class)
|
||||
*
|
||||
* @param array $m preg_match_all 的匹配组
|
||||
* @param string $inner_pre pre 内部原文(已含未处理的 HTML)
|
||||
* @param string $attr_pre pre 自身的属性原文(含 lang= / class= / data-*)
|
||||
* @return string
|
||||
*/
|
||||
function yang_hl_render_block($inner_pre, $attr_class = '')
|
||||
function yang_hl_render_block($inner_pre, $attr_pre = '')
|
||||
{
|
||||
$code = $inner_pre;
|
||||
$lang = '';
|
||||
@@ -306,10 +448,22 @@ function yang_hl_render_block($inner_pre, $attr_class = '')
|
||||
|| preg_match("/class\s*=\s*'([^']*)'/i", $attrs, $cm)) {
|
||||
$lang = yang_hl_pick_lang_from_class($cm[1]);
|
||||
}
|
||||
/* code 上也可能挂 lang= 属性。
|
||||
*
|
||||
* 🔴 锚定必须是 `(?:^|\s)lang`,**不能**写 `\blang`。
|
||||
* `-` 是非单词字符,`\b` 在 `data-lang=` 的 `l` 前面同样成立
|
||||
* ⇒ 会把 data-lang 的值当语言名读走。
|
||||
* 这是同一类错误在本文件的**第二处**:上面
|
||||
* yang_hl_pick_lang_from_attrs() 里已经修过一次,这里漏改。
|
||||
* 教训:修正则时要 grep 同款写法,不能只改报错的那一处。 */
|
||||
if ($lang === ''
|
||||
&& preg_match('/(?:^|\s)lang\s*=\s*["\']([^"\']*)["\']/i', $attrs, $lm)) {
|
||||
$lang = yang_hl_norm_lang($lm[1]);
|
||||
}
|
||||
}
|
||||
// pre 自己的 class 也可能是语言来源(形态 B)
|
||||
if ($lang === '' && $attr_class !== '') {
|
||||
$lang = yang_hl_pick_lang_from_class($attr_class);
|
||||
// pre 自己的属性也是语言来源(形态 B/C —— 站内共 79 个块靠这里)
|
||||
if ($lang === '' && $attr_pre !== '') {
|
||||
$lang = yang_hl_pick_lang_from_attrs($attr_pre);
|
||||
}
|
||||
|
||||
// 代码内容保持原样:它已经是 HTML 转义过的(< &),
|
||||
@@ -394,15 +548,11 @@ function yang_hl_filter_content($content)
|
||||
$out = preg_replace_callback(
|
||||
'#<pre\b([^>]*)>(.*?)</pre>#is',
|
||||
function ($m) {
|
||||
// 🔴 必须把**完整属性串**传下去,不能只传 class。
|
||||
// 站内 79 个块的语言写在 lang= / data-enlighter-language= 上,
|
||||
// 只传 class 会把这 79 个块的语言全部丢掉(实测:标签退化成「—」)。
|
||||
$attrs = isset($m[1]) ? $m[1] : '';
|
||||
$cls = '';
|
||||
if (preg_match('/class\s*=\s*"([^"]*)"/i', $attrs, $cm)
|
||||
|| preg_match("/class\s*=\s*'([^']*)'/i", $attrs, $cm)) {
|
||||
$cls = $cm[1];
|
||||
}
|
||||
// wp-editormd 会产出 class="editor-md-pre",那是纯样式类,不是语言
|
||||
$cls = preg_replace('/\beditor-md-pre\b/i', '', $cls);
|
||||
return yang_hl_render_block($m[2], $cls);
|
||||
return yang_hl_render_block($m[2], $attrs);
|
||||
},
|
||||
$content
|
||||
);
|
||||
@@ -535,7 +685,7 @@ function yang_hl_enqueue()
|
||||
* (本项目历史上被 OPcache/缓存这类"文件名对、内容旧"坑过多次)。
|
||||
* 读主题头用 wp_get_theme()->get('Version'),WordPress 自带缓存,无额外开销。 */
|
||||
$theme = wp_get_theme();
|
||||
$ver = $theme && $theme->get('Version') ? $theme->get('Version') : '3.2.42';
|
||||
$ver = $theme && $theme->get('Version') ? $theme->get('Version') : '3.2.43';
|
||||
$uri = get_template_directory_uri();
|
||||
$key = yang_hl_theme_key();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user