diff --git a/inc/options-framework/js/options-custom.js b/inc/options-framework/js/options-custom.js index f07f3aa..5ab0514 100644 --- a/inc/options-framework/js/options-custom.js +++ b/inc/options-framework/js/options-custom.js @@ -135,25 +135,49 @@ jQuery(document).ready(function ($) { // 硬编码的 jQuery)。所以主题里其它 19 处 'requires' 其实都是死代码, // 这里不再往那个坑里加,直接写显式逻辑。 // - // 为什么连说明文字一起藏:用户没勾别名生成时,那段「去百度申请 APPID」 - // 只会是噪声。 + // ── 3.2.58 起这里变成**两层**条件 ────────────────────────────────── // - // ⚠️ 用 delegation + toggleClass('yq-cond-hide'),不直接绑 input.hide(): - // 原因与上面的自动目录一致(.section 的 display 是 flow-root, - // jQuery 会把行内 display 写成 block,回退 3.2.28 的布局修复)。 - // info 那块没有 .section 类,靠 CSS 里补的 .section-info 规则命中。 + // 第一层(原有):w_on_slug 勾了 → 这一整块才出现 + // 第二层(本次):w_provider 选了谁 → 只出现那一家的输入框 + // + // 两层是「与」的关系:没勾别名生成时,整块都不该出现(不管下拉框选的谁)。 + // + // 🔴🔴 最容易踩的坑:这两层必须**在同一个函数里一起算**,不能各写一个 + // yq_toggle_xxx() 分别去 toggleClass 同一批元素。因为 toggleClass 的 + // 第二参数是「绝对赋值」不是「与」: + // 函数A 按第一层算出 true → 去掉 yq-cond-hide(显示) + // 函数B 按第二层算出 false → 加上 yq-cond-hide(隐藏) + // 两个都跑完之后,结果**只取决于谁后注册**,跟真实业务无关。 + // 表现就是「勾了别名生成、选了百度,百度框却时有时无」这种鬼故事。 + // + // ── 分组是数据驱动的,不是 JS 里写死映射表 ──────────────────────── + // 服务商清单只有 theme-writing.php 的 yang_writing_providers() 一份。 + // 若在这里抄一遍 {baidu:[...], youdao:[...]},加一家要改两处,且不一致时 + // **不报错**,只表现成「新服务商的框永远不出现」。所以分组标记写在 PHP + // 每个字段的 'class' 上: + // yang-prov → 我参与「按服务商显示」 + // yang-prov- → 我属于 这家 + // 这里只读 class、和下拉框当前 value 比对,零服务商知识。 function yq_toggle_writing_slug_fields () { var on = jQuery('#w_on_slug').is(':checked') + + // 整块的两个「共同成员」(不区分服务商的部分) jQuery('#section-w_provider').toggleClass('yq-cond-hide', !on) - jQuery('#section-w_baidu_appid').toggleClass('yq-cond-hide', !on) - jQuery('#section-w_baidu_key').toggleClass('yq-cond-hide', !on) - jQuery('#section-w_youdao_appid').toggleClass('yq-cond-hide', !on) - jQuery('#section-w_youdao_key').toggleClass('yq-cond-hide', !on) jQuery('#section-w_timeout').toggleClass('yq-cond-hide', !on) - jQuery('#w_provider_help').toggleClass('yq-cond-hide', !on) + + // 按服务商分组的字段:两层条件合成一个结果再赋值 + var provider = String(jQuery('#w_provider').val() || '') + + jQuery('.yang-prov').each(function () { + var el = jQuery(this) + var belongs = el.hasClass('yang-prov-' + provider) + // 第一层不成立时,第二层算什么都不重要 —— 一律隐藏 + el.toggleClass('yq-cond-hide', !(on && belongs)) + }) } - jQuery(document).on('change', '#w_on_slug', yq_toggle_writing_slug_fields) + // 两个控件任一变化都要重算:下拉框换了服务商、或者勾掉别名生成 + jQuery(document).on('change', '#w_on_slug, #w_provider', yq_toggle_writing_slug_fields) yq_toggle_writing_slug_fields() // 写作增强:「每篇最多新增几个标签」只在「自动生成标签」勾选时才有意义。 diff --git a/inc/theme-options.php b/inc/theme-options.php index 952eddc..9eb79a1 100644 --- a/inc/theme-options.php +++ b/inc/theme-options.php @@ -1364,42 +1364,111 @@ function kratos_options() $prov_sel[$k] = $v['label']; } + /* ── 首帧显隐:在服务端就把它算对 ────────────────────────── + * + * 不这么做的后果:进设置页时四个密钥框**全部先显示**,等 + * options-custom.js 跑到 yq_toggle_writing_slug_fields() 才被藏起来 —— + * 每次进页面、每次保存后重载,都闪一下。 + * + * inc/theme-avatar.php 那边踩过同一个坑,结论一样: + * 「首帧的显隐由 PHP 打 class 上(不闪),JS 只管‘点了之后’。」 + * + * ⚠️ 这里读的是**当前已保存值**,不是 std。用户没保存过时 + * kratos_option 会给回传的默认值,正好也是表单上显示的值,一致。 */ + $yang_prov_now = (string) kratos_option('w_provider', 'mymemory'); + if (!isset($prov_sel[$yang_prov_now])) { + /* 库里的值不在清单里(配置被改坏 / 服务商下架)时,按第一项算, + 与 select 实际会选中的那项保持一致 —— 否则首帧和 JS 修正后不一样。 + + 🔴 别用 key($prov_sel) 取「第一项」:那读的是数组**内部指针**, + 而指针位置取决于前面有没有跑过 foreach / reset,属于隐式状态。 + PHP 7 起 foreach 按值遍历不动指针,所以现在恰好对;但这是巧合, + 不是保证 —— 中间插一句 reset() 或改成按引用遍历就会变。 + 线上硬基线是 PHP 7.4,用 array_key_first()(7.3+)才是显式的。 */ + $yang_first = function_exists('array_key_first') ? array_key_first($prov_sel) : null; + if ($yang_first === null && !empty($prov_sel)) { + $yang_keys = array_keys($prov_sel); + $yang_first = $yang_keys[0]; + } + $yang_prov_now = (string) $yang_first; + } + $yang_on_slug = function_exists('yang_writing_on') ? yang_writing_on('w_on_slug') : false; + + /* 生成某个字段的 class。$key 为 null 表示「不按服务商分组、只看总开关」。 */ + $yang_prov_class = function ($key = null) use ($yang_on_slug, $yang_prov_now) { + if ($key === null) { + return $yang_on_slug ? '' : 'yq-cond-hide'; + } + $cls = 'yang-prov yang-prov-' . $key; + if (!$yang_on_slug || $key !== $yang_prov_now) { + $cls .= ' yq-cond-hide'; + } + return $cls; + }; + $options[] = array( 'name' => __('翻译服务商', 'kratos'), 'desc' => __('别名生成用的翻译服务。MyMemory 无需密钥,开箱可用,' - . '但它是国外服务、有每日字符限额;被限流时换成百度即可。', 'kratos'), + . '但它是国外服务、有每日字符限额;被限流时换成百度即可。' + . '
选定之后,下方只出现这一家的账号输入框 —— 用不上的不会摆在那里碍事。', 'kratos'), 'std' => 'mymemory', 'id' => 'w_provider', 'type' => 'select', 'options' => $prov_sel, + 'class' => $yang_prov_class(), ); + /* ── 按服务商分组的账号字段 ──────────────────────────────── + * + * 需求(3.2.58):选了谁才显示谁的输入框,别把四家的密钥框全摆着。 + * + * 实现要点 —— `class` 里那两个勾子不是装饰,是**唯一的分组声明处**: + * yang-prov 「我说哪家就显示哪家」的成员标记 + * yang-prov- 属于哪一家,key 必须与 yang_writing_providers() 一致 + * + * 为什么把分组写在 PHP 而不是 JS 里写死一张映射表: + * 服务商清单本来就只有 theme-writing.php 一份(yang_writing_providers())。 + * 若 JS 里再抄一份 {baidu:[...], youdao:[...]},将来加一家就得改两处, + * 而且两边不一致时**不会报错**,只会表现成「新服务商的框永远不出现」。 + * 现在 JS 只做一件事:读元素上的 class,和下拉框当前值比对。 + * + * ⚠️ 框架确实会把这里的 class 拼到包裹 div 上(见 + * class-options-interface.php:70 的 `$class .= ' ' . $value['class']`), + * 所以 text/password 会得到 `section section-text yang-prov ...`。 + * + * ⚠️ info 类型走的是另一条分支(:272),它拼出来的 class 是 + * `section section-info yang-prov ...` —— 注意它**也有 .section 类**。 + * 本文件下面那段 CSS 注释里写的「info 不生成 .section 包裹层」只对 + * `id="section-xxx"` 这个 id 成立,class 是有的。别再被那句话误导。 + */ + + /* MyMemory:无需任何账号,说明只有一句。 */ $options[] = array( 'name' => __('服务商账号填写说明', 'kratos'), - /* 给 info 一个 id:它不生成 section- 包裹层(框架对 info 特殊处理), - 所以只能靠这个 id 让 JS 能把它一起折叠。 */ - 'id' => 'w_provider_help', - 'desc' => __('MyMemory:不用填任何东西。
' - . '百度翻译:访问 fanyi-api.baidu.com ' - . '→ 注册登录 → 进入「管理控制台」→ 在「开发者信息」里可看到 APPID 与 密钥。' - . '免费版每月 5 万字符。
' - . '有道翻译:访问 ai.youdao.com ' - . '→ 注册登录 → 「创建应用」选「文本翻译」→ 在「应用管理」里可看到 应用 ID 与 应用密钥。', 'kratos'), + 'id' => 'w_provider_help_mymemory', + 'desc' => __('MyMemory 不用填任何东西,选它就已经可用。' + . '
它是免费公共服务,每日有字符限额;某天开始报错、别名生成不出来,' + . '多半是限额用完了 —— 换百度即可。', 'kratos'), 'type' => 'info', + 'class' => $yang_prov_class('mymemory'), + ); + + $options[] = array( + 'name' => __('百度翻译 · 账号怎么拿', 'kratos'), + 'id' => 'w_provider_help_baidu', + 'desc' => __('访问 fanyi-api.baidu.com ' + . '→ 注册登录 → 进入「管理控制台」→ 在「开发者信息」里可看到 APPID 与 密钥。' + . '
免费版每月 5 万字符,对个人博客够用。', 'kratos'), + 'type' => 'info', + 'class' => $yang_prov_class('baidu'), ); - /* 密钥字段用 password 类型。 - * - * ⚠️ 注意:框架会把真实值写进 HTML 的 value 属性(浏览器里 F12 - * 就能看到)。SMTP 密码字段现在也是这样。这里保持与之一致, - * 不单独另做一套脱敏 —— 否则同一个页面上两种行为,更容易混淆。 - * 想彻底避免,只能改成「不回显、留空即不修改」,那会牵动整个 - * 框架的保存语义,不在本次范围。 */ $options[] = array( 'name' => __('百度 APPID', 'kratos'), - 'desc' => __('选「百度翻译」时填写。', 'kratos'), + 'desc' => __('在百度控制台的「开发者信息」里,形如 20240xxx 的一串数字。', 'kratos'), 'id' => 'w_baidu_appid', 'type' => 'text', + 'class' => $yang_prov_class('baidu'), ); /* 密钥字段的特殊处理(只对这两项生效)。 @@ -1417,29 +1486,42 @@ function kratos_options() $options[] = array( 'name' => __('百度密钥', 'kratos'), - 'desc' => __('选「百度翻译」时填写。留空表示不修改已保存的密钥。', 'kratos') + 'desc' => __('留空表示不修改已保存的密钥。', 'kratos') . ($yang_baidu_hint !== '' ? '
当前已保存:' . esc_html($yang_baidu_hint) . '' : ''), 'id' => 'w_baidu_key', 'type' => 'password', 'std' => '', + 'class' => $yang_prov_class('baidu'), + ); + + $options[] = array( + 'name' => __('有道翻译 · 账号怎么拿', 'kratos'), + 'id' => 'w_provider_help_youdao', + 'desc' => __('访问 ai.youdao.com ' + . '→ 注册登录 → 「创建应用」选「文本翻译」→ 在「应用管理」里可看到 应用 ID 与 应用密钥。', 'kratos'), + 'type' => 'info', + 'class' => $yang_prov_class('youdao'), ); $options[] = array( 'name' => __('有道应用 ID', 'kratos'), - 'desc' => __('选「有道翻译」时填写。', 'kratos'), + 'desc' => __('在「应用管理」里查。', 'kratos'), 'id' => 'w_youdao_appid', 'type' => 'text', + 'class' => $yang_prov_class('youdao'), ); $options[] = array( 'name' => __('有道应用密钥', 'kratos'), - 'desc' => __('选「有道翻译」时填写。留空表示不修改已保存的密钥。', 'kratos') + 'desc' => __('留空表示不修改已保存的密钥。', 'kratos') . ($yang_youdao_hint !== '' ? '
当前已保存:' . esc_html($yang_youdao_hint) . '' : ''), 'id' => 'w_youdao_key', 'type' => 'password', 'std' => '', + 'class' => $yang_prov_class('youdao'), ); + /* 超时对所有服务商通用,所以只跟第一层(w_on_slug)走,不分服务商分组。 */ $options[] = array( 'name' => __('接口超时(秒)', 'kratos'), 'desc' => __('调用翻译接口的最长等待时间,建议 8 秒。' @@ -1447,6 +1529,7 @@ function kratos_options() 'std' => '8', 'id' => 'w_timeout', 'type' => 'text', + 'class' => $yang_prov_class(), ); /* ══════════════════════════════════════════════════════════ diff --git a/style.css b/style.css index 2e86921..15f5b4d 100644 --- a/style.css +++ b/style.css @@ -1,7 +1,7 @@ /* Theme Name: Kratos_Yang Text Domain: kratos_Yang -Version: 3.2.57 +Version: 3.2.58 Requires PHP: 7.0 Description: 这是一个Kratos主题的分支版本,用来增加一些定制化功能。 Tags: 博客, 双栏, 响应式, 自定义背景, 自定义颜色, 自定义图标, 自定义菜单, 特色图像, 收录优化, 邮件中心, 主题选项, 小工具, 文章置顶, 无障碍友好