fix(widgets): 分类挂件改成「点开才占一行」的下拉面板 #24

需求改判:3.2.54 做的「父级常显 + 子级折叠树」被否,用户要的是
「不点只有一行,点开把所有一级分类拉出来」的真下拉面板。

## 改了什么

- widget() 重写:`<details class="yang-cats-drop">` + `<summary>` 触发条
  + `position:absolute` 浮层面板。收起时整个挂件只占一行(40.5px),
  展开时面板盖在下方内容上,不把后面的挂件推走。
- children 兜底值 true → false。线上实例的配置里本来没有这个键,
  所以走 pick() 兜底 ⇒ 改兜底就等于改线上行为,用户零操作即变成
  「只列一级分类」,不用进后台勾一次。
- 子级不再嵌套 <details>(3.2.54 是嵌套)。现在父级与子级的 DOM
  结构完全一致,3.2.54 那个「space-between 推不动、数字右边界差
  7.1px」的缺陷从结构上消失了,不需要 flex:1 补丁。
- 补一段渐进增强脚本,只做「点外部收起 / Esc 收起并还焦点」两件事,
  不接管开合本身(关掉 JS 仍能正常展开跳转)。
- CSS:.w-cats 区块整块重写。修掉子级 li 右内边距叠加的缺陷 ——
  子级 ul 嵌在已 padding:0 12px 的父级 li 里,子级 li 又写了一遍
  右内边距 ⇒ 叠加成 24px,导致子级数字比父级多缩进 12px、父子排不成
  一列,竖着比大小时会错位。改成 0 后父子数字同列。
- style.css 版本号 3.2.54 → 3.2.55(3.2.54 已落过 update.json,
  同号重发会让 WP 的 version_compare 判「没有新版本」)。

## 验证

- 端到端(线上真实 WordPress,96 项):全通过
- 反证:内容判据 12 组 + 端到端 3 组,全部真红,无一组空跑
- 视觉几何(真实浏览器 50 项):全通过
- 上一版(3.2.53)老代码跑同一套端到端判据:88 项失败
- 包内 PHP lint 96 个全过;zip 无 1980 时间戳(防 OPcache 误判)

## 发布

只写 theme-update/:zip + readme + update.json → 3.2.55。
线上主题目录仍是 3.2.54,等用户在后台点升级(红线)。
This commit is contained in:
2026-10-27 13:00:37 +08:00
parent 4c1d67b86b
commit f5fd363dce
4 changed files with 388 additions and 274 deletions
+240 -219
View File
@@ -2072,7 +2072,7 @@ button:focus {
} }
/* ══════════════════════════════════════════════════════════════════════ /* ══════════════════════════════════════════════════════════════════════
* 「文章分类」挂件(.w-cats)(3.2.54 / 工单 #24) * 「文章分类」挂件(.w-cats)(3.2.55 / 工单 #24)
* *
* ── 它为什么要重做 ──────────────────────────────────────────────────── * ── 它为什么要重做 ────────────────────────────────────────────────────
* 原来侧栏跑的是 WP 核心 `WP_Widget_Categories`,实例配置 `dropdown = 1`, * 原来侧栏跑的是 WP 核心 `WP_Widget_Categories`,实例配置 `dropdown = 1`,
@@ -2081,237 +2081,263 @@ button:focus {
* 更糟的是主题里那套 `.widget_categories ul li` 规则只匹配 `ul`, * 更糟的是主题里那套 `.widget_categories ul li` 规则只匹配 `ul`,
* 下拉框里没有 `ul` ⇒ **写了也一条不生效**。 * 下拉框里没有 `ul` ⇒ **写了也一条不生效**。
* *
* 现在改成主题自建的父子折叠树,和 `.w-comments` / `.w-tags` 同一套视觉。 * 3.2.54 先改成「父级常显 + 子级折叠树」,8 个父级铺开占约 300px 侧栏高度,
* 被用户否决。3.2.55 改成**真下拉面板**:收起一行、点击弹出、默认只列一级。
* 形态回到"类似原来的 select",但外观是自己画的,能统一到侧栏视觉。
* *
* ── 一条硬约束(3.2.51 的教训,别再踩)──────────────────────────────── * ── 一条硬约束(3.2.51 的教训,别再踩)────────────────────────────────
* 🔴 选择器**只依赖 `.w-cats` 自身**,绝不写 `.k-main .sidebar .w-cats`。 * 🔴 选择器**只依赖 `.w-cats` 自身**,绝不写 `.k-main .sidebar .w-cats`。
* 3.2.51「个人简介撑爆」就是三段祖先选择器造成的:挂件在后台 * 3.2.51「个人简介撑爆」就是三段祖先选择器造成的:挂件在后台
* 「外观→小工具」的预览里没有 `.k-main .sidebar` 那层,规则全部失效。 * 「外观→小工具」的预览里没有 `.k-main .sidebar` 那层,规则全部失效。
* 前台看着正常,恰恰把问题藏死。 * 前台看着正常,恰恰把问题藏死。
* 🔴 所以这里**不写内边距/白底**:那由外层 `.widget` 提供 * 🔴 所以这里**不写外边距/白底**:那由外层 `.widget` 提供
* (前台 `.k-main .sidebar .widget`,后台由 WP 自己的挂件容器提供)。 * (前台 `.k-main .sidebar .widget`,后台由 WP 自己的挂件容器提供)。
* *
* ── 视觉层级 ────────────────────────────────────────────────────────── * ── 浮层凭什么不被裁掉(实测过,不是猜的)─────────────────────────────
* 父分类 15px #333 ← 主体,带折叠箭头 * `.w-cats` 本身就是那个 `.widget`(类名是 `widget w-cats` 两个)。
* 子分类 14px #666 ← 比父级轻一档,靠左侧缩进 + 细线表示从属 * 它、`.sidebar`、`.k-main` 都**没有** `overflow` 声明;
* 计数 12px #bbb ← 最轻,右对齐;父级数字含子级(pad_counts) * `body { overflow-x: hidden }` 只管横向,纵向裁剪不影响面板。
* 三层字号必须不同 —— 一样大的话 29 个分类会糊成一片,找不着重点。 * ⇒ 面板可以溢出挂件边界往下盖。
* 🔴 若将来给 `.widget` 加上 `overflow:hidden`,面板会被整块切掉 ——
* 这条写在这里就是给那时候看的。
* ══════════════════════════════════════════════════════════════════════ */ * ══════════════════════════════════════════════════════════════════════ */
.w-cats .yang-cat-list { /* 定位锚点:面板要相对「触发条」定位,所以 <details> 必须是 containing block。
* 🔴 不能省:缺了它,面板的 absolute 会一路往上找到最近的定位祖先,
* 最坏情况直接贴到视口(`position:absolute` 的包含块规则),
* 面板会飘到页面左上角去。 */
.w-cats .yang-cats-drop {
position: relative;
}
/* ── 触发条(收起时唯一可见的一行)────────────────────────────────── */
.w-cats .yang-cats-trigger {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 12px;
border: 1px solid #e7e7e7;
border-radius: 3px;
background-color: #fbfbfb;
color: #333;
font-size: 15px;
line-height: 1.5;
cursor: pointer;
list-style: none; /* 关掉原生三角 marker(Firefox 认这个) */
-webkit-transition: border-color 0.2s, background-color 0.2s;
transition: border-color 0.2s, background-color 0.2s;
/* 关掉长按选中:手机上点开面板时容易顺手把文字选中,很碍事 */
-webkit-user-select: none;
user-select: none;
}
/* Chrome/Safari/Edge 的原生三角 marker 走这个伪元素,不是 list-style */
.w-cats .yang-cats-trigger::-webkit-details-marker {
display: none;
}
.w-cats .yang-cats-trigger:hover {
border-color: #00a2ff;
background-color: #fff;
}
/* 展开时:触发条与面板连成一体(下边圆角收掉、下边框交给面板) */
.w-cats .yang-cats-drop[open] > .yang-cats-trigger {
border-color: #00a2ff;
border-bottom-color: transparent;
border-radius: 3px 3px 0 0;
background-color: #fff;
color: #00a2ff;
}
/* 长分类名:允许任意位置换行(中文没有空格),但不许把箭头挤走 */
.w-cats .yang-cats-label {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
overflow-wrap: break-word;
}
/* 箭头:CSS 画的三角,展开时翻 180°。
*
* 🔴 为什么不用字体图标:主题的 kicon 字体只含固定几个字形,
* 加一个要动字体文件;而这个三角用边框就能画,
* 也不会因字体没加载而"箭头消失"。 */
.w-cats .yang-cats-caret {
flex: 0 0 auto;
width: 0;
height: 0;
margin-left: 8px;
border-top: 5px solid #999;
border-right: 4px solid transparent;
border-left: 4px solid transparent;
-webkit-transition: -webkit-transform 0.2s;
transition: transform 0.2s;
}
.w-cats .yang-cats-trigger:hover .yang-cats-caret,
.w-cats .yang-cats-drop[open] > .yang-cats-trigger .yang-cats-caret {
border-top-color: #00a2ff;
}
.w-cats .yang-cats-drop[open] > .yang-cats-trigger .yang-cats-caret {
-webkit-transform: rotate(180deg);
transform: rotate(180deg);
}
/* ── 面板 ──────────────────────────────────────────────────────────────
*
* 🔴 `position: absolute` 是「不推开下面挂件」的关键。
* 若写成 static/relative,展开时整块侧栏往下跳,页面会抖。
*
* 🔴 必须显式写 `:not([open]) .panel { display:none }`,不能只靠浏览器默认。
* 实测背景:`<details>` 收起时浏览器确实不渲染非 summary 子元素,
* 但那属于 UA 样式;一旦面板带 `position:absolute`,某些引擎会把
* "未渲染"与"不参与布局"混起来处理,历史上出现过收起状态仍能
* 被 Tab 聚焦到的情况(焦点跑到看不见的链接上)。显式关掉最稳。
*
* 🔴 `z-index: 20`:主题里更高的只有页脚工具箱的 999(fixed 定位,
* 在页面右下角,与侧栏不重叠)和 banner 的 10。
* 面板要盖住**后面那些挂件**,所以必须建立自己的堆叠层级。 */
.w-cats .yang-cats-panel {
position: absolute;
top: 100%;
right: 0;
left: 0;
z-index: 20;
max-height: 320px;
overflow-x: hidden;
overflow-y: auto;
border: 1px solid #00a2ff;
border-top: none;
border-radius: 0 0 3px 3px;
background-color: #fff;
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
/* iOS 上惯性滚动才顺滑 */
-webkit-overflow-scrolling: touch;
}
.w-cats .yang-cats-drop:not([open]) .yang-cats-panel {
display: none;
}
/* ── 列表与行 ──────────────────────────────────────────────────────────
*
* 🔴🔴 必须重置主题自己的全局 `ul > li`(kratos.css:74):
*
* ul > li { margin-bottom: 6px; padding-left: 2px;
* list-style: circle; line-height: 22px; }
*
* 这一条 3.2.54 是"线上量坐标"才发现的:光看代码很难想到,
* 因为它的特异性 (0,0,2) 看着很弱、而且名字里只是 `ul > li`。
* 不重置的后果都能量出来:
* ① 每行多 6px 下边距,9 行累积 54px —— 面板虚高、滚动条提前出现;
* ② 每行左边多 2px,名字与触发条的文字**对不齐**(差 2px);
* ③ `list-style: circle` 会让每行前面多个圆点;
* ④ `line-height: 22px` 会顶掉组件的 1.5。
*
* ⇒ 在这个组件的每一层 `li` 上都重置,不靠 `:not()` 分叉。
* 3.2.54 正是只在 `:not(.has-children)` 上重置,导致带子级那行漏网。 */
.w-cats .yang-cats-list,
.w-cats .yang-cats-children {
margin: 0; margin: 0;
padding: 0; padding: 0;
list-style: none; list-style: none;
} }
/* 首层每一行:让「名字 · 数字」两端对齐。 .w-cats .yang-cats-item {
*
* 🔴🔴 这里踩过一个真坑,记清楚 —— 两种行的 DOM 结构**不一样**:
*
* 无子级: <li class="yang-cat-item">
* <a class="yang-cat-link">名字</a>
* <span class="yang-cat-count">12</span> ← a 的**兄弟**
* </li>
* 有子级: <li class="yang-cat-item has-children">
* <details><summary>
* <span class="yang-cat-caret"></span>
* <a class="yang-cat-link">名字</a> ← 都是 summary 的**子项**
* <span class="yang-cat-count">120</span>
* </summary>...</details>
* </li>
*
* 我第一版把 flex 写在 `.yang-cat-item > .yang-cat-link` 上(也就是 `<a>` 自身),
* 结果无子级那一行里**只有 `<a>` 变成 flex 容器,而它内部只有一个文本节点**,
* 于是 `space-between` 没有第二个子项可推 —— 数字留在原地紧贴文字,
* 实测右侧间隙 299px、行高被撑到 36.5px(数字挤到下一行)。
*
* ⇒ 正确做法:flex 必须加在**同时包含名字和数字的那一层**,
* 也就是 `<li>`(无子级)与 `<summary>`(有子级)。
*
* 🔴🔴 还要**重置主题自己的全局 `ul > li`**(kratos.css:74)——
* 这条是 3.2.54 实测抓出来的,光看代码很难想到:
*
* ul > li { margin-bottom: 6px; padding-left: 2px;
* list-style: circle; line-height: 22px; }
*
* 它的特异性是 (0,0,2),而我们 `.w-cats .yang-cat-item` 是 (0,2,0),
* 本来能压过 —— 但 `padding-left` / `margin-bottom` 那几条
* **只写在了 `:not(.has-children)` 上**,带子级的 `<li>` 完全没被覆盖:
*
* 实测(线上真实页面注入新 DOM 后 getComputedStyle):
* 无子级 li display:flex pl:0px mb:6px
* 有子级 li display:list-item pl:2px mb:6px ← 没被覆盖
*
* 后果有两个,都是线上能看出来的:
* ① 每行之间多出 6px 空隙,8 行累积 42px —— 行距看着松散、不齐;
* ② 有子级那行名字被推开 2px,与无子级行**对不齐**(实测 30 vs 45,
* 其中 13px 是箭头按设计占的宽度,剩下 2px 就是这个 padding)。
*
* ⇒ 改成在 `.yang-cat-item` 上统一重置,不靠 `:not()` 分叉。
* 这样两种行基线一致,将来加第三层结构也不会漏。 */
.w-cats .yang-cat-item {
margin-bottom: 0;
padding-left: 0;
list-style: none;
/* `<li>` 的 line-height 也被全局规则钉成 22px,这里跟上组件字号 */
line-height: 1.5;
}
/* 两种行都要成为 flex 容器,名字与数字分居两端。
* 🔴 用 `display:flex` 覆盖 `list-item` 会**同时**干掉圆点标记
* (list-item 是唯一能显示 marker 的 display 值),
* 所以这里不再需要额外写 list-style-type:none 去压 circle。
* 🔴 上下 padding 不在这里给:带子级的行由 <summary> 自己撑(它也写了
* 7px),两处都写会变成 14px,行高直接翻倍。分开写在下面。 */
.w-cats .yang-cat-item {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
margin-bottom: 0;
padding: 0;
list-style: none;
line-height: 1.5;
} }
/* 只有「无子级」和「子级」这两种叶子行需要自己撑上下间距 */ /* 🔴 这一版的 DOM 结构**统一了**,这是相对 3.2.54 的一处实质改进:
.w-cats .yang-cat-item:not(.has-children) { *
padding: 7px 0; * 父级: <li class="yang-cats-item"><a>名字</a><span>数字</span></li>
} * 子级: <li class="yang-cats-item"><a>名字</a><span>数字</span></li>
*
* 两者一字不差,都是「<a> + <span> 两个直接子项」,flex 加在 li 上
* 就能两端对齐,不需要区分有没有子级。
*
* 3.2.54 那版父级行是 `<li><details><summary><a/><span/></summary></details></li>`,
* li 里只有 **一个** flex 子项 `<details>`,而 `<details>` 默认宽度
* auto(按内容收缩)⇒ `space-between` 没有第二个子项可推 ⇒ 数字
* 右边界比其他行短 7.1px(实测 [220.5,220.5,227.6,227.6,220.5])。
* 当时的修法是给 `<details>` 加 `flex:1 1 auto; min-width:0`。
* 现在没有嵌套详情框了,**那个 bug 从结构上就不存在了**。 */
/* 行内文字(无子级 li 里的 a、有子级 summary 里的 a): .w-cats .yang-cats-list > .yang-cats-item > .yang-cats-link {
* 只负责字号/颜色/可换行,**不再**做 flex 容器。 */
.w-cats .yang-cat-item > .yang-cat-link {
min-width: 0; min-width: 0;
color: #333; color: #333;
font-size: 15px; font-size: 15px;
line-height: 1.5; line-height: 1.5;
text-decoration: none; text-decoration: none;
/* 中文长分类名没有空格,必须允许任意位置换行,否则撑破挂件 */ /* 中文长分类名没有空格,必须允许任意位置换行,否则撑破面板 */
overflow-wrap: break-word; overflow-wrap: break-word;
word-wrap: break-word; word-wrap: break-word;
} }
.w-cats .yang-cat-item > .yang-cat-link:hover { /* 行内点击热区:把内边距放在 <li> 上而不是 <a> 上,
color: #00a2ff; * 这样点在行尾空白处也算点中这一行(<a> 只有文字宽度时热区很窄)。
* 🔴 但 <a> 撑不满只剩一半,所以让 <a> 吃满左侧空间:
* 数字是固定宽的胶囊,<a> 用 flex:1 占其余全部宽度。 */
.w-cats .yang-cats-list > .yang-cats-item {
padding: 0 12px;
} }
/* ── 折叠容器 ────────────────────────────────────────────────────────── */ .w-cats .yang-cats-list > .yang-cats-item > .yang-cats-link,
.w-cats .yang-cats-children > .yang-cats-item > .yang-cats-link {
/* 🔴 <summary> 默认带 display:list-item 与三角形 marker。
* 我们要自己的箭头(用 CSS 画,不依赖字体图标),
* 所以先关掉原生 marker —— 两个都显示会变成"双箭头"。 */
.w-cats .yang-cat-summary {
display: flex;
align-items: center;
padding: 7px 0;
color: #333;
font-size: 15px;
line-height: 1.5;
cursor: pointer;
list-style: none; /* Firefox */
/* 关掉长按选中,避免手机端点开时误选文字 */
-webkit-user-select: none;
user-select: none;
}
.w-cats .yang-cat-summary::-webkit-details-marker {
display: none;
}
.w-cats .yang-cat-summary:hover {
color: #00a2ff;
}
/* 🔴🔴 `<details>` 必须显式撑满整行宽度,否则**两种行的数字对不齐**。
*
* 这是 3.2.54 视觉几何实测抓出来的真缺陷(不是量法问题):
*
* 无子级行(<a> 是 li 的直接子项) 数字右边界 220.5
* 有子级行(<details> 包着) 数字右边界 227.6 ← 差 7.1px
*
* 原因:无子级行的 `<a>` 与数字是 li 的**两个** flex 子项,
* `space-between` 把它们推到两端,数字贴右沿。
* 有子级行的 li 里只有 **一个** flex 子项 `<details>`,
* `space-between` 没有第二个子项可推 ⇒ 不起作用;
* 而 `<details>` 默认宽度是 auto(按内容收缩),
* 于是整组靠左,数字停在内文字结尾处。
*
* ⇒ 给 `<details>` 加 `flex: 1 1 auto; min-width: 0`,
* 让它吃满 li 的可用宽度,内部的 summary 才能把数字推到右沿。
* `min-width: 0` 不能省:它是 flex 项允许收缩到内容宽度以下的前提,
* 长分类名才能按 overflow-wrap 换行而不是撑破容器(3.2.51 的教训)。
*
* 这条同时解释了为什么 `<summary>` 自己写了 width 也没用 ——
* 父级 `<details>` 收缩了,子级撑满的只是那个收缩后的宽度。 */
.w-cats .yang-cat-group {
flex: 1 1 auto; flex: 1 1 auto;
min-width: 0; padding: 8px 0;
} }
/* summary 里的名字链接:占满剩余宽度,让数字被推到最右 */ .w-cats .yang-cats-list > .yang-cats-item:hover,
.w-cats .yang-cat-summary .yang-cat-link { .w-cats .yang-cats-children > .yang-cats-item:hover {
flex: 1 1 auto; background-color: #f5faff;
min-width: 0;
color: inherit;
text-decoration: none;
overflow-wrap: break-word;
word-wrap: break-word;
} }
.w-cats .yang-cat-summary:hover .yang-cat-link { .w-cats .yang-cats-list > .yang-cats-item > .yang-cats-link:hover {
color: #00a2ff; color: #00a2ff;
} }
/* 箭头:CSS 画的实心三角,展开时旋转 90°。 /* ── 子分类(可选,默认关)──────────────────────────────────────────────
* *
* 🔴 为什么不用字体图标:主题的 kicon 字体只包含固定的几个字形, * 缩进 + 字号轻一档。不加竖线:3.2.54 加过,但那是"常显树"的视觉语言;
* 加一个要动字体文件;而这是个极简三角形,CSS 边框三角形足够, * 在浮层面板里加竖线会让本就不宽的面板更挤,缩进已经足够表达层级。 */
* 且不会因字体没加载而"箭头消失"。 */ .w-cats .yang-cats-children {
.w-cats .yang-cat-caret { /* 子级整段缩进 12px,与父级名字错开一档 */
flex: 0 0 auto; padding: 0 0 6px 12px;
width: 0; background-color: #fcfcfc;
height: 0;
margin-right: 8px;
border-top: 4px solid transparent;
border-bottom: 4px solid transparent;
border-left: 5px solid #bbb;
-webkit-transition: -webkit-transform 0.2s;
transition: transform 0.2s;
} }
.w-cats .yang-cat-summary:hover .yang-cat-caret { /* 🔴 子级 li 的**右侧**内边距必须是 0,不能跟着父级写 12px。
border-left-color: #00a2ff; *
* 这是 3.2.55 几何实测抓出来的真缺陷(不是量法问题):
*
* 父级数字右边界距面板内沿 12px
* 子级数字右边界距面板内沿 24px ← 多出整整 12px
*
* 原因:子级 `<ul>` 嵌在父级 `<li>` 里,而父级 li 已经有
* `padding: 0 12px`,那 12px 已经把子级 ul 的右边界推进去了;
* 子级 li 再写一次 12px ⇒ 叠加成 24px。
*
* 后果很直观:**数字排不成一列**。面板里父级数字靠右、
* 子级数字齐刷刷往左缩 12px,看着像两家不同的列表拼在一起。
* 分类数字是要竖着比的(哪个分类文章多),错开一列就没法比。
*
* ⇒ 子级只留左缩进(由 ul 的 padding-left 提供),右侧归零。 */
.w-cats .yang-cats-children > .yang-cats-item {
padding: 0;
} }
/* 展开时箭头朝下 */ .w-cats .yang-cats-children > .yang-cats-item > .yang-cats-link {
.w-cats details[open] > .yang-cat-summary .yang-cat-caret {
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
}
/* 子级列表:左侧缩进 + 一条竖线表示从属关系。
* 🔴 竖线用 border-left 而不是额外元素:分类层级只有 2 层,
* 加伪元素会让"缩进"和"线"两套定位各自出错。 */
.w-cats .yang-cat-children {
margin: 0;
padding: 0 0 2px 13px;
border-left: 1px solid #f0f0f0;
list-style: none;
}
.w-cats .yang-cat-children > .yang-cat-item {
list-style: none;
/* 子级行比父级紧一档(5px vs 7px):层级越深、行距越小,
* 视觉上才"收"得进去,否则子列表会显得和父级一样重。 */
padding: 5px 0;
}
/* 🔴 子级 li 的 flex 已由上面通用的 `.w-cats .yang-cat-item` 提供,
* 这里**只需**给行高与间距,绝不能再在 `<a>` 上写 flex ——
* 那正是本组件第一版的原始 bug(见上方 DOM 结构说明):
* 子级的 `<a>` 里只有一个文本节点,数字是它的**兄弟**,
* 在 `<a>` 上写 flex + space-between 推不动任何东西。 */
/* 子级每一行:与父级同样两端对齐,但字号轻一档、颜色浅一档 */
.w-cats .yang-cat-children > .yang-cat-item > .yang-cat-link {
min-width: 0; min-width: 0;
/* 左侧不用 padding:竖线已经提供了缩进,再加就成双倍缩进 */ color: #777;
color: #666;
font-size: 14px; font-size: 14px;
line-height: 1.5; line-height: 1.5;
text-decoration: none; text-decoration: none;
@@ -2319,13 +2345,12 @@ button:focus {
word-wrap: break-word; word-wrap: break-word;
} }
.w-cats .yang-cat-children > .yang-cat-item > .yang-cat-link:hover { .w-cats .yang-cats-children > .yang-cats-item > .yang-cats-link:hover {
color: #00a2ff; color: #00a2ff;
} }
/* ── 文章数 ──────────────────────────────────────────────────────────── */ /* ── 文章数 ──────────────────────────────────────────────────────────── */
.w-cats .yang-cats-count {
.w-cats .yang-cat-count {
flex: 0 0 auto; flex: 0 0 auto;
margin-left: 8px; margin-left: 8px;
padding: 1px 7px; padding: 1px 7px;
@@ -2338,50 +2363,46 @@ button:focus {
transition: 0.2s; transition: 0.2s;
} }
/* 悬停联动:鼠标在名字(或整行)上时,数字胶囊也变成主题蓝。 /* 悬停联动:鼠标在整行上时,数字胶囊也变成主题蓝。
* *
* 🔴 第一版写错了:`.yang-cat-item > .yang-cat-link:hover .yang-cat-count` * 🔴 这里**只需要一条**规则(3.2.54 要两条,因为那版两种行结构不同)。
* 里的**空格**表示"数字是链接的后代",可实际上数字是链接的**兄弟** * 现在父级/子级、有没有子级,DOM 都是 `<li><a/><span/></li>`,
* (见上面两种行的 DOM 结构)。于是无子级那一行悬停时胶囊颜色不变, * 所以「悬停 li、染色直接子级 span」这一条就全覆盖。
* 实测 rgb(245,245,245) → rgb(245,245,245),联动完全没生效。
* *
* 正确写法有两条: * 🔴 用 `>` 不用空格:子级 li 也是 .yang-cats-item,
* ① 无子级行:`.yang-cat-item:hover > .yang-cat-count` * 用空格会在悬停父级行时连带把子级数字也染色。 */
* —— 悬停在整行(li)上,把**直接子级**的数字染色。 .w-cats .yang-cats-item:hover > .yang-cats-count {
* 用 `>` 而不是空格,避免连带命中子级的计数。
* ② 有子级行:`.yang-cat-summary:hover .yang-cat-count`
* —— summary 里数字本来就是后代,这条原来是对的,保留。 */
.w-cats .yang-cat-item:not(.has-children):hover > .yang-cat-count,
.w-cats .yang-cat-summary:hover .yang-cat-count {
background-color: #00a2ff; background-color: #00a2ff;
color: #fff; color: #fff;
} }
/* ── 当前分类高亮 ────────────────────────────────────────────────────── */ /* ── 当前分类高亮 ────────────────────────────────────────────────────── */
/* 只染色不加粗:加粗会让这一行宽度变化,29 行里突兀地抖一下。 /* 只染色不加粗:加粗会让这一行宽度变化,列表里突兀地抖一下。
* * 同时给个浅浅的底色,让"我在这"在面板里一眼看到(面板一打开就是
* 🔴 三种情况都要覆盖(同上,注意无子级的 a 是 li 的直接子项、 * 一堆同样式样的行,光靠蓝色文字不够醒目)。 */
* 有子级的 a 在 summary 里,不能用同一个后代选择器一把抓): .w-cats .yang-cats-item.current-cat > .yang-cats-link {
* · 无子级的行高亮 → li.current-cat > .yang-cat-link
* · 有子级的行高亮 → li.current-cat > .yang-cat-summary
* 以及 summary 里的链接(summary 自身不染色时链接会覆盖掉)
* · 子级行高亮 → 子级 li.current-cat > .yang-cat-link
* (这条原来漏了:子级 li 也是 .yang-cat-item,但它的 a 是
* 直接子项,第一条 `> .yang-cat-link` 其实已经覆盖,
* 这里显式写出以免将来改结构时断掉) */
.w-cats .yang-cat-item.current-cat > .yang-cat-link,
.w-cats .yang-cat-item.current-cat > .yang-cat-summary,
.w-cats .yang-cat-item.current-cat > .yang-cat-summary .yang-cat-link,
.w-cats .yang-cat-children > .yang-cat-item.current-cat > .yang-cat-link {
color: #00a2ff; color: #00a2ff;
} }
/* 看子分类时,父级也给个提示(不与真正的高亮抢戏) */ .w-cats .yang-cats-item.current-cat {
.w-cats .yang-cat-item.current-cat-parent > .yang-cat-summary .yang-cat-link { background-color: #f0f8ff;
}
/* 看子分类时,父级也给个提示(不与真正的高亮抢戏:只染字,不铺底) */
.w-cats .yang-cats-item.current-cat-parent > .yang-cats-link {
color: #00a2ff; color: #00a2ff;
} }
/* ── 窄屏 ──────────────────────────────────────────────────────────────
* 992px 以下侧栏整行显示、宽度充裕,面板可以高一点少滚动。
* 🔴 只调 max-height,不改宽度/定位 —— 那些由 flex 与 left/right:0 自适应,
* 写死像素就会在别的容器里错位(3.2.51 的老毛病)。 */
@media (max-width: 991.98px) {
.w-cats .yang-cats-panel {
max-height: 60vh;
}
}
.k-main .sidebar .w-recommended .bookmark-item { .k-main .sidebar .w-recommended .bookmark-item {
display: block; display: block;
overflow: hidden; overflow: hidden;
+1 -1
View File
File diff suppressed because one or more lines are too long
+144 -51
View File
@@ -1101,15 +1101,28 @@ class widget_posts extends WP_Widget
* 沿用 `categories` 则 option 名不变,存量实例继续有效。 * 沿用 `categories` 则 option 名不变,存量实例继续有效。
* 这也意味着 update() **必须容忍核心那套老字段**(dropdown 等),见下方说明。 * 这也意味着 update() **必须容忍核心那套老字段**(dropdown 等),见下方说明。
* *
* ── 「父级常显 + 子级可折叠」的呈现(用户 2026-10-27 选定)────────────── * ── 呈现形态:收起一行 + 点击弹出面板(3.2.55 按用户反馈改判)────────────
* 本站实测 29 个分类(9 父 + 20 子)。全展开侧栏要拉长约 1100px, * 3.2.54 先做成「父级常显 + 子级折叠树」,8 个父级铺开占约 300px 侧栏高度。
* 所以默认只列父级,有子级的父级带一个折叠箭头,点开才展开。 * 用户否决:「不点击的话就一行,这样占用的空间小,点击后把所有的一级分类
* 🔴 折叠用 `<details>/<summary>` 原生实现,**不写一行 JS**: * 拉出来,类似于之前那样(指原生的 <select>),但样式要好看」。
*
* 所以现在是**真下拉面板**:
* 收起 → 一行触发条(当前分类名,不在分类页时显示「选择分类」+ 箭头)
* 展开 → 浮层面板,`position:absolute` 贴在触发条下方,**不推开下面的挂件**
* 内容 → 默认只列一级分类(本站 9 个);子分类是可选项(默认关)
*
* 🔴 仍然用 `<details>/<summary>` 做开合,**核心逻辑不写 JS**:
* - 不依赖 jQuery/Bootstrap 版本,不会跟主题其它脚本抢事件; * - 不依赖 jQuery/Bootstrap 版本,不会跟主题其它脚本抢事件;
* - 键盘可操作、读屏软件能正确朗读,自带 `aria-expanded` 语义; * - 键盘可操作、读屏软件能正确朗读,自带展开/收起语义;
* - 关掉 JS 也能用(分类导航属于基础功能,不该依赖脚本)。 * - 关掉 JS 也能展开、也能点分类跳转(分类导航是基础功能)。
* `<summary>` 里同时放"父级链接 + 箭头按钮"会有嵌套点击问题, * JS 只做两件 `<details>` 原生没有的事:**点面板外部关闭、按 Esc 关闭**。
* 所以箭头用 `::marker` 之外的独立 span,父级链接本身仍是普通 `<a>`。 * 这段是渐进增强:脚本没跑,功能不缺,只是要再点一次触发条才收起来。
*
* 🔴 为什么面板能做绝对定位浮层而不被裁掉(实测过,不是猜的):
* 链路 `.k-main .sidebar .widget` → `.w-cats` → 面板,`sidebar` 与 `widget`
* 都**没有** `overflow` 声明(`body` 只有 `overflow-x:hidden`,不影响纵向),
* 所以面板可以溢出挂件边界。若将来谁给 `.widget` 加了 `overflow:hidden`,
* 面板会被整块裁掉 —— 这条写在这里就是给那时候看的。
* *
* ── 父级数字为什么要 pad_counts(这条最容易做错)──────────────────────── * ── 父级数字为什么要 pad_counts(这条最容易做错)────────────────────────
* 核心 `wp_list_categories()` 在 `show_count && hierarchical` 时**自动**打开 * 核心 `wp_list_categories()` 在 `show_count && hierarchical` 时**自动**打开
@@ -1152,7 +1165,7 @@ class widget_categories extends WP_Widget
* 名字走第 2 参(工单 #23,理由见 widget_ad 的注释)。 */ * 名字走第 2 参(工单 #23,理由见 widget_ad 的注释)。 */
$widget_ops = array( $widget_ops = array(
'classname' => 'widget_categories', 'classname' => 'widget_categories',
'description' => __('按父子层级展示文章分类,父级常显、子级可折叠', 'kratos'), 'description' => __('以「点击展开」的下拉面板展示文章分类,收起时只占一行', 'kratos'),
); );
parent::__construct('categories', __('文章分类', 'kratos'), $widget_ops); parent::__construct('categories', __('文章分类', 'kratos'), $widget_ops);
@@ -1332,7 +1345,15 @@ class widget_categories extends WP_Widget
{ {
$show_count = $this->pick($instance, 'count', true); $show_count = $this->pick($instance, 'count', true);
$hide_empty = $this->pick($instance, 'hide_empty', true); $hide_empty = $this->pick($instance, 'hide_empty', true);
$show_children = $this->pick($instance, 'children', true); /* 🔴 children 默认值 3.2.55 改成 **false**(3.2.54 是 true)。
*
* 用户的诉求原话是「点击后把所有**一级**分类拉出来」—— 默认就该
* 只有一级。线上那个实例 `categories-3` 是核心老配置,压根没有
* children 这个键 ⇒ 走 fallback ⇒ 改默认值就等于改线上呈现,
* 不需要用户去后台勾一下,也不用动数据库。
*
* 想连子级一起看的人,在后台勾上即可(update() 会存成 1)。 */
$show_children = $this->pick($instance, 'children', false);
$orderby = $this->pick($instance, 'orderby', 'name'); $orderby = $this->pick($instance, 'orderby', 'name');
$order = $this->pick($instance, 'order', 'ASC'); $order = $this->pick($instance, 'order', 'ASC');
@@ -1379,7 +1400,57 @@ class widget_categories extends WP_Widget
echo '<div class="' . esc_attr(yang_widget_class('w-cats')) . '">'; echo '<div class="' . esc_attr(yang_widget_class('w-cats')) . '">';
yang_widget_title($args, $instance, __('文章分类', 'kratos'), $this->id_base); yang_widget_title($args, $instance, __('文章分类', 'kratos'), $this->id_base);
echo '<ul class="yang-cat-list">'; /* 🔴 当前分类名要显示在触发条上,让收起状态也能看出"我人在哪个分类"。
*
* 这里必须先把它找出来(而不是边遍历边拼),因为触发条**在面板之前**
* 输出 —— 面板内容才能边遍历边 echo。
*
* 只在 is_category() 时取,空串表示"不在分类页" ⇒ 触发条显示占位文案。
* 父级分类被访问时,$current_id 就是那个父级(不是它的子级),
* 所以不必再往祖先链上找。 */
$current_name = '';
if ($current_id > 0) {
foreach ($tree as $node) {
if ((int) $node['cat']->term_id === $current_id) {
$current_name = (string) $node['cat']->name;
break;
}
foreach ($node['children'] as $kid) {
if ((int) $kid->term_id === $current_id) {
$current_name = (string) $kid->name;
break 2;
}
}
}
}
/* 面板 DOM id:一页上可能有多个实例(虽然实际很少),必须唯一,
* 否则 `<summary>` 里若要写 aria-controls 会指到第一个。
* 用 yang_widget_uid() 拿 `widget_categories-3` 这类稳定串。 */
$panel_id = yang_widget_uid($this) . '-panel';
echo '<details class="yang-cats-drop">';
/* 🔴 触发条用 <summary>,**里面不放 <a>**。
*
* 3.2.54 那版把父级链接塞进 <summary> 里靠"<a> 是 interactive
* content 所以不触发展开"来分工(实测确实可行)。但这一版
* <summary> 的职责变了:它整条就是"开合开关",不该再带导航。
* 把 <a> 放进来只会让"点空白=开合、点文字=跳转"这种
* 半吊子分工回来,反而更难用。
*
* 所以这里纯文本 + 箭头,箭头用 CSS 三角(不依赖字体图标)。 */
echo '<summary class="yang-cats-trigger">';
echo '<span class="yang-cats-label">'
. ($current_name !== ''
? $this->name_html($current_name)
: esc_html__('选择分类', 'kratos'))
. '</span>';
echo '<span class="yang-cats-caret" aria-hidden="true"></span>';
echo '</summary>';
echo '<div class="yang-cats-panel" id="' . esc_attr($panel_id) . '">';
echo '<ul class="yang-cats-list">';
foreach ($tree as $node) { foreach ($tree as $node) {
$root = $node['cat']; $root = $node['cat'];
@@ -1387,7 +1458,7 @@ class widget_categories extends WP_Widget
$kids = $node['children']; $kids = $node['children'];
$has_kids = ($show_children && count($kids) > 0); $has_kids = ($show_children && count($kids) > 0);
$root_cls = 'yang-cat-item'; $root_cls = 'yang-cats-item';
if ($has_kids) { if ($has_kids) {
$root_cls .= ' has-children'; $root_cls .= ' has-children';
} }
@@ -1400,58 +1471,36 @@ class widget_categories extends WP_Widget
} }
echo '<li class="' . esc_attr($root_cls) . '">'; echo '<li class="' . esc_attr($root_cls) . '">';
echo '<a class="yang-cats-link" href="' . esc_url(get_category_link($rid)) . '">'
if ($has_kids) {
/* 有子级:<details> 原生折叠。
*
* 🔴 为什么父级链接可以放在 <summary> 里(这不是偷懒):
* HTML 规范规定,`<summary>` 的"激活即折叠"行为只在该
* 点击目标**不是** interactive content 时才触发,而 `<a>`
* 本身属于 interactive content。所以点名字是**纯导航**、
* 不会顺带展开;点箭头/数字/行内空白才折叠。
* 两个动作各归各的,不需要一行 JS。
* (若哪天有人把 `<a>` 换成 `<span onclick>`,这条就失效了,
* 所以这里写清楚原因,避免后人改错。)
*
* 🔴 为什么用 <details> 而不是 JS 手风琴:
* · 不依赖 jQuery/Bootstrap 版本,也不跟主题其它脚本抢事件;
* · 键盘 Tab + 回车天然可用,读屏软件正确朗读展开状态;
* · 关掉 JS 仍能展开(分类导航是基础功能,不该依赖脚本)。 */
echo '<details class="yang-cat-group">';
echo '<summary class="yang-cat-summary">';
echo '<span class="yang-cat-caret" aria-hidden="true"></span>';
echo '<a class="yang-cat-link" href="' . esc_url(get_category_link($rid)) . '">'
. $this->name_html($root->name) . '</a>'; . $this->name_html($root->name) . '</a>';
if ($show_count) { if ($show_count) {
echo '<span class="yang-cat-count">' . (int) $root->count . '</span>'; echo '<span class="yang-cats-count">' . (int) $root->count . '</span>';
} }
echo '</summary>';
echo '<ul class="yang-cat-children">'; /* 子分类:只有用户显式勾了「显示子分类」才输出。
*
* 🔴 这里**不再用 <details> 嵌套**(3.2.54 是那样)。原因:
* 面板本身就是浮层,展开状态只由触发条一个开关管;
* 再套一层折叠,用户点开面板后仍要再点一次才能看见子级,
* 「点击后把所有的一级分类拉出来」这个诉求就没满足。
* 子级直接平铺在父级下面、缩进显示 —— 一目了然,少一次点击。 */
if ($has_kids) {
echo '<ul class="yang-cats-children">';
foreach ($kids as $kid) { foreach ($kids as $kid) {
$kid_id = (int) $kid->term_id; $kid_id = (int) $kid->term_id;
$kid_cls = 'yang-cat-item'; $kid_cls = 'yang-cats-item';
if ($current_id === $kid_id) { if ($current_id === $kid_id) {
$kid_cls .= ' current-cat'; $kid_cls .= ' current-cat';
} }
echo '<li class="' . esc_attr($kid_cls) . '">'; echo '<li class="' . esc_attr($kid_cls) . '">';
echo '<a class="yang-cat-link" href="' . esc_url(get_category_link($kid_id)) . '">' echo '<a class="yang-cats-link" href="' . esc_url(get_category_link($kid_id)) . '">'
. $this->name_html($kid->name) . '</a>'; . $this->name_html($kid->name) . '</a>';
if ($show_count) { if ($show_count) {
echo '<span class="yang-cat-count">' . (int) $kid->count . '</span>'; echo '<span class="yang-cats-count">' . (int) $kid->count . '</span>';
} }
echo '</li>'; echo '</li>';
} }
echo '</ul>'; echo '</ul>';
echo '</details>';
} else {
// 无子级:普通一行
echo '<a class="yang-cat-link" href="' . esc_url(get_category_link($rid)) . '">'
. $this->name_html($root->name) . '</a>';
if ($show_count) {
echo '<span class="yang-cat-count">' . (int) $root->count . '</span>';
}
} }
echo '</li>'; echo '</li>';
@@ -1459,6 +1508,49 @@ class widget_categories extends WP_Widget
echo '</ul>'; echo '</ul>';
echo '</div>'; echo '</div>';
echo '</details>';
echo '</div>';
/* ── 渐进增强:点面板外部 / 按 Esc 收起 ──────────────────────────
*
* 🔴 为什么这段必须写在挂件里而不是 kratos.js:
* 面板是 `<details>`,只有挂件知道它在哪;写进主脚本就得靠类名
* 全局查询,一旦页面上没有这个挂件就是一次空查询 + 一个常驻事件
* 监听。挂在挂件输出处,没挂件就没这段代码。
*
* 🔴 为什么用 `once` 标志:挂件可以有多个实例,每实例输出一段脚本,
* 全局 document 上的监听会被装 N 次。用 window 上的标志位保证
* 只装一次(监听器内部用 `closest('.w-cats')` 定位到自己)。
*
* 🔴 为什么不用内联 onclick 属性:CSP 里 `unsafe-inline` 一收紧
* 就全废。这里用 addEventListener,属性里只有 data-* 标记。
*
* 🔴 无 JS 时行为:点触发条展开、点分类跳转都正常,只是
* "点外部不自动收起"。功能不缺,所以不写成必要条件。 */
echo '<script>'
. '(function(){'
/* 已经装过一次就直接退出(多实例场景) */
. 'if(window.__yangCatsBound){return;}window.__yangCatsBound=1;'
/* 点外部关闭:只处理"点到的不是这个 details 内部"的情况 */
. 'document.addEventListener("click",function(e){'
. 'var t=e.target;'
. 'var list=document.querySelectorAll(".yang-cats-drop[open]");'
. 'for(var i=0;i<list.length;i++){'
. 'if(!list[i].contains(t)){list[i].removeAttribute("open");}'
. '}'
. '},false);'
/* Esc 关闭:键盘用户 */
. 'document.addEventListener("keydown",function(e){'
. 'if(e.key!=="Escape"&&e.keyCode!==27){return;}'
. 'var list=document.querySelectorAll(".yang-cats-drop[open]");'
. 'for(var i=0;i<list.length;i++){'
. 'list[i].removeAttribute("open");'
. 'var s=list[i].querySelector(".yang-cats-trigger");'
. 'if(s&&s.focus){s.focus();}'
. '}'
. '},false);'
. '})();'
. '</script>';
} }
public function update($new_instance, $old_instance) public function update($new_instance, $old_instance)
@@ -1501,7 +1593,8 @@ class widget_categories extends WP_Widget
$title = isset($instance['title']) ? (string) $instance['title'] : ''; $title = isset($instance['title']) ? (string) $instance['title'] : '';
$show_count = $this->pick($instance, 'count', true); $show_count = $this->pick($instance, 'count', true);
$hide_empty = $this->pick($instance, 'hide_empty', true); $hide_empty = $this->pick($instance, 'hide_empty', true);
$show_children = $this->pick($instance, 'children', true); // 与 widget() 保持同一默认值(理由见 widget() 里的注释)
$show_children = $this->pick($instance, 'children', false);
$orderby = $this->pick($instance, 'orderby', 'name'); $orderby = $this->pick($instance, 'orderby', 'name');
$order = $this->pick($instance, 'order', 'ASC'); $order = $this->pick($instance, 'order', 'ASC');
if ($orderby !== 'count') { if ($orderby !== 'count') {
@@ -1520,8 +1613,8 @@ class widget_categories extends WP_Widget
<p> <p>
<input class="checkbox" type="checkbox" id="<?php echo esc_attr($this->get_field_id('children')); ?>" name="<?php echo esc_attr($this->get_field_name('children')); ?>" <?php checked($show_children); ?>> <input class="checkbox" type="checkbox" id="<?php echo esc_attr($this->get_field_id('children')); ?>" name="<?php echo esc_attr($this->get_field_name('children')); ?>" <?php checked($show_children); ?>>
<label for="<?php echo esc_attr($this->get_field_id('children')); ?>"><?php _e('显示子分类(父级可展开)', 'kratos'); ?></label> <label for="<?php echo esc_attr($this->get_field_id('children')); ?>"><?php _e('展开时同时列出子分类', 'kratos'); ?></label>
<span class="description"><?php _e('关掉则只列父分类。', 'kratos'); ?></span> <span class="description"><?php _e('默认只列一级分类。勾上后,有子分类的父级下面会缩进列出它的子分类。', 'kratos'); ?></span>
</p> </p>
<p> <p>
+1 -1
View File
@@ -1,7 +1,7 @@
/* /*
Theme Name: Kratos_Yang Theme Name: Kratos_Yang
Text Domain: kratos_Yang Text Domain: kratos_Yang
Version: 3.2.54 Version: 3.2.55
Requires PHP: 7.0 Requires PHP: 7.0
Description: 这是一个Kratos主题的分支版本,用来增加一些定制化功能。 Description: 这是一个Kratos主题的分支版本,用来增加一些定制化功能。
Tags: 博客, 双栏, 响应式, 自定义背景, 自定义颜色, 自定义图标, 自定义菜单, 特色图像, 收录优化, 邮件中心, 主题选项, 小工具, 文章置顶, 无障碍友好 Tags: 博客, 双栏, 响应式, 自定义背景, 自定义颜色, 自定义图标, 自定义菜单, 特色图像, 收录优化, 邮件中心, 主题选项, 小工具, 文章置顶, 无障碍友好