From d55667a39407c65c31dd9dd12adc8a78be7d53c4 Mon Sep 17 00:00:00 2001 From: laoyang Date: Tue, 27 Oct 2026 22:31:26 +0800 Subject: [PATCH] =?UTF-8?q?=E8=87=AA=E5=8A=A8=E7=9B=AE=E5=BD=95=E4=B8=A4?= =?UTF-8?q?=E4=BB=BD=E9=BB=98=E8=AE=A4=E7=8A=B6=E6=80=81=E5=88=86=E5=88=AB?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=EF=BC=8C=E8=80=81=E9=85=8D=E7=BD=AE=E5=B9=B3?= =?UTF-8?q?=E6=BB=91=E8=BF=81=E7=A7=BB=20#27?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 正文那份与浮层那份的「默认状态」拆成两项,各管各的: - theme-toc.php:toc_default 拆成 inline_default / float_default; normalize 加四分支迁移段(新键合法→用它;新键非法→回落该键默认; 新键缺失 + 老键存在→用老键;都不存在→各键默认); 读库键列表继续读老键 'default',不读就拿不到迁移值,老站点会被静默重置; 正文的 open 与浮层的 is_open 各读自己的键,互不引用。 - theme-options.php:两个独立下拉分别落在「正文里的目录」「浮动目录」 分组末尾;共用分组里那句「对两份同时生效」已过时,改写。 - 新增 yang_toc_admin_legacy_values() 挂 option_kratos(仅 is_admin): 后台读配置时把老键的值内存投影到缺失的新键上。不投影的话, 老站点会出现「页面按 hide 渲染、后台下拉框却显示 show」的错位。 只在读取时投影,库不动,降级回旧版仍有退路。 - style.css 版本号 3.2.59(本版未改前台 CSS,kratos.min.css 与 JS 均未动)。 验证:桩判据 61/0,反证 16 组 36/0,真实 WP 判据 30/0, 回归 _toc330_logic 101/0、_test_toc 53/0,打包自检 42/0。 --- inc/theme-options.php | 40 +++++++---- inc/theme-toc.php | 152 ++++++++++++++++++++++++++++++++++++++++-- style.css | 2 +- 3 files changed, 175 insertions(+), 19 deletions(-) diff --git a/inc/theme-options.php b/inc/theme-options.php index 9eb79a1..8a4cb3f 100644 --- a/inc/theme-options.php +++ b/inc/theme-options.php @@ -520,9 +520,23 @@ function kratos_options() ), ); + /* 3.2.59:从「共用」分组搬到这里。它只作用于正文那份, + 摆在「正文里的目录」分组下才不会让人以为它也管浮层。 */ + $options[] = array( + 'name' => __('正文目录的默认状态', 'kratos'), + 'desc' => __('默认显示:进来就能看到全部条目。默认隐藏:先只露出标题栏,读者自己点开。较长或标题很多的文章建议默认隐藏,否则目录会占掉一屏。
这一项只管正文里那一份;浮动目录的默认状态在下面「浮动目录」分组里单独设,两者互不影响。
注:这里只管「默认长什么样」,读者点开后仍然可以正常使用;正文里手写的 [TOC] 还能用 open="0" 单独指定,且只影响正文这份。', 'kratos'), + 'id' => 'toc_inline_default', + 'std' => 'show', + 'type' => 'select', + 'options' => array( + 'show' => __('默认显示', 'kratos'), + 'hide' => __('默认隐藏', 'kratos'), + ), + ); + $options[] = array( 'name' => __('浮动目录', 'kratos'), - 'desc' => __('下面两项只影响浮在页面边上那一份 —— 它不占正文位置,和正文里的目录互不相干。', 'kratos'), + 'desc' => __('下面几项只影响浮在页面边上那一份 —— 它不占正文位置,和正文里的目录互不相干。', 'kratos'), 'id' => 'toc_group_float', 'type' => 'info', ); @@ -548,24 +562,24 @@ function kratos_options() ); $options[] = array( - 'name' => __('两份目录共用的设置', 'kratos'), - 'desc' => __('下面几项对正文里和浮动的那一份同时生效。', 'kratos'), - 'id' => 'toc_group_common', - 'type' => 'info', - ); - - $options[] = array( - 'name' => __('默认状态', 'kratos'), - 'desc' => __('默认显示:进来就能看到全部条目。默认隐藏:先只露出标题栏(或侧边的标签),读者自己点开。较长或标题很多的文章建议默认隐藏,否则目录会占掉一屏。
注:这里只管「默认长什么样」,读者点开后仍然可以正常使用;正文里手写的 [TOC] 也可以用 open="0" 单独指定。', 'kratos'), - 'id' => 'toc_default', + 'name' => __('浮层目录的默认状态', 'kratos'), + 'desc' => __('默认显示:一进文章,面板就已经滑出展开。默认隐藏:只留边上那片竖向小标签,读者点一下才滑出 —— 长文建议用这个,不然面板会一直挡着正文。
这一项只管浮动那一份,和正文目录的默认状态各设各的:想让正文展开、浮层收起,就这里选「默认隐藏」、上面选「默认显示」。
注:展开态直接写在页面 HTML 的 class 上,浏览器禁用 JS 也能看到正确的默认状态。', 'kratos'), + 'id' => 'toc_float_default', 'std' => 'show', 'type' => 'select', 'options' => array( - 'show' => __('默认显示', 'kratos'), - 'hide' => __('默认隐藏', 'kratos'), + 'show' => __('默认显示(面板展开)', 'kratos'), + 'hide' => __('默认隐藏(只留侧边标签)', 'kratos'), ), ); + $options[] = array( + 'name' => __('两份目录共用的设置', 'kratos'), + 'desc' => __('下面几项对正文里和浮动的那一份同时生效 —— 它们的标题文字、收录哪些级别、要不要加序号,两份保持一致。
「默认状态」不在这里了:正文那份和浮层那份现在各自单独设(见上面两个分组),因为「正文展开、浮层收起」是很常见的搭配。', 'kratos'), + 'id' => 'toc_group_common', + 'type' => 'info', + ); + $options[] = array( 'name' => __('目录标题', 'kratos'), 'desc' => __('显示在目录顶部的那行字。留空则用「目录」。', 'kratos'), diff --git a/inc/theme-toc.php b/inc/theme-toc.php index fcfbefa..dbef528 100644 --- a/inc/theme-toc.php +++ b/inc/theme-toc.php @@ -115,7 +115,25 @@ function yang_toc_defaults() 'float_side' => 'left', // left | right(贴页面哪一边) 'mode' => 'manual', // manual | auto 'anchor' => 'first', // start | first | end(只对 inline 有效) - 'default' => 'show', // show | hide + + /* ── 默认开合:两份**各自独立**(3.2.59 起)─────────────── + * + * 原来只有一个 'default',正文那份和浮层那份共用同一个值, + * 改一个另一个必然跟着变。用户要的是「浮层默认隐藏、正文默认显示」 + * 这种组合,一个值表达不了。 + * + * ⚠️ 'default' 不再出现在返回数组里,但它**仍然是被接受的输入键** + * (见 yang_toc_normalize_settings() 里的迁移段)。老配置、 + * 老版本的子主题调用传进来都还能生效。 + * + * 为什么不在返回值里保留 'default' 当"两者的投影": + * 那会变成一个可能跟两个新键不一致的影子值, + * 将来谁读到它就成了一个说不清来源的 bug。宁可让老键 + * 在输出里彻底消失 —— 输入兼容、输出单一事实。 + */ + 'inline_default' => 'show', // show | hide(正文那份) + 'float_default' => 'show', // show | hide(浮层那份) + 'title' => __('目录', 'kratos'), 'levels' => array('1' => 1, '2' => 1, '3' => 1, '4' => 0), 'numbering' => 0, @@ -219,7 +237,46 @@ function yang_toc_normalize_settings($raw) $s['float_side'] = $pick('float_side', array('left', 'right')); $s['mode'] = $pick('mode', array('manual', 'auto')); $s['anchor'] = $pick('anchor', array('start', 'first', 'end')); - $s['default'] = $pick('default', array('show', 'hide')); + + /* ── 默认开合:两份各自独立 + 老配置迁移 ─────────────────────── + * + * 3.2.59 把原来的单个 'default' 拆成 inline_default / float_default。 + * 这里要同时满足四件事,少一件就会有人升级后设置被偷偷改掉: + * + * ① 新键存在且合法 → 用它 + * ② 新键存在但值非法 → 回落该键的默认(不用老键顶,老键更可能是脏的) + * ③ 新键**不存在**、老键存在 → 老键的值(这就是迁移:老站点只存了 + * toc_default,升级后两份都该保持原样) + * ④ 新旧都不存在 → 各键默认 + * + * 🔴 为什么不能简单写成 $pick('inline_default', ...): + * $pick 的语义是「键不存在 ⇒ 取 $d[$key]」,也就是 ④。 + * 那样一来 ③ 就落进 ④,老站点设了「默认隐藏」的,升级后 + * 两份**全部变回 show**,而界面上还看不出任何异常 —— + * 这正是「静默重置用户配置」,比崩掉更难发现。 + * + * 为什么顺序是「先看新键,再退回老键」而不是反过来: + * 用户一旦在 3.2.59 之后动过新下拉框,库里就有新键了, + * 此时老键成了历史残留 —— 必须让新键完全压过它,否则 + * 用户「改成默认显示」保存后会发现没生效(老键还在那儿赢)。 + */ + $legacy = null; + if (array_key_exists('default', $raw) && in_array($raw['default'], array('show', 'hide'), true)) { + $legacy = $raw['default']; + } + + $new_pair = array('inline_default', 'float_default'); + $s['inline_default'] = $d['inline_default']; + $s['float_default'] = $d['float_default']; + foreach ($new_pair as $k) { + if (array_key_exists($k, $raw)) { + $s[$k] = in_array($raw[$k], array('show', 'hide'), true) + ? $raw[$k] + : $d[$k]; + } elseif ($legacy !== null) { + $s[$k] = $legacy; // ③ 迁移:只存过老键的站点 + } + } $title = array_key_exists('title', $raw) ? $raw['title'] : $d['title']; $title = is_string($title) ? trim(wp_strip_all_tags($title)) : ''; @@ -249,7 +306,13 @@ function yang_toc_settings() $raw = array(); foreach (array('inline', 'inline_align', 'float', 'float_side', - 'mode', 'anchor', 'default', 'title', 'levels', 'numbering') as $k) { + 'mode', 'anchor', 'title', 'levels', 'numbering', + 'inline_default', 'float_default', + /* 'default' 是 3.2.58 及更早版本的旧键,**必须继续读**: + 不读就不知道老配置里存了什么,迁移段拿不到值, + 老站点升级后默认开合会被重置成 show。 + 读进来之后由 normalize 的迁移段消化掉,不会出现在输出里。 */ + 'default') as $k) { $raw[$k] = kratos_option('toc_' . $k, null); } // 库里没有的键用 null 占位,normalize 时按「未配置」回落默认值 @@ -263,6 +326,80 @@ function yang_toc_settings() return $cache; } +/** + * 后台表单读设置时,把老键 toc_default 的值"投影"到两个新键上 + * + * ── 为什么需要这一层(3.2.59,工单 #27,实测出来的问题)─────────── + * + * 迁移在 yang_toc_normalize_settings() 里已经做对了:老站点只存 + * toc_default=hide 时,页面**实际**渲染成 hide。 + * + * 但后台表单不是走 normalize 的 —— 框架(class-options-interface.php:87-96) + * 的规则是: + * $val = $value['std']; // 先取 std + * if (isset($settings[$id])) { $val = $settings[$id]; } // 库里有就覆盖 + * 老站点库里**没有** toc_inline_default 这个键 ⇒ `$val` 停在 std=show。 + * + * 结果就是(真机上实测到的): + * 页面实际渲染:hide + * 后台下拉框: show ← 错位 + * + * 用户看到下拉框写着「默认显示」,会以为自己的设置丢了;而且他若不 + * 主动去改,就永远不会触发保存,这个错位会一直存在。 + * 更糟的是"看起来是 show、实际是 hide",两边单独看都正常。 + * + * ── 怎么做:内存里投影,不写库 ───────────────────────────────── + * 用 `option_kratos` filter(WP 核心在 get_option 之后触发,**只影响读取**), + * 把老键的值补进缺的两个新键。库不动 —— 这样: + * · 用户没保存:每次读都投影一次,显示始终和页面一致 + * · 用户保存了:框架写库时带上新键,之后投影自动失效(他动过了) + * · 想回滚到旧版:库里从未被加过新键,旧版读 toc_default 一切照旧 + * + * 🔴 为什么不用 `pre_option_kratos`:那个 filter 在 get_option **之前**, + * 拿到的是 null,得自己去查库,等于绕开缓存重查一次。 + * `option_kratos` 拿到的已经是解析好的数组,改完直接返回即可。 + * + * 🔴 为什么不放在 normalize 里做:normalize 是纯函数,它的职责是 + * 「输入 → 可渲染的设置」。让它去改后台表单看到的东西,是把 + * 「渲染语义」和「配置界面语义」混在一起。这里用一层 filter 分开, + * 哪一层出问题都能单独定位。 + * + * @param mixed $value get_option('kratos') 的返回值 + * @return mixed + */ +function yang_toc_admin_legacy_values($value) +{ + if (!is_array($value)) { + return $value; + } + + /* 老键必须存在且合法,否则没有可投影的值 */ + if (!array_key_exists('toc_default', $value)) { + return $value; + } + $legacy = $value['toc_default']; + if (!in_array($legacy, array('show', 'hide'), true)) { + return $value; + } + + /* 只补**缺失**的键。已经有的不动 —— 用户动过新下拉框之后, + 新键才是真相,老键是历史残留。 */ + foreach (array('toc_inline_default', 'toc_float_default') as $k) { + if (!array_key_exists($k, $value)) { + $value[$k] = $legacy; + } + } + + return $value; +} + +/* 只在后台挂。前台也挂上的话,会让 kratos_option() 读到的值多出两个 + 本来不存在的键 —— 虽然无害(normalize 有白名单),但那是没有理由的 + 行为改变,且会让前台与"库里真实内容"不再一致。 */ +if (is_admin()) { + add_filter('option_kratos', 'yang_toc_admin_legacy_values'); +} + /** * 把设置翻译成渲染参数(与短代码属性同名同格式) * @@ -293,7 +430,9 @@ function yang_toc_atts_from_settings($s = null) 'levels' => implode(',', $levels), 'numbering' => !empty($s['numbering']) ? '1' : '0', 'toggle' => '1', - 'open' => $s['default'] === 'show' ? '1' : '0', + /* 正文那份默认开合。只影响正文内嵌的
; + 浮层走 yang_toc_rail_html() 自己的 float_default,两条路互不引用。 */ + 'open' => $s['inline_default'] === 'show' ? '1' : '0', ); } @@ -979,7 +1118,10 @@ function yang_toc_rail_html($items, $plan) } $side = ($plan['side'] === 'right') ? 'right' : 'left'; - $open = ($s['default'] === 'show'); + /* 浮层那份默认开合,**独立于**正文那份(3.2.59 起)。 + 展开态写在 class 上(is-open),所以没有 JS 也能看到默认状态; + JS 只负责之后的切换 —— 这个特性由 3.2.30 定下,拆分后必须保留。 */ + $open = ($s['float_default'] === 'show'); $title = $s['title']; $cls = 'yang-toc-rail yang-toc-rail-' . $side . ($open ? ' is-open' : ''); diff --git a/style.css b/style.css index 15f5b4d..32779a9 100644 --- a/style.css +++ b/style.css @@ -1,7 +1,7 @@ /* Theme Name: Kratos_Yang Text Domain: kratos_Yang -Version: 3.2.58 +Version: 3.2.59 Requires PHP: 7.0 Description: 这是一个Kratos主题的分支版本,用来增加一些定制化功能。 Tags: 博客, 双栏, 响应式, 自定义背景, 自定义颜色, 自定义图标, 自定义菜单, 特色图像, 收录优化, 邮件中心, 主题选项, 小工具, 文章置顶, 无障碍友好