版本号 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)。
This commit is contained in:
2026-10-23 14:43:47 +08:00
parent 37c52948c4
commit 269364bed4
9 changed files with 833 additions and 21 deletions
+63 -14
View File
@@ -87,16 +87,41 @@ function yang_mail_is_dark($hex)
}
/**
* 把可能含 HTML 的片段转成邮件安全的纯文本
* 把可能含 HTML 的片段转成邮件安全的纯文本(保留换行)
*
* 邮件正文里塞用户提交的评论内容,必须防注入:邮件客户端也会执行
* `<script>`(部分 webmail 会),而且一旦渲染出畸形 HTML,整封邮件布局就崩。
*
* ── 换行为什么要专门处理 ─────────────────────────────────────
* 两个坑都会把整条评论拍成一行:
* 1. `wp_strip_all_tags($x, true)` 的第二个参数是 remove_breaks,
* 真实实现里会 `preg_replace('/[\r\n\t ]+/', ' ', $x)` —— 换行直接没了。
* 所以这里必须传 false。
* 2. `strip_tags()` 遇到 `<br>` 是**直接删掉、不补换行**的,`</p>` 同理。
* 评论里手写的换行和段落因此在去标签阶段就无痕消失。
* 结论:先把块级边界换成 `\n`,再去标签,最后才收拾空白。
* 这样模板里的 `nl2br()` 才有换行可转,不是一段死代码。
*/
function yang_mail_text($html, $limit = 0)
{
$t = wp_strip_all_tags((string) $html, true);
$t = (string) $html;
// 1. 先把"视觉上的换行"降级成真正的 \n
$t = preg_replace('@<br\s*/?>@i', "\n", $t);
$t = preg_replace('@</(p|div|blockquote|pre|h[1-6])\s*>@i', "\n", $t);
// <li> 自己带前缀,就不要</li>再加换行,否则列表每项之间会多一个空行
$t = preg_replace('@<li[^>]*>@i', "\n- ", $t);
// 2. 去标签,但不要 remove_breaks(那是换行消失的真正原因)
$t = wp_strip_all_tags($t, false);
// 3. 收拾空白:行内空白收成一个空格,换行保留,最多一个空行
$t = html_entity_decode($t, ENT_QUOTES, 'UTF-8');
$t = preg_replace('/\s+/u', ' ', $t);
$t = str_replace(array("\r\n", "\r"), "\n", $t);
/* 用 [^\S\n]("非空白"取反 + 排除换行 = 除换行外的空白)而不是
[ \t\f\v]:PCRE 里 \v 是**垂直空白类**,把 \n 也算进去,
写成 [ \t\f\v] 会连换行一起替换成空格 —— 而且只在带 /u 时表现如此,
极易漏看。语义直接写出来,别依赖转义字母的记忆。 */
$t = preg_replace('/[^\S\n]+/u', ' ', $t);
$t = preg_replace('/ *\n */u', "\n", $t);
$t = preg_replace('/\n{3,}/u', "\n\n", $t);
$t = trim($t);
if ($limit > 0 && mb_strlen($t, 'UTF-8') > $limit) {
$t = mb_substr($t, 0, $limit, 'UTF-8') . '…';
@@ -174,13 +199,25 @@ function yang_mail_render($args)
'heading' => '',
'intro' => '',
'rows' => array(),
'quote' => array(),
'quote' => array(), // 单个引用块(旧接口,保留兼容)
'quotes' => array(), // 多个引用块,按顺序渲染
'cta_text' => '',
'cta_url' => '',
'note' => '',
'badge' => '',
));
/* 统一成 $quoteList:
* 调用方可能只给一个 quote,也可能给 quotes 数组(评论回复场景要同时放
* 「被回复的那条」和「新的回复」,两段各自有作者和时间,必须分开渲染)。
* 两者都给时以 quotes 为准,避免重复出现。 */
$quoteList = array();
if (!empty($d['quotes']) && is_array($d['quotes'])) {
$quoteList = $d['quotes'];
} elseif (!empty($d['quote'])) {
$quoteList = array($d['quote']);
}
$brand = yang_mail_brand_color();
$brandD = yang_mail_shade($brand, 0.78);
$onBrand = yang_mail_is_dark($brand) ? '#ffffff' : '#1a1a1a';
@@ -283,28 +320,40 @@ function yang_mail_render($args)
</div>
<?php endif; ?>
<?php if (!empty($d['quote'])): ?>
<?php /* 引用块:左边一条主色竖线,用 border-left 而不是伪元素(邮件不支持 ::before) */ ?>
<table border="0" cellpadding="0" cellspacing="0" width="100%" role="presentation" style="margin:0 0 22px 0;">
<?php foreach ($quoteList as $qi => $q): ?>
<?php
/* 引用块:左边一条竖线,用 border-left 而不是伪元素(邮件不支持 ::before)。
*
* 配色规则:**最后一块用主色高亮,前面的用弱化灰**。
* 为什么是「最后」而不是「第一」:评论回复场景里顺序是
* 「被回复的原评论」→「新的回复」,语义重点在新的回复上,所以让它高亮。
* 只有一个块时它既是第一也是最后 ⇒ 拿到主色,与旧版行为一致。
*/
$isLast = ($qi === count($quoteList) - 1);
$barColor = $isLast ? $brand : '#c2cbd4';
$barBg = $isLast ? '#f6f8fa' : '#fafbfc';
$barMargin = $isLast ? '22px' : '14px';
?>
<table border="0" cellpadding="0" cellspacing="0" width="100%" role="presentation" style="margin:0 0 <?php echo $barMargin; ?> 0;">
<tr>
<td style="border-left:3px solid <?php echo esc_attr($brand); ?>;background-color:#f6f8fa;padding:16px 18px;border-radius:0 8px 8px 0;">
<?php if (!empty($d['quote']['label'])): ?>
<td style="border-left:3px solid <?php echo esc_attr($barColor); ?>;background-color:<?php echo esc_attr($barBg); ?>;padding:16px 18px;border-radius:0 8px 8px 0;">
<?php if (!empty($q['label'])): ?>
<div style="font-family:<?php echo $sans; ?>;font-size:12px;font-weight:600;color:#5f6b78;letter-spacing:0.6px;text-transform:uppercase;margin:0 0 8px 0;">
<?php echo esc_html($d['quote']['label']); ?>
<?php echo esc_html($q['label']); ?>
</div>
<?php endif; ?>
<div style="font-family:<?php echo $sans; ?>;font-size:15px;color:#2d3740;line-height:1.7;word-break:break-word;">
<?php echo nl2br(esc_html($d['quote']['content'])); ?>
<?php echo nl2br(esc_html(isset($q['content']) ? $q['content'] : '')); ?>
</div>
<?php if (!empty($d['quote']['meta'])): ?>
<?php if (!empty($q['meta'])): ?>
<div style="font-family:<?php echo $sans; ?>;font-size:13px;color:#5f6b78;line-height:1.6;margin:9px 0 0 0;">
<?php echo esc_html($d['quote']['meta']); ?>
<?php echo esc_html($q['meta']); ?>
</div>
<?php endif; ?>
</td>
</tr>
</table>
<?php endif; ?>
<?php endforeach; ?>
<?php if (!empty($d['rows'])): ?>
<?php /* 信息表:两列。label 列定宽 88px 并右对齐,value 左对齐,视觉上最稳。