laoyang
|
fd27b4d218
|
3.2.24: 评论富文本工具条(B/I/颜色/表情移入)+ kses 放行 span+style + 后台设置页标签左列竖排重排
|
2026-10-24 21:59:06 +08:00 |
|
laoyang
|
78fce2d87d
|
3.2.23 评论区:引导线和输入框聚焦统一成主色
用户对 3.2.22 的反馈:
「这蓝色有点奇怪啊,应该用主题颜色,就是评论的那个按钮背景色。
把评论回复左侧那个竖着的小蓝条,改成这个颜色,下面输入框被选中的
时候,周边的颜色,也变成这个颜色。」
1. 引导线 #cfe6fb → var(--yq-primary)
浅蓝和「发布评论」按钮的背景色(#00a2ff)不是一个蓝,
页面上两蓝并存反而显乱。改成引用主色变量,以后改主色线自动跟。
2. 输入框聚焦变主色 —— 顺带翻出一个藏了三版的老问题
实测发现正文输入框聚焦后边框仍是灰色:kratos.css 里
.k-main .details .comments .comment-respond .comment-textarea
{ border: 1px solid #eee; ... }
特异性 0,6,0,把 comments.css 的静态边框(0,1,0)、hover 变深
(0,2,0)、focus 变主色(0,2,0)全部压死。也就是说 3.2.20 重做
以来,正文框的边框色、圆角、浅底、悬停变色、聚焦变色**全部
从未真正上线**,用户看到的一直是基础主题的 #eee / 3px / 灰底,
聚焦时只有一圈很淡的外环是主色的。
修法:textarea 的静态 / hover / focus 三态都改用同样的
0,6,0 选择器收编(本表依赖 kratos 后加载,每条稳定压过)。
不去改 kratos.css —— 那是基础主题,改它等于放弃覆盖层架构。
姓名/邮箱/网址那排 input 不受影响(kratos.css 没对它们写边框),
聚焦本来就是主色。
3. 新增线上判据并先证明它能红
④ 引导线颜色 = 主色(动态取 .comment-submit 背景色做参照,
不写死 RGB,主色一改判据自动跟);
⑧ 真实点击姓名框与正文框、等 0.16s 过渡结束后量边框色。
部署前在 3.2.22 线上跑:④⑧ 正确变红、几何项依旧全绿——
判据有效,不是空转。
只改样式表与版本号,评论的取数、分页、回复、加载更多逻辑未动。
|
2026-10-24 20:49:41 +08:00 |
|
laoyang
|
420605f462
|
3.2.22 评论区:引导线跟着右移到正文左边缘,锚点高亮改回黄色
用户对 3.2.21 的反馈:
「回复的内容和上面评论的正文是对齐了,但是回复内容左侧有一条蓝色的线
还是没有对齐。那条蓝色的线也要向右移,让那条线和上面评论的内容正文对齐。
如果是为了突出显示打开文章时的锚点,那还是用黄色吧。」
1. 引导线右移
原来线写在容器的 border-left 上。边框只能贴在盒子外沿,也就是父评论的
最左边(父头像那一列),而子回复整块已经右移到父正文的位置了,中间
空出 55px,线与内容脱节。
现在改成绝对定位的 ::before,位置由缩进算出:
left: calc(var(--yq-indent) - var(--yq-guide-w))
线的右边缘正好压住正文左边缘。断点里改缩进,线自动跟着走,
不需要另写一遍。三档屏幕实测误差 0px。
取右边缘对齐而不是左边缘,是因为线宽 2px:若左边缘对齐,线会压住
子头像左侧(头像有一圈 1px 描边,压上去发脏)。
2. 缩进值 53 → 55(连带 44→46、40→42)
旧值扣掉了 2px,因为那时线是 border,而 padding 从边框内侧算起。
线不再占布局后,那 2px 不该再扣,否则子回复整体左移 2px。
缩进回到最朴素的形式:头像宽 + 列间距。以后改这一个数,
线和内容一起动,不会再出现「只对一半」。
3. 锚点高亮改回暖黄 #fffbe8
3.2.21 改成浅蓝的理由是「全站只有待审核用暖色」。但用户指出它
是用于突出显示锚点,该用黄色。这个判断是对的:锚点高亮需要在一整片
蓝白里「跳出来」,浅蓝与引导线、按钮描边同色系,反而被当成背景噪音。
与「刚提交」的分工因此清楚:
:target → 暖黄(一次性,指路)
is-new → 浅蓝(即时反馈,我刚发的)
同页出现也能分辨。
深层(第三个 children 容器起)仍然不画线,改用 content: none 关掉,
而不是以前的 border-left-color: transparent —— 伪元素没有"透明的线"
这种语义,直接不生成更干净。
只改样式表与版本号,评论的取数、分页、回复、加载更多逻辑未动。
|
2026-10-24 20:12:23 +08:00 |
|
laoyang
|
5cb42f1716
|
3.2.21 评论区:去掉多余的装饰,把回复对齐到正文
用户在 3.2.20 上线后提了 5 条,逐条对应:
1. 去掉「评论」左侧的蓝色竖条。它本身是多余的视觉噪音,
而且逼着标题让出 12px 左内边距,让标题和下方评论正文对不齐。
连带断点里为它让路的 11px 一起删掉。
2. 去掉标题下方的灰色横线。它和评论容器自己的上边框只隔十几像素,
两条平行线挨得太近,像是重复画了一次。
3. 回复内容对齐到父评论正文的左边缘。上一版缩进 18px 是拍脑袋定的,
结果子回复正文比父正文又右偏 20px,左边缘错开一小截。
这一版按布局算:父评论一行是「头像 42 + 列间距 13 + 正文」,
子容器是父 li 的直接子元素、从父 li 左边缘起算,自身还有 2px 引导线,
而 padding-left 从边框内侧算起,所以
缩进 = 42 + 13 − 2 = 53px
窄屏同步为 36+10−2=44、32+10−2=40。实测四个视口误差均为 0。
缩进改存成 CSS 变量,在 .comments 作用域定义、断点里重新赋值,
避免三处写死改一漏二。
同时收住深层累加:padding-left 逐层叠加,53×3=159px 会把
390px 屏的正文切成竖条。第三层起换用更小的缩进并去掉引导线,
三层累计 136px 仍有递进感。
4. 回复左侧的引导线换成主题浅蓝 #cfe6fb,与「写下你的评论」
按钮的描边同一个色系;原来的灰蓝在一片灰白里发闷。
5. 统一高亮底色。原来「有些回复有黄色背景、有些没有」并不是随机:
暖黄只出现在 :target 命中的那一条,也就是链接末尾带了
#comment-1234 锚点的时候(从评论通知邮件点进来就是这种)。
正常打开文章一条都不黄。加上刚发出的 is-new 原本也是暖黄,
两个黄叠在同一页面上更难分辨。
现在两者统一为 --yq-primary-soft(#eaf6ff)。徽标里的
「待审核」暖色保留 —— 那是状态色,需要区分度。
只改样式表与版本号,评论的取数、分页、回复、加载更多逻辑未动。
|
2026-10-24 19:38:08 +08:00 |
|
laoyang
|
b97f265142
|
feat(comments): 评论区样式重做,修掉三处可读性不达标与窄屏头像溢出(3.2.20)
用无头浏览器把评论区的计算样式逐条量出来之后,确认「不好看」里有三处
是硬指标不达标,不是主观审美:
· 时间戳 #a4abb5 对白底对比度 2.32 → 4.83(WCAG AA 正文需 ≥4.5)
· 回复按钮 #8a9099 对比度 3.22 → 6.0 (可点元素同样需 ≥4.5)
· 顶层分隔线 #f5f6f8 对白底 ≈1.04 → 1.17(可见,此前等于没有分隔)
顺带修掉一个只在窄屏出现的真 bug:768px 断点里 avatar 的 width 组
只写了容器与占位符、漏了 img,容器缩到 36px 而图片仍是 42px,
溢出 6px 压在正文上。400px 断点同类问题一并补齐。
这个 bug 之所以能活过多个版本,是因为几何探针量的是容器宽度 ——
容器确实等于 36,98 条断言全绿。已在探针里新增 3 条直接量 img 的断言,
并在反向验证里加入对应的注入用例。
其余为层次与细节调整:
· 新增 .comments 作用域内的局部设计变量层(--yq-*),信息性颜色带兜底
· 标题加主色竖条;外框圆角 4→6、阴影对齐整站 .widget 的档位
· 头像边框改外描边,不占布局,直径保持精确 42px
· 缩进 20→18px、引导线加深;正文 14→14.5px、行高 1.8
· 新增 :target 高亮与 prefers-reduced-motion 分支
保持不变的 DOM 契约(已在文件头注明):.yang-comment 仍为 grid、
子容器缩进仍只加 padding-left、头像断点节奏仍为 42/36/32。
验证:verify_all.sh 30 组全通过;本地几何 107/0、反向注入 25/0、
AJAX 34/0;线上四视口几何 52/0;线上实测时间 4.83、按钮 6.0、分隔线 1.17。
|
2026-10-24 12:16:30 +08:00 |
|
laoyang
|
460abcdb97
|
3.2.19: 修复关闭 wpDiscuz 后评论列表整段不显示
现象:#5264 等文章标题「评论(4)」与输入框都在,但 <ol> 内 0 字节。
只有单页评论的文章必现,多页文章反而正常。
根因:3.2.18 给 wp_list_comments() 传了 page=1 / per_page=10,
进入核心的"独立查询"分支。当这两个值与核心自己推导的
current_cpage / comments_per_page 恰好相等时,内层 if 不成立,
$_comments 一次都没被赋值(源码注释说会回退到 $wp_query->comments,
但那句注释下面没有回退代码),结果 paged_walk(null) 返回空串。
修复:
- 新增 yang_comment_page():按顶层评论分页 + get_children() 展平子孙。
按顶层分页可避免父子被切到两页产生孤儿回复。
- comments.php 把结果当第二参数显式传给 wp_list_comments,
走 null !== $comments 分支,绕开有问题的推导。
连带修掉的三处:
- Walker_Comment::end_el() 总会补 </li>,callback 再写一个导致
</li></li>,子容器被挤成父评论的兄弟节点、CSS 栅格失效。
yang_comment_html() 新增 $close_li,列表路径交给 walker 闭合。
- 核心 start_lvl() 固定输出 class="children",而样式表按
.yang-children 写,类名不符导致缩进/栅格全失效。
新增 Yang_Walker_Comment 覆盖 start_lvl()。
- AJAX「加载更多」原先只取顶层、只渲染顶层,吞掉全部子回复
(实测某篇 10 条顶层名下 12 条回复丢失)。改为走同一 walker,
has_more 也改成与首屏同口径的顶层计数。
|
2026-10-24 08:23:00 +08:00 |
|
laoyang
|
9c598fef48
|
3.2.18 补:评论提醒的三个开关在后台有界面
m_comment_notify / m_comment_approved_notify / m_keep_core_notify
原先只有代码在读、后台没有对应控件,用户想关也关不掉,只能改代码。
是 _static_315.php 的「模板读取的选项都有界面」这项契约检查抓出来的
(缺 m_comment_notify、m_comment_approved_notify 两项)。
默认值取 true,与代码里的 kratos_option(..., true) 一致。
|
2026-10-24 01:38:25 +08:00 |
|
laoyang
|
cbe0f2dc59
|
3.2.18: 清明按节气换算、主题内 [TOC] 目录、重建评论区替代 wpDiscuz
三项功能:
1. 清明不再用固定 04-04
清明是节气,日期在 4/4~4/6 之间浮动。硬编码 04-04 实测 1900-2100
共 201 年里只有 76 年是对的(约 38%)。改为内嵌逐年表换算真实日期,
超出 1900-2100 返回 null 明确放弃,不退化成近似值。
兼容 3.2.17 及以前勾过的 '04-04' 键,升级后勾选不会被静默丢弃。
2. 主题内实现 [TOC]
文章里写 [TOC],该位置输出本文目录,点击跳到对应标题。
在 the_content 优先级 9 处理(早于 wpautop 10 / do_shortcode 11),
保证"补 id"与"扫目录"用的是同一份文本,href 与 id 必然对得上。
嵌套用深度归一化,h1→h3 跳级不会算错层级。
scroll-margin-top 声明式让位,原生锚点/外部 #hash/后退全都生效。
3. 重建评论区
旧实现的问题:列表渲染与 AJAX 渲染各写一份 HTML,已经分叉
(AJAX 返回的新评论没有回复按钮);<li> 被 <div> 包着,嵌套回复错位。
现在统一到 yang_comment_html() 单一渲染函数,列表与 AJAX 只差一个
is-new 类。同时修掉三处真实缺陷:
· 评论项用 flex 会把 <ol class="yang-children"> 当成第三个 flex 项,
子评论跑到父评论右侧、正文被挤到 156px、页面横向溢出 75px —— 改 grid
· 成功提示在 exitReplyMode() 之前设置,被其末尾的 setStatus('','')
擦掉,用户提交成功却看不到反馈 —— 调整顺序
· 窄屏媒体查询打不过深层选择器(媒体查询不增加特异性),
嵌套头像反向变大 —— 补深层覆盖
与 wpDiscuz 解耦:邮件模块不再无条件 return false(那等于永久关掉
notify_postauthor 且不报错);插件在场时不加载主题评论资源,
避免双份表单互抢。
验证:PHP 语法 73/73;_static_qingming 36、_test_toc 53、
_test_comments 62、_test_comments_falsify 17、
_toc_geometry 121、_toc_geometry_falsify 9、
_comments_geometry 98、_comments_geometry_falsify 23,全部 0 失败。
线上端到端 29/0(真实文章渲染目录 + 真实评论数据渲染)。
|
2026-10-24 01:03:36 +08:00 |
|
laoyang
|
4beb09307c
|
3.2.17: 修复「保存设置时静默清空未提交的字段」
现象
在主题设置里填了「附加日期」(例如 10-23),过一阵发现它变回空了,
于是当天的哀悼效果不再出现,而其他设置都好好的,看不出哪里坏了。
根因
inc/options-framework/includes/class-options-framework-admin.php 的
validate_options() 里,对每个选项无条件取 $input[$id] 去 sanitize:
$clean[$id] = apply_filters('of_sanitize_'.$type, $input[$id], $option);
一旦某次提交里没有这个键(分组没渲染、字段被条件隐藏、表单中途出错只
提交了一半……),$input[$id] 是未定义索引 ⇒ null ⇒ sanitize_text_field()
返回空串 ⇒ 把用户存了很久的配置写成空。而且"清成空串"和"用户主动清空"
在库里完全一样,事后无法区分,只表现为"我明明填过,怎么没了"。
实测证据(线上 PHP 8.5.12,同一套用例)
修复前:3 通过 / 2 失败,失败项同时伴随
PHP Warning: Undefined array key "g_rip_extra" ... line 252
修复后:5 通过 / 0 失败
改法
- 循环外先取一次 get_option('kratos') 作 $saved_options(不在循环里查库)
- 提交了(array_key_exists($id,$input))才 sanitize
- 没提交则沿用 $saved_options 里的旧值;旧值也没有就不写这个键,
让运行时的默认值继续生效
- checkbox / multicheck 保持原语义:这两类"没提交"确实等于"没勾",
沿用旧值会导致开关关不掉。用 $is_toggle 单独判定,与"沿用旧值"互斥
为什么这个改动值得单独发一版
它不是界面问题,是**数据被静默改写**。凡是这种"用户没动、值却变了"的
缺陷,都会让人怀疑数据不可靠;而且每次后台保存都可能再触发一次。
验证
- 线上 _test_keep_old_value.php:5/5(修复前 3/5,用例 2 精确复现空串)
- 本地 _static_317.php:8/8,第 4 组自带注入(改成老写法必须变红)
- 全量流水线 verify_all.sh:rc=0
- 线上浏览器 _live315.py:纪念日分支 33/33、非纪念日分支 27/27
(判据改为「由配置侧独立算 expect_show,再与页面渲染对比」,
不再无条件断言"必须有灰度",也不再拿页面自身当预期值)
|
2026-10-23 23:18:51 +08:00 |
|
laoyang
|
0799268661
|
3.2.16: 删三项无用功能、选项重排为 10 组、修移动端提示条偏左
删除
- 微信二维码:配置项 s_wechat / 隐藏上传 s_wechat_url、footer 浮动块、
默认图注册表条目、CSS 规则、wechat.png
- Font Awesome:选项 g_fontawesome、theme-core 入队、58KB 样式、
assets/fonts/webfonts 全套字体(2.8MB,CSS 删后零引用)
- 多人模式:选项 multiusers、page-content / page-toolbar 两处分支
选项重排(9 组 → 10 组)
全站配置 / 顶部导航 / 首页配置 / 文章配置 / 站长信息 /
页脚配置 / 哀悼模式 / 收录与统计 / 邮件通知 / 自定义代码
哀悼 5 个字段从「首页配置」独立成「哀悼模式」——它不是首页专属功能。
g_404/g_chrome/g_animate/g_gravatar 等归「全站配置」,s_copyright 归「页脚配置」。
修复
- footer.php 版权行此前无条件硬编码,后台 s_copyright 填了不生效
- 附加日期命中时 yang_mourning_today_name() 返回空串,提示条退化成
只说「今天是纪念日」而不给名字
- 小屏哀悼提示条 padding 写成 `7px 40px 7px 12px`(右宽左窄给关闭按钮
让位),内容盒中心 181 vs 视口中心 195,整体偏左 14px。关闭按钮本就
absolute 不占内部流,改为左右等宽 + 缩小按钮。桌面端同类错误 3.2.14 已修,
此处漏了同一个断点。
加载顺序
theme-options.php 可能早于 theme-mourning.php / theme-custom-code.php 加载,
新增 yang_opt_* 四个 function_exists 包装,避免裸调用导致后台白屏。
验证:静态断言 62/0 · 后台面板 14/0 · 反向验证 7/7 与 44/0 ·
提示条 4 视口几何 20/0(注入旧 bug 精确复现 -14px)· 线上 3 视口 33/0
|
2026-10-23 22:56:10 +08:00 |
|
laoyang
|
96bbb3d1b5
|
v3.2.14 哀悼提示条改到整页最上方正中,只在纪念日当天出现
用户要求:哀悼说明「放在最上方的最中间,只在纪念日、哀悼模式开启时才显示,
其他时间不显示」。
改的过程中量出三个真问题:
1. 提示条与站点标题重叠 32px。提示条从 y=0 开始、高 40px,而导航栏
.k-nav 是 position:absolute; top:0、z-index 1030;提示条 z-index 9990
⇒ 直接盖在 Logo 上。CSS 每个字都没写错,就是叠在一起。
2. 内容偏左 38px。为了给右侧关闭按钮让位写了 padding-right:46px、
左侧只有 18px ⇒ 文字中心 602 vs 视口中心 640。"最中间"没做到。
3. 页脚有常驻的「纪念日哀悼说明」链接,一年 365 天都显示。平时打开
站点的人既没看到黑白也不知在哀悼什么,这个入口本身就是困惑源。
修法:
- 提示条占文档流第一行、width:100%,内容交给内层 .ymb-inner 居中,
左右 padding 等宽、关闭按钮 absolute 不占内部流 ⇒ 几何中心 = 视口中心。
- .k-nav 是绝对定位,body 的 padding 推不动它(这个原因单看代码想不到,
是量几何量出来的)⇒ 单独补一条 body.has-yang-mourning-bar .k-nav{top:var(--ymb-h)}。
偏移量由脚本读真实高度写入,窄屏换行后也准。
- 不用 fixed:那会滚动时顶部永远压一条深色带,内容从它下面穿过去。
- 移除页脚常驻入口与 wp_footer 注册,说明页入口只在提示条里。
非哀悼日输出 0 字节。
验证:
- run_tests2 163 条(新增 9 条位置契约)
- _check_hidden.php 19 条(非哀悼日 0 字节、每个纪念日都能触发)
- mourning_bar_geometry.py 28 条(线上真实页面实测 4 种视口:
顶部贴边、横跨整宽、居中误差 ≤1px、与标题和导航栏零重叠)
- falsify 44 条注入全被抓住(新增 9 条)
两个假绿当场被反面验证抓出并修掉:
- 判据照着 fixed 方案写,实现改成 relative 后 4 条断言集体假红
(教训:设计前提变了,判据必须跟着改)。
- 页脚断言用 ob_start 取输出,但函数是 return 不是 echo ⇒ $fl 恒空
⇒ 注入"返回一个链接"仍全绿(教训:否定式断言在空输出上恒真)。
|
2026-10-23 20:43:25 +08:00 |
|
laoyang
|
ef9a58dc51
|
v3.2.13 图片选项全部自带默认图,新装站点不再出现空洞/破图
根本原因:kratos_option() 运行时只读数据库,`std` 仅在后台表单预填和
「恢复默认」时生效,前台根本读不到。因此新装站点所有图片项都是空值。
另外 isset() 挡不住「用户清空」——of_sanitize_upload() 对空输入返回 '',
键存在且值为空串,调用方传的兜底值被绕过。
同时不能把绝对 URL 写进 std:它会随保存一起写库,主题目录改名/换 CDN/
迁站后永久 404。本站 s_wechat_url / g_donate_alipay / g_donate_wechat
三项就指向已删除的 kratos-master 目录,一直在破图(这三项是 hidden 字段,
后台界面上看不到,所以坏了很久没人发现)。
做法:
- 新增 inc/theme-option-defaults.php,图片默认值注册表(11 项),
默认图一律运行时给出,不写数据库。
- yang_option_image() 三层判断:空/纯空白 → 默认图;指向已不存在的主题
目录 → 默认图;文件不存在则不输出地址(避免换来一个 404)。
- 陈旧判断用 get_template()(主题 slug)而非 basename(目录名),避免本地
开发目录名与 slug 不一致时误伤当前主题自己的图。
- 模板统一走取值函数:header(logo/favicon/banner)、footer(微信码/国旗)、
404、index、article 缩略图、widgets 头像、page-toolbar、theme-core 赞赏码。
- 国旗与 favicon 的渲染抽成 yang_flag_block() / yang_favicon_tag(),
空值时不输出空 src / 空 href。
- 后台 9 处 std 改为 yang_options_default_img(),与前台默认图同源;
该包装函数自带 function_exists 防护,单独加载 theme-options.php 不会 Fatal。
新增图片(本地生成,合计 291KB):default-404 / default-share /
default-empty / default-avatar / default-banner / default-flag / favicon.ico。
favicon 为 WordPress 风格自绘(品牌蓝圆底 + 白色 W),非站长个人专用那张。
验证:run_tests 146 条(新增第 4 组图片默认值 22+ 条);falsify 35 条注入
全部能被抓住(新增 10 条);verify_all.sh 14 组全绿。
|
2026-10-23 19:50:07 +08:00 |
|
laoyang
|
704a9346e1
|
3.2.12 紧急修复:点「测试邮件」引发无限递归、内存耗尽
3.2.11 引入的事故。线上表现:
Allowed memory size of 268435456 bytes exhausted
in .../wp-includes/class-wp-block-parser.php on line 91
前台首页、后台登录页都正常,只有点那个按钮才炸。
根因不在区块解析器 —— 那只是递归链最后执行到的一行,看它会完全跑偏。
validate_options() 是 register_setting() 注册的 sanitize_callback,
WP 把它挂在 `sanitize_option_kratos` 过滤器上(wp-includes/option.php:3073)。
3.2.11 为了让"刚改的 SMTP 配置立刻生效",在这个回调里调了
update_option('kratos', $clean)。而 update_option() 内部**会再跑一遍**
同名过滤器 ⇒ 又调回 validate_options ⇒ 再 update_option ⇒ 无限递归。
每层都在复制 $clean 派生的大数组,直到吃满 256M。
本地实测确认递归存在:回调被调用 3 次、嵌套深度 2。
改法:不写库,改用一次性覆盖层。
- 分支里把 $clean 放进 $GLOBALS['__yang_options_override']
- kratos_option() 优先读它,取不到再读库
- 效果等价(点测试用的就是刚填的值),但不触发任何过滤器,不可能递归
- 覆盖层是本请求内的全局,不落库、不跨请求残留
assertions: run_tests 104 → 110
- 分支里**没有** update_option(这条是红线,直接对应本次事故)
- 分支里有覆盖层
- 覆盖层真生效:kratos_option 优先返回新值
- 覆盖层未命中的键仍读库(不能把别的配置一起抹掉)
- 撤掉覆盖层回到库值(不跨请求残留)
- 桩与产品代码一致:直接读 autoload.php 确认它也认覆盖层
(桩比产品温和是这次漏检的根源之一 —— 桩里 update_option 不挂
sanitize 过滤器,所以注入递归 bug 后 harness 根本不崩)
顺带修了两个测试基础设施问题:
1. wp_stub.php 的 kratos_option 与真实实现语义不一致。已同步,
并加断言防止两边再次漂移(否则"测的是桩、不是产品代码")。
2. falsify 的 rebuild() 没把被 file_get_contents 读的 autoload.php
复制进副本 ⇒ 新断言读不到文件 ⇒ 基线假红。
falsify 24 → 25:注入「分支里调 update_option」能抓住(靠静态红线,
不靠跑崩)、注入「去掉覆盖层」能抓住、注入「退回裸 wp_mail」能抓住。
全量 14 组全绿;run_tests 110 / run_tests2 154 / falsify 25。
|
2026-10-23 18:49:06 +08:00 |
|
laoyang
|
9a053169e9
|
3.2.11 后台「测试邮件」改走主题模板,并修发信早于落库
两个问题叠在一起,都不报错,只是「测了等于没测」:
1. 按钮发的是纯文本
validate_options() 里 isset($_POST['sendmail']) 那个分支只有一句裸
wp_mail(),正文是「恭喜您 SMTP 邮件服务配置成功!」。于是点测试收到的
东西和真实评论通知(HTML 模板)完全不同,没法用它验收版式。
主题里 yang_mail_send_test() 本来就是干这件事的,但全仓库零调用点 ——
注释写着「后台测试按钮走这条」,实际上从没接上线。
2. 发信发生在配置落库之前
validate_options() 是 register_setting 的回调,返回值之后才由 WP
写进数据库。在分支里直接发信,主题配 PHPMailer 时读 kratos_option()
拿到的还是上一次保存的主机/账号/密码。刚改完 SMTP 密码点测试,
测的是旧密码,表现成「明明填对了却发不出去」。
改法:
- 分支里先 update_option('kratos', $clean) 再调 yang_send_test_mail()
- yang_send_test_mail() 优先调主题的 yang_mail_send_test();
主题被换掉时退回原来的纯文本,不能因此白屏
- send_mail_notice() 从「一律发送完成」改成三态:WP_Error / wp_mail
返回 false / 成功,前两者用 error 样式。原来密码错了也显示成功,
用户会去邮箱里干等。
assertions: run_tests 95 → 104 条(新增 2k 组 8 条 + 1 条)
- sendmail 分支确实调用主题发信函数、确实先落库
- 落库位置在发信位置之前(判顺序,不判有没有)
- 分支里没有裸 wp_mail
- 发信函数对主题缺失做了兜底
- 提示语分三态,两类失败都用 error 样式
falsify 22 → 24 条:
- 注入「先发信后落库」⇒ 变红(这条一开始是假绿:判据比的是注释里
那两个词谁先出现,注释里 update_option 和函数名都在,恒真。
改成先剥注释再比代码里的位置)
- 注入「退回裸 wp_mail」⇒ 变红
顺带修了 falsify 的一个基础设施坑:options-framework 那批文件在工作区
是 CRLF,锚点里写 \n 对多行内容恒不匹配,报的是「命中 0 次」,
很像锚点选错了。inject() 现在先试原样、不中再把 \n 换 \r\n 重试一次。
全量 12 组 / 506 条断言全绿。
|
2026-10-23 18:28:53 +08:00 |
|
laoyang
|
6f50533e8e
|
3.2.10 修复邮件页脚显示字面量 ©
邮件正文会被 yang_mail_escape_for_wpdiscuz() 无差别地把 & 加倍,用来抵消
wpDiscuz 在过滤器之后又做的那一次 html_entity_decode —— 不加倍的话,访客
评论里的 <script> 会在解码时还原成活标签。
但这个替换是全局的,连模板骨架里手写的字符实体也一起被加倍。于是
© 变成 &copy;,能否正常显示完全取决于后面那次解码有没有执行:
走了解码 → 还原成实体 → 浏览器显示 ©;没走 → 只解析一次 → 收件人看到
字面量 ©。用户遇到的正是后者。
修法不是在转义函数上开口子(那会重新打开脚本注入口),而是让骨架不再
依赖实体:版权符号与 3px 分隔条的不换行空格一律输出 UTF-8 字符本身。
整封邮件本就声明 charset=UTF-8,这样无论被解码几次都不会变形。
用户数据(站名、标题、URL)仍走 esc_html/esc_url —— 那类 & 的转义是
必要的,加倍后能由那次解码还原,属于应当保留的一类。
assertions: run_tests 89 → 95 条(新增 1e 组 6 条)
- 骨架里零手写实体(0 次 decode 也不露原文)
- 经 0~5 次 html_entity_decode 始终是 ©、从不露实体原文
- URL/站名里的 & 仍被转义(不能把 esc_html 一起废掉)
- 分隔条是 U+00A0 裸字符,过过滤器后仍是裸字符
falsify 20 → 22 条,两处注入(页脚退回实体、分隔条退回实体)都能抓住。
全量流水线 14 组 / 465 条断言全绿。
|
2026-10-23 17:29:37 +08:00 |
|
laoyang
|
99e5a1ec0a
|
版本号 3.2.8 -> 3.2.9(邮件版式重做:白底 + 深色头部 + 暖色点缀)
用户对 3.2.8 的"整封同色系"给出了明确否定,并说明要什么:
背景要白的;顶部深蓝底白字;「新回复」标签不能用蓝色,
要黄或红、与头部有区分度;「查看完整讨论」按钮红底白字。
版式骨架改成了「白底 + 深头 + 暖色点缀」:
- page_bg 由主色浅底改为纯白;
- 头部改为主色的深版(deepen_to 压到白字 7:1),白字达 AAA;
- 标签用暖色族,具体哪个由"与头部色相距离"挑(pick_warm),
主色偏红时金黄才撞得开;
- 按钮固定红 #c62828 + 白字(5.62:1);
- 新回复竖线改用同一套暖色,与标签呼应。
顺带修掉两个真 bug:
- 标题色的循环从最暗档开始扫,实际取到的是最暗档(亮蓝下几乎全黑),
注释还写着"取最亮的达标档",方向与实现相反。改为从 0.90 往下扫,
并加色度下限(不低于种子 70%),避免浅主色被压成黑色;
- 纯中性灰主色下 21px 标题取到 #717171 中灰,发虚。改为深墨色。
测试同步重写(旧判据服务的是"整封同色系",在白底版式下必红):
- mail_color_audit.py 改为守四条性质:白底、头部深且白字达标、
标签暖色且撞开、按钮红底白字;定位靠结构不靠猜颜色;
- pixel_color_audit.py 改为像素级守同一组性质:白像素 ≥45%、
顶部横跨的深色带、带内白字像素、带内撞色像素;
撞色必须判「带内」(判全图会把底部红按钮算进来,没牙齿);
- falsify_color.py 四处注入全部退回旧做法,两层都能抓住;
- run_tests/run_tests2/visual_audit/mail_design_audit/mail_shot 里
写死 #24292e 或"按钮跟主色"的过期断言一并改成本质性质。
verify_all.sh 14 组 / 463 条断言全绿(上一轮 320 条)。
|
2026-10-23 17:08:55 +08:00 |
|
laoyang
|
9c48ba56eb
|
版本号 3.2.7 -> 3.2.8(邮件配色整封同色系化 + 对比度修复)
邮件模板原来只有品牌条一个色块,其余大面积底色全是中性灰,
主色稍朴素(主题默认 #24292e)整封信看起来就是黑白的。
- 新增 yang_mail_palette():底色/描边/强调文字全部从主色同色系派生
- 新增 yang_mail_chroma()/hsl()/hsl_hex()/ensure_chroma()/has_hue()
- 修复深浅判定:白字对比度 = 黑字对比度的交叉点在 L≈0.179,不是 0.5。
用 0.5 会把亮蓝 #00a2ff(L=0.331)判成深色配白字,只有 2.76:1
- 新增 yang_mail_fg_on():在白与深之间取对比度更高者,
中灰 #808080 上从 4.41 提到 5.32(AA 达标)
- 修复:与白混合是同比压缩色度,0.94 档压 16 倍 ⇒ 近中性主色
派生出的浅底只剩 0.002,物理不可见。加彩度兜底,按各自亮度补足
- 修复:页脚改浅底后 12px 说明文字降到 4.26:1,muted 改为按背景推导
- 品牌条/按钮仍严格用用户填的原色,补彩度只作用于派生色
|
2026-10-23 16:13:48 +08:00 |
|
laoyang
|
269364bed4
|
版本号 3.2.6 -> 3.2.7(纪念日全部默认不勾 + 彩色说明页 + 邮件彩色 + 双引用块)
哀悼默认值全部改为不勾选
- 九个纪念日的 default 全改成 0。之前法定那三个是 1,导致全新安装的站点
一到日子就自己变黑白,而站主根本没同意过这件事。
- 后台多选组件的 std 全为 0,打开设置页一个都不勾。
- 「从未保存过」与「保存过但全不勾」两条路径都返回空数组,
不能因为结果为空就回退到默认 —— 那等于用户取消勾选无效。
新增彩色说明页(inc/theme-mourning-page.php + pages/page-mourning.php)
- 站点变黑白后必须有个地方解释清楚,否则访客只看到一片灰、以为站点坏了。
- 三处入口:顶部提示条(wp_body_open,首屏可见)、页脚常驻链接、
[yang_mourning] 短代码。
- 页面内容由真实配置驱动,不是写死的文案:没勾的日期不会出现,
今天在列表里带「今天」标记,一个都没勾时明说「不会生效」。
- 页面必须保持彩色,否则解释「为什么是灰的」的页面自己也是灰的。
取消灰度做了两层:filter:none 与 grayscale(0%) 都写,且带 !important;
优先级放在 wp_head 的 2,晚于灰度样式的 1。
- 路由走 parse_request 优先级 5 而非 template_redirect:
避免先走一遍 404 判定与 404 模板。带子目录安装时先剥掉前缀再比对。
- 不抢占用户自己建的页面:如果站里已有同名 slug 的页面就放手。
否则用户会觉得「我建的页面坏了」,且没有任何提示。
邮件不受影响,且回复邮件同时给出双方内容
- 邮件是独立 HTML,不走 wp_head,结构上就不可能被灰度,已断言验证。
- theme-mail-template 的单个 quote 改成 quotes 列表,一个模板同时渲染
「被回复的那条」与「新的回复」,最后一块用主色高亮(回复是重点),
前面的用弱化色。只有一块时退化为主色,与旧行为一致。
- 通过 wpdiscuz_email_content 重建整封邮件,不需要改插件。
审核通过通知用 wpdiscuz_comment_approved_email_content 走同一模板,
但它只给 post_id、不给评论对象,能放的内容受此限制。
两个必须记下来的坑
- wpDiscuz 在过滤器之后还会做一次 html_entity_decode 再发信。
esc_html 的输出会被那一步还原成真标签,评论里的 <script> 直接生效。
所以返回前先把 & 编码一次。本地 stub 没有这一步,测试看不出来。
- header.php 从来没有调用 wp_body_open,挂在上面的东西全部静默失效。
不调用不会报错,只会「什么都没发生」。已补在 <body> 之后。
yang_mail_text 保留换行
- 原来用 wp_strip_all_tags($t, true),第二个参数 remove_breaks 会把换行
压成空格;而 strip_tags 遇到 <br> 是直接删掉不补换行的。
结果整条评论被拍成一行,模板里的 nl2br 成了死代码。
- 改为先把块级边界降级成 \n,再去标签(remove_breaks=false)。
收行内空白用 [^\S\n] 而不是 [ \t\f\v]:PCRE 的 \v 是垂直空白类,
会把 \n 一起匹配掉。
说明页对比度修正(WCAG AA)
- 金色 #b8860b 在白底只有 3.06:1,当装饰色没问题,当 12-15px 文字偏虚。
拆成 --ym-gold(装饰)与 --ym-gold-text(#946600,4.75:1,仍属金色)。
- 分组小标题 #9aa5b1 只有 2.36:1,改用 #5f6b78(5.12:1)。
- 「今天」标签白字压金底 3.25:1,改用 #8a6100(5.54:1)。
|
2026-10-23 14:43:47 +08:00 |
|
laoyang
|
37c52948c4
|
版本号 3.2.5 -> 3.2.6(统一邮件出口 + 邮件模板 + 纪念日自动哀悼 + 去掉广告位)
评论邮件重复的根因是发信源不止一个,分两层处理:一层管「谁发」,一层管「发几封」。
只做前者的话邮件照样重复,只是都从同一个 SMTP 出去。
邮件治理(新增 inc/theme-mail.php)
- phpmailer_init 优先级 9999 接管通道,保证主题配置优先于其它 SMTP 插件;
接管前先重置可能被别的插件设过的字段,避免残留半套配置。
- From 强制等于 SMTP 登录账号。QQ / 163 会以「发件人不符」拒收。
- wp_mail 优先级 99 统一 From 与 Content-Type;调用方自带的 From 与
Content-Type 被剥离后重建,自定义 header 保留。
- wp_mail 优先级 100 做短期去重:同一收件人 + 同主题 + 同正文,90 秒内只发一次。
用 transient 是因为评论通知跨请求(wpDiscuz 走 AJAX、审核通知走后台请求),
静态变量跨不过去。90 秒足够宽(一次提交引发的通知都在同一秒)也足够窄
(手动重发通常隔几分钟,不该被吞)。
- 被去重的那次把 to 置空并由 pre_wp_mail 返回 true,而不是 return null:
返回 null 会改变 wp_mail 返回值语义,有的插件据此判定失败并重试,反而更多封。
- 用 filter 关掉核心的 notify_postauthor / notify_moderator,不改进数据库里的
comments_notify / moderation_notify。改用户设置属于越权,而且用户下次进后台
一勾又回来了;filter 是可逆的。只拦通知,不影响评论审核流程。
- 线上站点 m_smtp=1 但 m_host 为空时判定为「未就绪」并完全放手。只勾开关不填
主机时若强行接管,所有邮件会静默失败,比回退到 mail() 更糟。
邮件模板(新增 inc/theme-mail-template.php)
- 表格布局 + 全内联样式:Outlook 2007-2019 用 Word 引擎,不认 flex / grid;
Gmail 会剥掉 <head> 里的 style 块(含 media query)。
- 不用外部图片、不用 CSS 变量、不用 ::before。按钮的 padding 放在 td 上,
因为 Outlook 忽略 a 的 padding。
- 宽度用混合写法:HTML 属性 width=600 给 Outlook,CSS 的
width:100% + max-width:600px 让手机窄屏自动收窄。只写死 600px 会让手机横向滚动。
- badge 背景用预算的实色而非 rgba。Outlook 不支持 rgba,会当成非法值丢掉,
半透明背景变成全透明,badge 直接消失。
- 主色下沿那条 3px 深色带必须 height 同时写成 HTML 属性和 CSS,
否则 Outlook 把它撑成一行文字高,比例就毁了。
- 用 WCAG 相对亮度判断主色深浅来决定叠在上面的文字用白还是深色。
简单取平均会把 #00ff00 判成中等亮度(人眼对绿最敏感、蓝最不敏感)。
- 次要文字统一调到 #5f6b78。原来的浅灰在白底上只有 1.84:1,看着"高级"但读不出来;
现在最低 4.62:1,全部过 AA。
theme-smtp.php
- 删掉 comment_notify / comment_approved 两个钩子及其硬编码模板。这两个钩子与
wpDiscuz 的 notifyCommentSubscribers 完全重复,是重复收信的直接来源。
纪念日自动哀悼(新增 inc/theme-mourning.php)
- 法定纪念日三个默认勾选:09-03 抗战胜利纪念日、09-30 烈士纪念日、
12-13 南京大屠杀死难者国家公祭日。依据是 2014 年全国人大常委会两次决定,
已在代码注释里记下来源。
- 民间通行做法(05-12、09-18 等)默认不勾,由用户自己决定。替用户做价值判断不合适。
- 一次性全国哀悼日(2020-04-04 等)支持 YYYY-MM-DD 写法,只在当年生效。
不能当周年循环,否则明年 4 月 4 日又自动变黑白。
- 按站点时区判断(current_time),不按服务器时区。服务器在 UTC 时会晚 8 小时生效。
- 只挂 wp_head 而不限定 is_home():老代码限定首页,详情页不黑白,那是 bug。
- 打印时取消灰度,否则打印出来是灰的。
- 保留旧的 g_rip 手动常开开关。不能因为升级把原本黑白中的站点变回彩色。
options-framework
- 补 multicheck 的渲染分支与 of_sanitize_multicheck。框架原来只在保存侧处理了
这个类型,没有渲染分支,所以这个类型此前根本不可用。
- 值存成 array(键 => 1|0) 而不是「勾选了的键组成的列表」:列表无法区分
「用户明确全部取消」和「从未配置」,会导致取消勾选后静默回退到默认值。
- 读取侧用 array_key_exists 判断是否保存过,而不是判断值是否为空。
- 配套补上 of-multicheck 样式,没有样式时 9 个选项会挤成一行流水文字。
去掉广告配置
- 删除主题选项里的广告配置段,以及 single.php 里上下两处广告位输出。
验证
- 全主题 68 个 PHP 文件语法检查通过。
- 行为测试 82 条、multicheck 闭环 25 条、邮件几何 15 条、设计审计 23 条。
- 反向验证 16 个注入 bug 全部被断言抓住(去重失效、不写标记、to 未清空、
From 未剥离、亮度算法退化、时区改用 date()、全不勾回退默认等)。
- 邮件最低文字对比度 4.62:1;320px 视口下卡片自动收窄到 296px 不溢出。
|
2026-10-23 13:55:23 +08:00 |
|
laoyang
|
4f0d19fb91
|
版本号 3.2.4 -> 3.2.5(修 Prism 报错,项目地址改为自建 Git)
## 修 Prism is not defined
首页控制台报 `Uncaught ReferenceError: Prism is not defined`,来源是
wp-editormd 插件(不是本主题):它在 Prism 脚本加载之前就无条件执行了
`Prism.plugins.autoloader.languages_path = "..."`。
线上页面里的真实顺序:
第 668 行 该赋值语句
第 695 行 prism-core.min.js
第 696 行 prism-autoloader.min.js
在 `wp_head` 优先级 0 垫一个最小空壳 `window.Prism`(含 plugins.autoloader
这条访问链路),让那句赋值有地方可写。已核对 prism-core.min.js 源码:它开头
只读 `u.Prism.manual` 与 `u.Prism.disableWorkerMessageHandler`,结尾
`global.Prism = Prism` 整体覆盖 —— 空壳必定被真实对象替换,不承担持久职责,
只为吸收那条过早的赋值。已用 jsdom 验证:去掉空壳重现原始报错,加上后消失,
且之后 `Prism.highlightAll` 仍可正常调用。
## 项目地址统一到 code.yangyq.net
原作者署名、原主题地址、README、语言包、JS 里的控制台横幅全部换成自建仓库地址。
LICENSE 文件保持原样不动(MIT 要求保留原始版权声明)。
## CDN 分支改成拿不到就回退本地
原来的 `g_cdn` 分支指向 `cdn.jsdelivr.net/gh/vtrois/kratos`,本仓库在自建
Gitea 上,jsDelivr 只认 github/npm/wordpress,实测该地址 404。现改为仅在
`YANG_THEME_CDN_REPO` 常量存在时才走 CDN,否则一律用
`get_template_directory_uri()`。留一个 404 的地址比留空更危险:页面能开、
样式全丢,极难排查。
## 顺带
- `.gitattributes` 补 `*.mo binary`:原来是靠"含 NUL"启发式判定,不是契约
- `update.json` 的版本号跟进到 3.2.5
- `kratos.min.js` / `kratos.min.css` 用压缩器重新生成,不手工改产物
|
2026-10-23 12:55:41 +08:00 |
|
laoyang
|
b36f4b184a
|
版本号 3.2.3 -> 3.2.4(修复升级时二进制资源被行尾归一化破坏)
|
2026-10-23 12:27:05 +08:00 |
|
laoyang
|
1680ece73e
|
换主题缩略图,并把二进制文件锁进 .gitattributes
缩略图:原图是 2740x1424 的宽屏截图,主题缩略图要求 1200x900(4:3),
按中心裁切后缩放到 1200x900。裁掉的是左右各 421px。
另加 .gitattributes:本仓库 core.autocrlf=true,工作区 CRLF、仓库 LF。
这条规则对源码是对的,但二进制文件里天然含 0x0D 0x0A,一旦被当成文本
转换就当场损坏 —— PNG 会变成 file 判不出的 "data",getimagesize() 返回
false,WordPress 后台主题缩略图直接不显示。显式标 binary 后 git 不再动它们。
这次的损坏其实不是 git 干的,是打包脚本无差别 sed 's/\r$//' 造成的
(screenshot.png 少 3 个字节即 3 个 \r),脚本已同步修复。
|
2026-10-23 11:17:55 +08:00 |
|
laoyang
|
16ea692e5d
|
版本号 3.2.2 -> 3.2.3(用于验证自动更新全链路)
|
2026-10-23 10:47:23 +08:00 |
|
laoyang
|
eaabc7717f
|
把自定义样式从主题目录搬到主题选项,升级不再丢
原来主题里有个 custom/ 目录(custom.css / custom.js / custom.php),
是给用户随手加样式的入口。问题是它位于主题目录内,而 WordPress 升级
主题的做法是把整个主题目录删掉再解压(class-theme-upgrader.php 里
clear_destination = true,随后 delete_old_theme() 递归删除),
所以每次升级这些文件都会丢。custom.php 还会被 require,
等于在主题里留了一个执行任意 PHP 的口子。
改成后台选项:
- 主题选项新增「自定义代码」组,含自定义 CSS / 自定义 JS 两个输入框
- 内容存在数据库的 kratos 选项里,与主题文件解耦,升级、重装都不影响
- 分别在主题样式和主题脚本之后加载,优先级最高
为此新增了 code 字段类型,没有复用 textarea:textarea 的 sanitizer
走 wp_kses(),CSS 的子选择器 > 和 JS 的 < 会被当成 HTML 标签剥掉,
样式会静默失效。code 类型只去掉 PHP 标签、打断 </style> 和 </script>,
其余原样保留。渲染时也不走公共的 stripslashes —— options.php 已经
wp_unslash 过,再剥一层会把 CSS 的 \f101、JS 的 \n 啃坏。
自定义 CSS 的默认值就是原来 custom.css 的内容,逐字节一致(354 字节),
所以升级瞬间站点外观没有任何变化。清空输入框保存即可彻底去掉。
验证:真实 WP 7.1.3 / PHP 8.5.12 环境 20 条断言全过(含 DB 往返、
子选择器 / 反斜杠保真、危险内容过滤),生产选项指纹前后一致;
反向注入「默认值改空」能立刻被抓到。
|
2026-10-23 10:35:56 +08:00 |
|
laoyang
|
8a55457210
|
版本号升到 3.2.1
自动升级靠 style.css 的 Version 判断有没有新版本,不升号线上永远收不到更新。
同时 THEME_VERSION 是静态资源 ?ver= 的缓存键,升号后浏览器会重新拉取,
避免用户拿着旧 CSS 看不到本次的样式修复。
|
2026-10-23 09:06:35 +08:00 |
|
laoyang
|
ffbe1b9b73
|
修复压缩产物丢失的代码块样式,并把主题更新源收进主站
1. kratos.min.css 补回 6 条代码块样式
上次为了注释掉 max-height 把单行压缩版手工展开,展开时整组丢了
.content 下的 pre / code / kbd / pre code / pre ol / pre li,
而未压缩的 kratos.css 里它们都在,源与产物从此不一致。
表现是行内 code 与 kbd 没有样式(代码块因插件主题覆盖才没露馅)。
改为用 clean-css 从 kratos.css 重新生成,不再手工维护产物。
2. 删掉 max-height / text-overflow 两条声明,而不是注释掉
注释会被压缩器剥掉,产物里等于没改过。
3. 补上 Prism 代码高亮兜底(此前只存在于线上,仓库里没有)
语言包路径改用 plugins_url() 动态生成,换域名或加 CDN 不会静默 404。
4. 主题更新元数据从独立子域名迁到主站 /theme-update/
地址可用 wp-config.php 的 YANG_THEME_UPDATE_URL 覆盖,换域名不必改主题代码。
5. footer 的主题链接指向自建源库;补文档说明自动升级会覆盖主题目录、
以及发布新版本必须"先打包再改版本号"的顺序。
6. 顺带去掉 .a-post .header .label 里重复声明的 position。
|
2026-10-23 09:06:16 +08:00 |
|
laoyang
|
04e8eb5278
|
修改了列表行高,看起来更舒服。
|
2022-07-16 17:20:49 +08:00 |
|
laoyang
|
dbd78621ec
|
更改了列表样式。
|
2022-07-16 08:10:00 +08:00 |
|
laoyang
|
a826b26bee
|
从gitee代码库中初始化项目。
|
2022-01-07 11:15:42 +08:00 |
|