From 9da5e9e2df365cb751295e1dbafa2b99b919620b Mon Sep 17 00:00:00 2001 From: laoyang Date: Mon, 26 Oct 2026 12:20:33 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=8E=E5=8F=B0=E4=BB=A3=E7=A0=81=E9=85=8D?= =?UTF-8?q?=E8=89=B2=E9=A2=84=E8=A7=88=E4=B8=8D=E4=B8=8A=E8=89=B2=20+=20do?= =?UTF-8?q?nate.png=20404=20=E4=B8=80=E5=B9=B6=E4=BF=AE=E5=A4=8D=20#14?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug 1:后台预览的示例代码没有高亮。三层原因叠在一起。 ① yang_hl_admin_assets() 从未 enqueue hljs 本体,而 yang_hl_enqueue() 开头 `if (is_admin() || is_feed()) return;` 挡掉后台 ⇒ 后台 window.hljs 恒 undefined ⇒ recolor() 静默 return。 卡片的边框/顶栏是 data-kind 驱动的纯 CSS,切深浅配色时照样会变, 看起来像"预览在工作,只是代码没颜色"。 ② init() 里 applyTheme(sel.value, true) 传 noRecolor=true ⇒ 首屏永不上色, 只有手动拨下拉框才有颜色(正是报障的"下面的示例代码并没有高亮")。 ③ recolor() 缺 hljs 时只是 return,控制台零输出 ⇒ 极难定位。 修法: - inc/theme-highlight.php:后台 enqueue hljs 本体,并作为 preview 脚本的 硬依赖(不靠 WP 的加载顺序碰运气);前台 yang_hl_enqueue() 仍挡后台。 - assets/js/highlight-admin.js:删掉 noRecolor 这个"能否跳过核心动作"的 参数(调用方迟早会用错);首屏与切换走同一条路径。 - 缺 hljs / 上色失败时 console.error 出声,并给 code 打 data-unhighlighted、给卡片打 data-hl-failed;CSS 画橙色虚线框 + 顶栏文字,让失败在画面上也看得出来,不再是一坨单色字。 - 配色样式加载主路径改为 setTimeout(once, 125),link.onload 只作加速。 onload 三情形不触发:样式表已在缓存、link 移除后重插、jsdom 无网络。 Bug 2:后台设置页报 donate.png 404。 库里 g_donate_alipay / g_donate_wechat 存的是旧主题目录名 `.../themes/kratos-master/assets/img/donate.png` 的绝对 URL。前台走 yang_option_image() 有 yang_image_url_is_stale() 兜底,后台 class-options-media-uploader.php 直接 echo $value,绕过检测。 修在渲染处:预览图与"浏览文件"href 走检测后的 $preview(带 function_exists 兜底),输入框仍显示库里的原值,不偷偷改用户数据。 版本 3.2.44 → 3.2.45。 --- assets/css/highlight-admin.css | 23 ++++ assets/js/highlight-admin.js | 113 +++++++++++++----- .../includes/class-options-media-uploader.php | 37 +++++- inc/theme-highlight.php | 29 ++++- style.css | 2 +- 5 files changed, 165 insertions(+), 39 deletions(-) diff --git a/assets/css/highlight-admin.css b/assets/css/highlight-admin.css index 1f60215..e77bf27 100644 --- a/assets/css/highlight-admin.css +++ b/assets/css/highlight-admin.css @@ -110,4 +110,27 @@ .yang-hl-preview-hint { flex-basis: 100%; } +} + +/* ── 上色失败的可视标记 ────────────────────────────────── + * + * 3.2.44 的教训:预览没上色时,画面上和"某套配色本来就很朴素" + * 完全无法区分。用户在后台看到单色代码,只能猜是配色问题还是坏了。 + * ⇒ 上色失败时打一个 data 标记,这里给一道醒目的虚线框, + * 让"坏了"和"配色就这样"在视觉上立刻分开。 + * + * 判据:这是**降级提示**,不是正常态。所以颜色用后台的警告橙 + * (#dba617),不引入新色值。 */ +.yang-hl-preview .cb[data-hl-failed="1"] { + outline: 2px dashed #dba617; + outline-offset: 2px; +} + +.yang-hl-preview .cb[data-hl-failed="1"] .cb-bar::after { + content: "预览未能上色,请查看浏览器控制台"; + margin-left: auto; + padding-left: 8px; + font-size: 11px; + color: #dba617; + white-space: nowrap; } \ No newline at end of file diff --git a/assets/js/highlight-admin.js b/assets/js/highlight-admin.js index ff7f83b..2286294 100644 --- a/assets/js/highlight-admin.js +++ b/assets/js/highlight-admin.js @@ -41,10 +41,17 @@ /** * 把预览的配色换成 key 对应的那套 * - * @param {string} key 配色 key,如 'monokai' - * @param {boolean} noRecolor 只换 CSS 不重新上色(初始化时用,避免多余计算) + * 🔴 这个函数**一定会**重新上色,没有"只换 CSS 不上色"的开关。 + * 3.2.44 曾有一个 noRecolor 参数供初始化时跳过上色,理由是 + * "省一次计算",结果首屏永远没颜色(见 init 里的注释)。 + * 一个"能否跳过核心动作"的参数,调用方迟早会用错, + * 而且用错之后**画面看着像正常工作**(配色边框都变了)。 + * ⇒ 干脆不要这个参数。recolor 从 textContent 重算是幂等的, + * 多跑一次的开销可以忽略(一段 284 字符的样例)。 + * + * @param {string} key 配色 key,如 'monokai' */ - function applyTheme(key, noRecolor) { + function applyTheme(key) { if (!key) { return; } @@ -89,30 +96,39 @@ } } - /* 等 CSS 真正生效后再重新上色。 - * 用 onload 而不是 setTimeout:网络快时不用白等,慢时也不会提前上色。 */ - link.onload = function () { - if (!noRecolor) { - recolor(); - } - }; + /* 🔴 recolor 不能只挂在 link.onload 上。 + * + * onload 不可靠的三种情况(都实测过或必然会发生): + * ① 样式表**已经在缓存里**、浏览器直接复用 ⇒ 某些浏览器不发 onload; + * ② 同一 URL 的 link 被移除后又插回来,事件可能不重放; + * ③ jsdom 等无网络环境**永远不触发** —— 于是自动化测试里 + * 代码永远是单色的,而"测试替我注入 hljs"又让问题看起来 + * 像别的原因(3.2.44 排查时绕了很久)。 + * + * ⇒ 主路径改成 **rAF/setTimeout 排队**,onload 只作为"提前一拍"的加速。 + * 两条路径都是幂等的(recolor 从 textContent 重算),重复调用无害。 */ + var fired = false; + function once() { + if (fired) { return; } + fired = true; + recolor(); + } + + link.onload = once; link.onerror = function () { - /* 加载失败就让预览保持原样,不要清空 —— 清空会让人以为主题坏了 */ + /* 加载失败就让预览保持原样,不要清空 —— 清空会让人以为主题坏了。 + * 但**仍然上色**:配色 CSS 没到,token 结构还是对的, + * 总比一坨单色字更像"预览在工作"。 */ if (window.console && console.warn) { console.warn('[yang-hl] 配色样式加载失败: ' + link.href); } + once(); }; document.head.appendChild(link); - /* 兜底:onload 在某些缓存命中场景下不一定触发。 - * 这里再排一次微延时重上色,保证最终一定是一致的。 - * 两次 recolor 是幂等的(内部先清空再写),不会叠加出问题。 */ - setTimeout(function () { - if (!noRecolor) { - recolor(); - } - }, 120); + /* 主路径:不管 onload 来不来,125ms 后一定上色(配色 CSS 通常早已就绪)。 */ + setTimeout(once, 125); } /** @@ -130,7 +146,19 @@ */ function recolor() { var code = document.querySelector('.yang-hl-preview code[data-sample]'); - if (!code || !window.hljs) { + if (!code) { + return; + } + /* 🔴 缺 hljs 时必须出声,绝不能静默 return。 + * 3.2.44 第一版后台漏加载 hljs,这里只是 `return`, + * 结果「预览不高亮」这个 bug 在控制台里**一个字都没有**, + * 靠肉眼盯着单色文字猜了很久。静默降级是最难查的一类缺陷。 */ + if (!window.hljs || typeof window.hljs.highlight !== 'function') { + if (window.console && console.error) { + console.error('[yang-hl] 预览无法上色:window.hljs 不存在。' + + '检查 yang_hl_admin_assets() 是否 enqueue 了 yang-hl-admin-lib。'); + } + markUnhighlighted(code); return; } var raw = code.textContent; @@ -139,10 +167,31 @@ try { res = window.hljs.highlight(raw, { language: lang, ignoreIllegals: true }); } catch (e) { + if (window.console && console.error) { + console.error('[yang-hl] 预览上色失败(' + lang + '):', e); + } + markUnhighlighted(code); return; } code.innerHTML = res.value; code.className = 'hljs language-' + lang; + code.removeAttribute('data-unhighlighted'); + } + + /** + * 标记「这块没能上色」,让问题在画面上也能看出来 + * + * 为什么不静默:预览的全部意义就是"让人看到长什么样"。 + * 上色失败却显示成一坨单色字,等于给用户一个错误信号 —— + * 他会以为"这个配色就是这样的"。加一道虚线边框,至少能分辨 + * "是配色长这样"还是"根本没跑起来"。 + */ + function markUnhighlighted(code) { + code.setAttribute('data-unhighlighted', '1'); + var card = code.closest ? code.closest('.cb') : null; + if (card) { + card.setAttribute('data-hl-failed', '1'); + } } /** 初始化 */ @@ -152,18 +201,22 @@ return; } - /* 初始配色:页面已经由 PHP 输出了选中那套的 CSS(前台样式不在这里加载, - * 所以这里要主动插一次;noRecolor=true 让首次不重复计算) */ - applyTheme(sel.value, true); + /* 🔴 首次**必须**上色,不能跳过。 + * + * 3.2.44 第一版这里是 `applyTheme(sel.value, true)`,注释写的理由是 + * "页面已经由 PHP 输出了选中那套的 CSS,首次不必重复计算"。 + * 那个理由只对**配色 CSS** 成立 —— PHP 确实没输出任何 token span。 + * + * 实际后果:用户打开后台,看到的是**一坨没有上色的单色代码**; + * 只有手动去拨一下下拉框,代码才第一次上色。 + * 这正好解释了"下面的示例代码并没有高亮"这个报障 —— + * 它不是完全不工作,是**首屏不工作**。 + * + * ⇒ 首次与切换走同一条路径,行为一致才不会有这种"半开"的 bug。 */ + applyTheme(sel.value); sel.addEventListener('change', function () { - applyTheme(sel.value, false); - }); - - /* 有些场景下框架会程序化改 select.value(比如"恢复默认"), - * change 事件不会触发 ⇒ 监听 click 兜底 */ - sel.addEventListener('click', function () { - /* 点击展开下拉不算切换,交给 change 处理 */ + applyTheme(sel.value); }); } diff --git a/inc/options-framework/includes/class-options-media-uploader.php b/inc/options-framework/includes/class-options-media-uploader.php index 6733622..19563bd 100644 --- a/inc/options-framework/includes/class-options-media-uploader.php +++ b/inc/options-framework/includes/class-options-media-uploader.php @@ -78,11 +78,38 @@ class Options_Framework_Media_Uploader { if ( $value != '' ) { $remove = '删除'; - $image = preg_match( '/(^.*\.jpg|jpeg|png|gif|svg|ico*)/i', $value ); - if ( $image ) { - $output .= '' . $remove; + + /* 🔴 预览图必须先过一遍「陈旧地址」检查,不能把库里的值直接塞进 src。 + * + * 3.2.45 实测:kratos 选项里 g_donate_alipay / g_donate_wechat + * 躺着 `https://站点/wp-content/themes/kratos-master/assets/img/donate.png` + * —— 很早以前保存表单时把当时的 std(绝对 URL)写进了库, + * 后来主题目录从 kratos-master 改名成 kratos_yang。 + * + * 前台没事:模板走 yang_option_image(),那个函数里有 + * yang_image_url_is_stale() 拦着,会落回默认图。 + * 但**后台这里不走那个函数**,直接 echo $value ⇒ + * 每打开一次选项页就往控制台报一次 donate.png 404。 + * + * 为什么在渲染处修而不是"直接把库改掉":库是所有站点的, + * 而这段代码要保证**任何人**的库里有陈旧地址时后台都不报 404。 + * 同时前台的行为一个字都不用变(它本来就有兜底)。 + * + * ⚠️ 只影响预览图与"浏览文件"链接的 href, + * **输入框里仍然显示库里的原值** —— 那是用户能看到、 + * 能自己改的地方。偷偷改显示值会让人以为设置被篡改了。 */ + $preview = $value; + if ( function_exists( 'yang_image_url_is_stale' ) && yang_image_url_is_stale( $preview ) ) { + $preview = function_exists( 'yang_image_default' ) + ? yang_image_default( $id ) + : ''; + } + + $image = preg_match( '/(^.*\.jpg|jpeg|png|gif|svg|ico*)/i', $preview ); + if ( $image && $preview !== '' ) { + $output .= '' . $remove; } else { - $parts = explode( "/", $value ); + $parts = explode( "/", $preview ); for( $i = 0; $i < sizeof( $parts ); ++$i ) { $title = $parts[$i]; } @@ -92,7 +119,7 @@ class Options_Framework_Media_Uploader { // Standard generic output if it's not an image. $title = __( '浏览文件', 'kratos' ); - $output .= '
'.$title.'
'; + $output .= '
'.$title.'
'; } } $output .= '' . "\n"; diff --git a/inc/theme-highlight.php b/inc/theme-highlight.php index 9f80301..d36709d 100644 --- a/inc/theme-highlight.php +++ b/inc/theme-highlight.php @@ -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.44'; + $ver = $theme && $theme->get('Version') ? $theme->get('Version') : '3.2.45'; $uri = get_template_directory_uri(); $key = yang_hl_theme_key(); @@ -771,7 +771,7 @@ function yang_hl_admin_assets($hook) $dir = get_template_directory(); $uri = get_template_directory_uri(); $theme = wp_get_theme(); - $ver = $theme && $theme->get('Version') ? $theme->get('Version') : '3.2.44'; + $ver = $theme && $theme->get('Version') ? $theme->get('Version') : '3.2.45'; // 卡片骨架(与前台同一个文件) wp_enqueue_style('yang-hl-admin-card', @@ -781,8 +781,31 @@ function yang_hl_admin_assets($hook) wp_enqueue_style('yang-hl-admin-preview', $uri . '/assets/css/highlight-admin.css', array('yang-hl-admin-card'), $ver); + /* 🔴 hljs 本体必须在后台也加载。 + * + * 3.2.44 第一版只 enqueue 了上面两个 CSS + 自己的 JS,**漏了 hljs**。 + * 而 yang_hl_enqueue() 开头就是 `if (is_admin() || is_feed()) return;` + * ⇒ 后台 window.hljs 恒 undefined ⇒ highlight-admin.js 的 + * `if (!code || !window.hljs) { return; }` 静默 return + * ⇒ 预览里一个 token span 都没有,只剩配色 CSS 给
 的底色。
+     *
+     *    这个 bug 极难从画面上判断:卡片的边框/顶栏颜色是 data-kind 驱动的
+     *    纯 CSS,不依赖 hljs,切换深/浅配色时**它们照样会变**
+     *    ⇒ 看起来像"预览在正常工作,只是代码没变色"。
+     *    控制台也一个字都没有(return 不是 throw)。
+     *
+     *    ⇒ 所以:① 这里把 hljs 作为 preview 脚本的硬依赖;
+     *             ② highlight-admin.js 里缺 hljs 时必须 console.error 出声。 */
+    wp_enqueue_script(
+        'yang-hl-admin-lib',
+        $uri . '/assets/hljs/highlight.min.js',
+        array(),
+        $ver,
+        true
+    );
+
     wp_enqueue_script('yang-hl-admin-preview',
-        $uri . '/assets/js/highlight-admin.js', array(), $ver, true);
+        $uri . '/assets/js/highlight-admin.js', array('yang-hl-admin-lib'), $ver, true);
 
     // 把 20 套配色的 key 和基准 URL 传给 JS
     wp_localize_script('yang-hl-admin-preview', 'YANG_HL_ADMIN', array(
diff --git a/style.css b/style.css
index f63ec19..516d40d 100644
--- a/style.css
+++ b/style.css
@@ -1,7 +1,7 @@
 /*
 Theme Name: Kratos_Yang
 Text Domain: kratos_Yang
-Version: 3.2.44
+Version: 3.2.45
 Requires PHP: 7.0
 Description: 这是一个Kratos主题的分支版本,用来增加一些定制化功能。
 Tags: 博客, 双栏, 响应式, 自定义背景, 自定义颜色, 自定义图标, 自定义菜单, 特色图像, 收录优化, 邮件中心, 主题选项, 小工具, 文章置顶, 无障碍友好