3.2.44: 后台配色实时预览 + 全站代码块语言标注统一 #13

需求 A —— 主题设置里选「代码配色」时能当场看到效果:
- 新增 assets/css/highlight-admin.css:只负责预览区布局,不改代码块外观
- 新增 assets/js/highlight-admin.js:监听下拉框,删旧 link 再插新 link,
  回调里重新上色
- inc/theme-highlight.php:新增 yang_hl_admin_assets()/yang_hl_admin_preview_html()
- inc/theme-options.php:在配色下拉框后插入 type=info 的预览面板

几个关键取舍(都有实测依据):
- 切换配色必须真正删掉旧 <link>。20 套配色的规则是裸选择器
  (.hljs-keyword 之类),同场会互相压制,切换看起来像「没反应」。
- 预览复用前台同一份 CSS,另写一套必然与前台不一致。
- 不用 highlightElement(会在已上色元素上再套一层,颜色变脏),
  改为从 textContent 取原文重新 highlight。
- 实测 hljs 的 HTML 转义无损(392 字符逐字符可还原),所以不需要额外的
  data-raw 暂存;那是两份状态,不同步就是 bug 源头。已删该冗余。

需求 B —— 全站代码块语言标注统一(数据侧,不在本次 diff 里):
- 284 块 / 71 篇全部统一为 <pre lang="xxx">,清掉 Enlighter 私有标记与
  code 上的 language-* 类
- 关键发现:站内没有「完全无标注」的块(0 个),但有 87 块标注无效
  (text 47 / generic 28 / raw 8 / jsp 2 / latex 2)—— 问题不是没标,是标是假的
- 87 块逐块判定:bash 174、plaintext 45、yaml 13、ini 9、xml 8、python 8、
  javascript 7、php 6、json 5、java 5、ruby 2、sql 2
- 判定表优先级必须高于「沿用旧标注」:产品把 generic/raw/text 兜底成
  plaintext,而 plaintext 在白名单里,旧逻辑会走 keep 把判定表整批丢弃
  (第一版实测 87 条只有 4 条生效,且结果看起来是成功的)
- 同一篇文章里有 11 对块原文完全相同,str_replace 会全局替换导致整片标错;
  改为按文内序号 + 内容签名定位

验证:
- 后台预览 jsdom 测试 22/0,反向注入 6 组全部真红
- 语言/属性单测 44/0 与 24/0,语言反向 10/0
- 独立审计脚本(另一套扫描逻辑)全绿;反向 3/3 真红
  (去掉 lang / 改成 cobol / 塞回 enlighter 各自都能抓出来)
- 真实页面 4 篇:data-guess 全为 0,正文里已无「猜出来的」代码块
This commit is contained in:
2026-10-26 11:37:42 +08:00
parent 8f8cffd69e
commit 03b0bc6cc1
5 changed files with 443 additions and 2 deletions
+135 -1
View File
@@ -685,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.43';
$ver = $theme && $theme->get('Version') ? $theme->get('Version') : '3.2.44';
$uri = get_template_directory_uri();
$key = yang_hl_theme_key();
@@ -738,3 +738,137 @@ function yang_hl_enqueue()
}
}
add_action('wp_enqueue_scripts', 'yang_hl_enqueue', 20);
/* ══════════════════════════════════════════════════════════════════
* 后台设置页:代码配色实时预览
*
* 为什么要做:后台「代码配色」是个下拉框,20 个名字(GitHub / Monokai /
* Night Owl…)光看名字根本不知道长什么样。原来的流程是
* 选一个 → 保存 → 去前台刷新 → 不满意 → 回来再选
* 来回几次就放弃了,最后一直用默认那套。
*
* 做法:在选项页里放一个**真代码块卡片**,配色 CSS 用 JS 动态换。
*
* 🔴 关键约束:预览必须加载**前台同一份 CSS**(assets/hljs/styles/*.min.css
* 和 assets/css/highlight.css),绝不能在这里另写一套"预览专用样式"。
* 另写一套的后果是:预览好看、前台不一样(或反过来),比没有预览更糟。
*
* 🔴 20 套配色不能一次性全加载到后台,否则规则互相压制、看不出切换效果
* (前台也是只加载选中的一套,见 yang_hl_enqueue 的注释)。
* 这里用「删掉旧的 <link>、插入新的」的方式模拟真实加载顺序。
* ══════════════════════════════════════════════════════════════════ */
/**
* 后台选项页加载预览所需资源
*
* @param string $hook 当前后台页面
*/
function yang_hl_admin_assets($hook)
{
if (strpos((string) $hook, 'theme') === false && strpos((string) $hook, 'option') === false) {
return;
}
$dir = get_template_directory();
$uri = get_template_directory_uri();
$theme = wp_get_theme();
$ver = $theme && $theme->get('Version') ? $theme->get('Version') : '3.2.44';
// 卡片骨架(与前台同一个文件)
wp_enqueue_style('yang-hl-admin-card',
$uri . '/assets/css/highlight.css', array(), $ver);
// 预览区自身的布局样式(只负责把卡片放在框里,不碰代码块外观)
wp_enqueue_style('yang-hl-admin-preview',
$uri . '/assets/css/highlight-admin.css', array('yang-hl-admin-card'), $ver);
wp_enqueue_script('yang-hl-admin-preview',
$uri . '/assets/js/highlight-admin.js', array(), $ver, true);
// 把 20 套配色的 key 和基准 URL 传给 JS
wp_localize_script('yang-hl-admin-preview', 'YANG_HL_ADMIN', array(
'styleBase' => $uri . '/assets/hljs/styles/',
'themes' => array_keys(yang_hl_themes()),
'ver' => $ver,
));
}
add_action('admin_enqueue_scripts', 'yang_hl_admin_assets');
/**
* 生成后台预览用的 HTML(选项页的 info 块直接输出)
*
* 预览内容刻意覆盖多种 token:关键字、字符串、注释、数字、函数名、变量。
* 只给一行 `print("hi")` 看不出配色差异 —— 大部分主题的区别在注释色、
* 字符串色、数字色上。
*
* @return string
*/
function yang_hl_admin_preview_html()
{
$key = yang_hl_theme_key();
$themes = yang_hl_themes();
$name = isset($themes[$key]) ? $themes[$key][0] : $key;
$kind = isset($themes[$key]) ? $themes[$key][1] : 'light';
// 样例代码:一段有代表性的 PHP,含注释/关键字/字符串/数字/函数/变量
$sample = "<?php\n"
. "// 从数据库取出最近 7 天的访问量\n"
. "\$days = 7;\n"
. "\$rows = \$wpdb->get_results(\n"
. " \"SELECT DATE(post_date) AS d, COUNT(*) AS n\n"
. " FROM {\$wpdb->posts}\n"
. " WHERE post_date > NOW() - INTERVAL 7 DAY\n"
. " GROUP BY d ORDER BY d\"\n"
. ");\n"
. "\n"
. "foreach (\$rows as \$r) {\n"
. " printf(\"%s 访问 %d 次\\n\", \$r->d, \$r->n);\n"
. "}\n";
/* 🔴 行数算法必须与 yang_hl_render_block 完全一致:
* substr_count("\n") 对 "a\nb\nc\n" 得到 3,但**实际只画 3 行**
* (末尾换行不产生新行盒)。所以不能无条件 +1。
*
* 本函数第一版写成 `substr_count($sample, "\n") + 1` ⇒ 13 行的样例
* 算出 14 个行号,比代码多一行 —— 由 _admin_preview_test.js 抓出。
* 这正是本项目红线的老坑:"行号条数 == 换行符数" 在末尾有换行时恒假。 */
$n = substr_count($sample, "\n");
if (substr($sample, -1) !== "\n") {
$n++;
}
$lines = max(1, $n);
$gutter = '';
for ($i = 1; $i <= $lines; $i++) {
$gutter .= '<span>' . $i . '</span>';
}
// 服务端就把 token 上好色,再交给 hljs 覆盖一次 ——
// 保证 JS 还没跑的时候也有颜色,不会先看到一坨黑字再变色
$escaped = esc_html($sample);
$out = '<div class="yang-hl-preview" data-theme-key="' . esc_attr($key) . '">';
$out .= '<div class="yang-hl-preview-head">';
$out .= '<span class="yang-hl-preview-title">实时预览</span>';
$out .= '<span class="yang-hl-preview-name" data-theme-name>' . esc_html($name) . '</span>';
$out .= '<span class="yang-hl-preview-hint">切换上面的配色,这里立刻跟着变(不用保存)</span>';
$out .= '</div>';
$out .= '<figure class="cb hl-' . esc_attr($key) . '"'
. ' data-lang="php" data-theme="' . esc_attr($key) . '"'
. ' data-kind="' . esc_attr($kind) . '" data-guess="0">';
$out .= '<figcaption class="cb-bar">';
$out .= '<span class="cb-lang">PHP</span>';
$out .= '<span class="cb-actions"><button type="button" class="cb-copy" data-copy disabled>复制</button></span>';
$out .= '</figcaption>';
$out .= '<div class="cb-body">';
$out .= '<div class="cb-ln" aria-hidden="true">' . $gutter . '</div>';
$out .= '<pre class="hljs"><code class="hljs language-php" data-sample="php">' . $escaped . '</code></pre>';
$out .= '</div></figure>';
$out .= '<p class="yang-hl-preview-note">'
. '预览用的是一段 PHP 样例,包含注释、关键字、字符串、数字、变量 —— '
. '不同配色的差别主要体现在这几种颜色上。'
. '</p>';
$out .= '</div>';
return $out;
}
+19
View File
@@ -1196,6 +1196,25 @@ function kratos_options()
'requires' => 'a_highlight_enable',
);
/* 配色预览:紧跟在配色下拉框后面
*
* 为什么需要:20 套配色只有名字(GitHub / Monokai / Night Owl…),
* 光看名字不知道长什么样。原来的流程是
* 选一个 → 保存 → 去前台刷新 → 不满意 → 回来再选,
* 来回几次就放弃、最后一直用默认那套。
*
* 这里放一张**真代码块卡片**,切换下拉框立刻换色,不用保存。
* 用的是前台同一份 CSS(配色 min.css + highlight.css),不另写一套 ——
* 另写一套会导致预览和前台不一致,比没有预览更误导人。 */
if (function_exists('yang_hl_admin_preview_html')) {
$options[] = array(
'name' => '',
'desc' => yang_hl_admin_preview_html(),
'type' => 'info',
'class' => 'yang-hl-preview-wrap',
);
}
$options[] = array(
'name' => __('显示行号', 'kratos'),
'desc' => __('<b>默认关闭。</b>'