版本号 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:
@@ -27,6 +27,9 @@ require get_template_directory() . '/inc/theme-setting.php';
|
|||||||
// 因为它用到 kratos_option)
|
// 因为它用到 kratos_option)
|
||||||
require get_template_directory() . '/inc/theme-mourning.php';
|
require get_template_directory() . '/inc/theme-mourning.php';
|
||||||
|
|
||||||
|
// 哀悼说明页(固定路径 /memory,由主题判定路由,不写数据库)
|
||||||
|
require get_template_directory() . '/inc/theme-mourning-page.php';
|
||||||
|
|
||||||
// 文章配置
|
// 文章配置
|
||||||
require get_template_directory() . '/inc/theme-article.php';
|
require get_template_directory() . '/inc/theme-article.php';
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,14 @@
|
|||||||
</head>
|
</head>
|
||||||
<?php flush(); ?>
|
<?php flush(); ?>
|
||||||
<body>
|
<body>
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* wp_body_open 是 WordPress 5.2 引入的标准钩子,正文最顶部(且必须紧跟 <body>)。
|
||||||
|
* 主题之前没有调用它,导致所有挂在这个钩子上的东西(插件、主题自己的提示条)静默失效。
|
||||||
|
* 补上是必须的:不调用它不会报错,只会"什么都没发生",极难发现。
|
||||||
|
*/
|
||||||
|
wp_body_open();
|
||||||
|
?>
|
||||||
<div class="k-header">
|
<div class="k-header">
|
||||||
<nav class="k-nav navbar navbar-expand-lg navbar-light fixed-top" <?php if(kratos_option('top_select', 'banner') !== 'banner'){ echo 'style="background:' . kratos_option('top_color', '#24292e') .'"';} ?>>
|
<nav class="k-nav navbar navbar-expand-lg navbar-light fixed-top" <?php if(kratos_option('top_select', 'banner') !== 'banner'){ echo 'style="background:' . kratos_option('top_color', '#24292e') .'"';} ?>>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
|||||||
+63
-14
@@ -87,16 +87,41 @@ function yang_mail_is_dark($hex)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 把可能含 HTML 的片段转成邮件安全的纯文本
|
* 把可能含 HTML 的片段转成邮件安全的纯文本(保留换行)
|
||||||
*
|
*
|
||||||
* 邮件正文里塞用户提交的评论内容,必须防注入:邮件客户端也会执行
|
* 邮件正文里塞用户提交的评论内容,必须防注入:邮件客户端也会执行
|
||||||
* `<script>`(部分 webmail 会),而且一旦渲染出畸形 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)
|
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 = 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);
|
$t = trim($t);
|
||||||
if ($limit > 0 && mb_strlen($t, 'UTF-8') > $limit) {
|
if ($limit > 0 && mb_strlen($t, 'UTF-8') > $limit) {
|
||||||
$t = mb_substr($t, 0, $limit, 'UTF-8') . '…';
|
$t = mb_substr($t, 0, $limit, 'UTF-8') . '…';
|
||||||
@@ -174,13 +199,25 @@ function yang_mail_render($args)
|
|||||||
'heading' => '',
|
'heading' => '',
|
||||||
'intro' => '',
|
'intro' => '',
|
||||||
'rows' => array(),
|
'rows' => array(),
|
||||||
'quote' => array(),
|
'quote' => array(), // 单个引用块(旧接口,保留兼容)
|
||||||
|
'quotes' => array(), // 多个引用块,按顺序渲染
|
||||||
'cta_text' => '',
|
'cta_text' => '',
|
||||||
'cta_url' => '',
|
'cta_url' => '',
|
||||||
'note' => '',
|
'note' => '',
|
||||||
'badge' => '',
|
'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();
|
$brand = yang_mail_brand_color();
|
||||||
$brandD = yang_mail_shade($brand, 0.78);
|
$brandD = yang_mail_shade($brand, 0.78);
|
||||||
$onBrand = yang_mail_is_dark($brand) ? '#ffffff' : '#1a1a1a';
|
$onBrand = yang_mail_is_dark($brand) ? '#ffffff' : '#1a1a1a';
|
||||||
@@ -283,28 +320,40 @@ function yang_mail_render($args)
|
|||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if (!empty($d['quote'])): ?>
|
<?php foreach ($quoteList as $qi => $q): ?>
|
||||||
<?php /* 引用块:左边一条主色竖线,用 border-left 而不是伪元素(邮件不支持 ::before) */ ?>
|
<?php
|
||||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" role="presentation" style="margin:0 0 22px 0;">
|
/* 引用块:左边一条竖线,用 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>
|
<tr>
|
||||||
<td style="border-left:3px solid <?php echo esc_attr($brand); ?>;background-color:#f6f8fa;padding:16px 18px;border-radius:0 8px 8px 0;">
|
<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($d['quote']['label'])): ?>
|
<?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;">
|
<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>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<div style="font-family:<?php echo $sans; ?>;font-size:15px;color:#2d3740;line-height:1.7;word-break:break-word;">
|
<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>
|
</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;">
|
<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>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<?php endif; ?>
|
<?php endforeach; ?>
|
||||||
|
|
||||||
<?php if (!empty($d['rows'])): ?>
|
<?php if (!empty($d['rows'])): ?>
|
||||||
<?php /* 信息表:两列。label 列定宽 88px 并右对齐,value 左对齐,视觉上最稳。
|
<?php /* 信息表:两列。label 列定宽 88px 并右对齐,value 左对齐,视觉上最稳。
|
||||||
|
|||||||
+144
-1
@@ -306,4 +306,147 @@ function yang_mail_send_test($to = null)
|
|||||||
));
|
));
|
||||||
|
|
||||||
return wp_mail($to, sprintf(__('[%s] 邮件配置测试', 'kratos'), get_bloginfo('name')), $body);
|
return wp_mail($to, sprintf(__('[%s] 邮件配置测试', 'kratos'), get_bloginfo('name')), $body);
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 抵消 wpDiscuz 在过滤器之后又做的那一次 html_entity_decode
|
||||||
|
*
|
||||||
|
* ── 为什么必须要这一步 ────────────────────────────────────────
|
||||||
|
* `wpdiscuz_email_content` 这个过滤器**不是最后一站**。插件拿到返回值后
|
||||||
|
* 还会执行:
|
||||||
|
*
|
||||||
|
* $message = html_entity_decode($message, ENT_QUOTES);
|
||||||
|
* wp_mail($email, $subject, $message, $headers, $attachments);
|
||||||
|
*
|
||||||
|
* (utils/class.WpdiscuzHelperEmail.php:494 附近)
|
||||||
|
*
|
||||||
|
* 于是我们在模板里用 esc_html() 做的转义会被整体还原:
|
||||||
|
* 用户评论里的 `<script>alert(1)</script>` 会变回可执行标签,
|
||||||
|
* 未闭合的 `<div>` 会吃掉整封邮件的布局。
|
||||||
|
*
|
||||||
|
* 这**在本地用桩环境测不出来** —— 桩环境没有插件那一步解码,
|
||||||
|
* 断言只会看到"已转义"而全绿。只有读插件源码才发现。
|
||||||
|
*
|
||||||
|
* 解法:把 `&` 全部预编码成 `&`。这样插件解码一次之后,
|
||||||
|
* 恰好还原成我们想要的转义状态(decode("&lt;") === "<")。
|
||||||
|
* 只处理 `&` 就够,因为其它实体的风险都源于 `&` 被还原。
|
||||||
|
*/
|
||||||
|
function yang_mail_escape_for_wpdiscuz($html)
|
||||||
|
{
|
||||||
|
return str_replace('&', '&', $html);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 把 wpDiscuz 的通知邮件接管成主题模板
|
||||||
|
*
|
||||||
|
* ── 为什么要接管 ──────────────────────────────────────────────
|
||||||
|
* wpDiscuz 自己的模板只印「新评论内容」,收件人看到一句陌生人说的话,
|
||||||
|
* 完全想不起自己在哪篇文章下说过什么。要理解上下文,必须**同时给出
|
||||||
|
* 被回复的那条评论**。
|
||||||
|
*
|
||||||
|
* 它的过滤器 `wpdiscuz_email_content` 能拿到 `$comment`(新评论对象)
|
||||||
|
* 和 `$emailData`(订阅记录),足以重建整封邮件,所以不需要改插件。
|
||||||
|
*
|
||||||
|
* ── 两个模板还是一个模板 ──────────────────────────────────────
|
||||||
|
* 用一个。评论通知和「审核通过」通知的差别只有标题、徽标、引言和
|
||||||
|
* 上面那两段引用,正文骨架完全一样。做成两套模板会立刻分叉,
|
||||||
|
* 以后改一次配色要改两处。
|
||||||
|
*
|
||||||
|
* @param string $message 插件生成的 HTML(丢弃)
|
||||||
|
* @param WP_Comment $comment 新评论
|
||||||
|
* @param array $email_data 订阅记录
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function yang_mail_render_comment_notify($message, $comment, $email_data = array())
|
||||||
|
{
|
||||||
|
if (!yang_mail_smtp_ready() || !($comment instanceof WP_Comment)) {
|
||||||
|
return $message;
|
||||||
|
}
|
||||||
|
|
||||||
|
$site = get_bloginfo('name');
|
||||||
|
$post_id = (int) $comment->comment_post_ID;
|
||||||
|
$post = get_post($post_id);
|
||||||
|
$title = $post ? get_the_title($post) : __('(文章已删除)', 'kratos');
|
||||||
|
$link = get_comment_link($comment->comment_ID);
|
||||||
|
$author = $comment->comment_author ? $comment->comment_author : __('匿名访客', 'kratos');
|
||||||
|
|
||||||
|
// 新评论这一段(一定是最后一块,模板会把它高亮成主色)
|
||||||
|
$quotes = array();
|
||||||
|
|
||||||
|
// 被回复的原评论:有父评论才取,取不到就不放,宁缺毋滥
|
||||||
|
$parent_id = (int) $comment->comment_parent;
|
||||||
|
if ($parent_id > 0) {
|
||||||
|
$parent = get_comment($parent_id);
|
||||||
|
if ($parent) {
|
||||||
|
$p_author = $parent->comment_author ? $parent->comment_author : __('匿名访客', 'kratos');
|
||||||
|
$quotes[] = array(
|
||||||
|
'label' => sprintf(__('你之前说的(%s)', 'kratos'), $p_author),
|
||||||
|
'content' => yang_mail_text($parent->comment_content, 1200),
|
||||||
|
'meta' => mysql2date('Y-m-d H:i', $parent->comment_date),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$quotes[] = array(
|
||||||
|
'label' => sprintf(__('%s 回复说', 'kratos'), $author),
|
||||||
|
'content' => yang_mail_text($comment->comment_content, 1200),
|
||||||
|
'meta' => mysql2date('Y-m-d H:i', $comment->comment_date),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 引言随场景变化:有父评论是「有人回复你」,没有是「有新评论」
|
||||||
|
$intro = $parent_id > 0
|
||||||
|
? sprintf(__('你在《%s》下的评论收到了一条回复。', 'kratos'), $title)
|
||||||
|
: sprintf(__('《%s》下有新的评论。', 'kratos'), $title);
|
||||||
|
|
||||||
|
$rows = array(
|
||||||
|
array('label' => __('文章', 'kratos'), 'value' => $title),
|
||||||
|
array('label' => __('评论者', 'kratos'), 'value' => $author, 'strong' => true),
|
||||||
|
array('label' => __('时间', 'kratos'), 'value' => mysql2date('Y-m-d H:i', $comment->comment_date)),
|
||||||
|
);
|
||||||
|
|
||||||
|
$body = yang_mail_render(array(
|
||||||
|
'title' => sprintf(__('%s · 新回复', 'kratos'), $site),
|
||||||
|
'preheader' => sprintf(__('%s 在《%s》下回复了评论。', 'kratos'), $author, $title),
|
||||||
|
'badge' => $parent_id > 0 ? __('新回复', 'kratos') : __('新评论', 'kratos'),
|
||||||
|
'heading' => $parent_id > 0 ? __('有人回复了你的评论', 'kratos') : __('文章有新评论', 'kratos'),
|
||||||
|
'intro' => $intro,
|
||||||
|
'quotes' => $quotes,
|
||||||
|
'rows' => $rows,
|
||||||
|
'cta_text' => __('查看完整讨论', 'kratos'),
|
||||||
|
'cta_url' => $link,
|
||||||
|
'note' => __('你收到这封邮件,是因为你订阅了这篇文章的评论,或你的评论被回复了。', 'kratos'),
|
||||||
|
));
|
||||||
|
|
||||||
|
return yang_mail_escape_for_wpdiscuz($body);
|
||||||
|
}
|
||||||
|
add_filter('wpdiscuz_email_content', 'yang_mail_render_comment_notify', 20, 3);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审核通过通知也套同一套模板
|
||||||
|
*
|
||||||
|
* 这个过滤器的参数是 ($message, $post_id, $email),拿不到评论对象,
|
||||||
|
* 所以只能给「你的评论已通过审核」这种通用内容 —— 这是插件接口的限制,
|
||||||
|
* 不是这里偷懒。
|
||||||
|
*/
|
||||||
|
function yang_mail_render_approved_notify($message, $post_id, $email)
|
||||||
|
{
|
||||||
|
if (!yang_mail_smtp_ready()) {
|
||||||
|
return $message;
|
||||||
|
}
|
||||||
|
|
||||||
|
$title = get_the_title($post_id);
|
||||||
|
|
||||||
|
return yang_mail_escape_for_wpdiscuz(yang_mail_render(array(
|
||||||
|
'title' => sprintf(__('%s · 评论已通过审核', 'kratos'), get_bloginfo('name')),
|
||||||
|
'preheader' => sprintf(__('你在《%s》下的评论已通过审核并公开显示。', 'kratos'), $title),
|
||||||
|
'badge' => __('已通过', 'kratos'),
|
||||||
|
'heading' => __('你的评论已通过审核', 'kratos'),
|
||||||
|
'intro' => sprintf(__('你在《%s》下提交的评论已经审核通过,现在其他读者可以看到它了。', 'kratos'), $title),
|
||||||
|
'rows' => array(
|
||||||
|
array('label' => __('文章', 'kratos'), 'value' => $title),
|
||||||
|
),
|
||||||
|
'cta_text' => __('去看看', 'kratos'),
|
||||||
|
'cta_url' => get_permalink($post_id),
|
||||||
|
'note' => __('这是评论审核结果通知,无需回复。', 'kratos'),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
add_filter('wpdiscuz_comment_approved_email_content', 'yang_mail_render_approved_notify', 20, 3);
|
||||||
|
|||||||
@@ -0,0 +1,414 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* 哀悼说明页
|
||||||
|
*
|
||||||
|
* ── 这个页面解决什么问题 ──────────────────────────────────────
|
||||||
|
* 站点在纪念日自动变黑白,但访客看到一片灰只会以为网站坏了。
|
||||||
|
* 需要在页面上给出一个入口,点进来能看到「为什么变黑白」的完整说明。
|
||||||
|
*
|
||||||
|
* ── 三条设计约束 ──────────────────────────────────────────────
|
||||||
|
* 1. **本页必须不受灰度影响**。全站 `html{filter:grayscale(100%)}` 会
|
||||||
|
* 把这一页也变灰,而用户明确要求「界面要做的漂亮,彩色的」。
|
||||||
|
* 灰度样式挂在 `wp_head` 优先级 1,本页在 `wp_head` 优先级 2 里
|
||||||
|
* 反转回来 —— 见 inc/theme-mourning.php 的 yang_mourning_page_css()。
|
||||||
|
*
|
||||||
|
* 2. **不依赖主题的 .article 样式**。哀悼页用独立的类名命名空间 `ym-`,
|
||||||
|
* 避免和正文排版规则打架(正文的 p/ul 样式会把卡片挤变形)。
|
||||||
|
*
|
||||||
|
* 3. **静态页 + 短代码**。不写进数据库,用 page 模板 + 主题自带内容,
|
||||||
|
* 这样升级主题就自动带上,用户不需要手动建页面。
|
||||||
|
*
|
||||||
|
* @author Yongquan Yang <yangyq.net>
|
||||||
|
* @license MIT License
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('ABSPATH')) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 说明页用的配色
|
||||||
|
*
|
||||||
|
* 哀悼页是**彩色**的,但主色调仍然克制(不用大红大绿),
|
||||||
|
* 因为它讲的是一件严肃的事。用「暖金 + 深墨」的组合:
|
||||||
|
* 金色呼应纪念的国家仪式感,深墨保证长文可读。
|
||||||
|
*/
|
||||||
|
function yang_mourning_page_palette()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
'gold' => '#b8860b', // 暗金,装饰色(SVG 描边、分隔线、纯装饰的描边)
|
||||||
|
/* 金的"可读版本"。
|
||||||
|
* #b8860b 在白底上只有 3.06:1,低于 WCAG AA 对正文的 4.5:1 ——
|
||||||
|
* 装饰线条用它没问题,但当**文字颜色**就偏虚,尤其 12–15px 小字。
|
||||||
|
* #946600 是同色相里仍属金色、且刚好过线(4.75:1)的最亮一档,
|
||||||
|
* 再亮一档 #9c7000 只有 4.18:1 不达标,再暗就往棕色走了。
|
||||||
|
* 所以装饰归装饰、文字归文字,两个变量分开,不要混用。 */
|
||||||
|
'gold_text' => '#946600',
|
||||||
|
'gold_soft' => '#fdf6e3', // 金的浅底
|
||||||
|
'ink' => '#1f2933', // 正文墨色
|
||||||
|
'ink_soft' => '#52606d', // 次要文字
|
||||||
|
'paper' => '#ffffff',
|
||||||
|
'canvas' => '#f7f8fa',
|
||||||
|
'line' => '#e4e7eb',
|
||||||
|
'red' => '#a4243b', // 公祭日用的暗红
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 渲染哀悼说明的正文(短代码与模板共用)
|
||||||
|
*
|
||||||
|
* @return string HTML
|
||||||
|
*/
|
||||||
|
function yang_mourning_page_content()
|
||||||
|
{
|
||||||
|
$p = yang_mourning_page_palette();
|
||||||
|
$days = yang_mourning_days();
|
||||||
|
$presets = yang_mourning_presets();
|
||||||
|
|
||||||
|
// 今天是否在哀悼
|
||||||
|
$is_today = yang_is_mourning_today();
|
||||||
|
$today = current_time('m') . '-' . current_time('d');
|
||||||
|
|
||||||
|
ob_start();
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="ym-wrap" style="--ym-gold:<?php echo esc_attr($p['gold']); ?>;--ym-gold-text:<?php echo esc_attr($p['gold_text']); ?>;--ym-gold-soft:<?php echo esc_attr($p['gold_soft']); ?>;--ym-ink:<?php echo esc_attr($p['ink']); ?>;--ym-ink-soft:<?php echo esc_attr($p['ink_soft']); ?>;--ym-line:<?php echo esc_attr($p['line']); ?>;--ym-red:<?php echo esc_attr($p['red']); ?>;">
|
||||||
|
|
||||||
|
<?php /* ── 顶部:当前状态 ── */ ?>
|
||||||
|
<div class="ym-hero">
|
||||||
|
<div class="ym-hero-inner">
|
||||||
|
<div class="ym-hero-mark" aria-hidden="true">
|
||||||
|
<svg viewBox="0 0 64 64" width="56" height="56" role="img">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="ymg" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop offset="0%" stop-color="#d4a017"/>
|
||||||
|
<stop offset="100%" stop-color="#8b6508"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<circle cx="32" cy="32" r="29" fill="none" stroke="url(#ymg)" stroke-width="2" opacity="0.55"/>
|
||||||
|
<circle cx="32" cy="32" r="23" fill="none" stroke="url(#ymg)" stroke-width="1" opacity="0.3"/>
|
||||||
|
<path d="M32 15 L32 49" stroke="url(#ymg)" stroke-width="3.2" stroke-linecap="round"/>
|
||||||
|
<path d="M20 27 L44 27" stroke="url(#ymg)" stroke-width="3.2" stroke-linecap="round"/>
|
||||||
|
<path d="M24 38 Q32 45 40 38" stroke="url(#ymg)" stroke-width="2.4" fill="none" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h1 class="ym-hero-title"><?php echo $is_today ? '今天是纪念日' : '关于本站的纪念日哀悼'; ?></h1>
|
||||||
|
<p class="ym-hero-sub">
|
||||||
|
<?php if ($is_today): ?>
|
||||||
|
本站今日以黑白呈现,以寄哀思。这不是故障,是刻意的。
|
||||||
|
<?php else: ?>
|
||||||
|
在特定的日子里,本站会转为黑白显示,以示纪念。
|
||||||
|
<?php endif; ?>
|
||||||
|
</p>
|
||||||
|
<div class="ym-hero-status <?php echo $is_today ? 'is-on' : 'is-off'; ?>">
|
||||||
|
<span class="ym-dot"></span>
|
||||||
|
<?php if ($is_today): ?>
|
||||||
|
当前状态:正在哀悼(全站黑白)
|
||||||
|
<?php else: ?>
|
||||||
|
当前状态:正常显示 · 今天是 <?php echo esc_html($today); ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php /* ── 为什么会变黑白 ── */ ?>
|
||||||
|
<section class="ym-sec">
|
||||||
|
<h2 class="ym-sec-title"><span class="ym-bar"></span>为什么会变成黑白</h2>
|
||||||
|
<div class="ym-prose">
|
||||||
|
<p>在一些具有特殊意义的日子里,本站会把整站色调转为黑白,暂停一切彩色装饰。这是一种无声的致意 —— 和降半旗、鸣笛、默哀是同一类表达。</p>
|
||||||
|
<p>它不改变内容,也不影响你阅读任何文章。只是把颜色暂时收起来。</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<?php /* ── 哪几天会变黑白(真实数据,不是写死的文案) ── */ ?>
|
||||||
|
<section class="ym-sec">
|
||||||
|
<h2 class="ym-sec-title"><span class="ym-bar"></span>哪几天会自动变黑白</h2>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// 按用户实际勾选分组,勾了才显示。没勾就明说没勾,别让访客以为本站不在乎。
|
||||||
|
$legal = array();
|
||||||
|
$custom = array();
|
||||||
|
foreach ($days as $md) {
|
||||||
|
if (!isset($presets[$md])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (isset($presets[$md]['group']) && $presets[$md]['group'] === 'legal') {
|
||||||
|
$legal[$md] = $presets[$md];
|
||||||
|
} else {
|
||||||
|
$custom[$md] = $presets[$md];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php if (!empty($legal)): ?>
|
||||||
|
<div class="ym-group-label">法定纪念日</div>
|
||||||
|
<div class="ym-days">
|
||||||
|
<?php foreach ($legal as $md => $info): ?>
|
||||||
|
<div class="ym-day <?php echo ($md === $today) ? 'is-today' : ''; ?>">
|
||||||
|
<div class="ym-day-date"><?php echo esc_html(str_replace('-', '.', $md)); ?></div>
|
||||||
|
<div class="ym-day-body">
|
||||||
|
<div class="ym-day-name"><?php echo esc_html($info['name']); ?></div>
|
||||||
|
<div class="ym-day-note"><?php echo esc_html($info['note']); ?></div>
|
||||||
|
</div>
|
||||||
|
<?php if ($md === $today): ?><div class="ym-day-tag">今天</div><?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (!empty($custom)): ?>
|
||||||
|
<div class="ym-group-label">本站另外启用的纪念日</div>
|
||||||
|
<div class="ym-days">
|
||||||
|
<?php foreach ($custom as $md => $info): ?>
|
||||||
|
<div class="ym-day <?php echo ($md === $today) ? 'is-today' : ''; ?>">
|
||||||
|
<div class="ym-day-date"><?php echo esc_html(str_replace('-', '.', $md)); ?></div>
|
||||||
|
<div class="ym-day-body">
|
||||||
|
<div class="ym-day-name"><?php echo esc_html($info['name']); ?></div>
|
||||||
|
<div class="ym-day-note"><?php echo esc_html($info['note']); ?></div>
|
||||||
|
</div>
|
||||||
|
<?php if ($md === $today): ?><div class="ym-day-tag">今天</div><?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// 附加的自定义日期
|
||||||
|
$extra = trim((string) kratos_option('g_rip_extra', ''));
|
||||||
|
if ($extra !== ''):
|
||||||
|
?>
|
||||||
|
<div class="ym-group-label">站主另行标注的日期</div>
|
||||||
|
<div class="ym-extra">
|
||||||
|
<?php
|
||||||
|
foreach (preg_split('/[\s,,、;;]+/u', $extra) as $one) {
|
||||||
|
$one = trim($one);
|
||||||
|
if ($one === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 这里是用户自由填写的字段,必须转义后再显示
|
||||||
|
echo '<span class="ym-extra-chip">' . esc_html($one) . '</span>';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (empty($days) && $extra === ''): ?>
|
||||||
|
<div class="ym-empty">
|
||||||
|
<p>本站目前没有启用任何纪念日。</p>
|
||||||
|
<p class="ym-empty-note">如果哪天你看到这里变成黑白,那说明站主刚刚启用了它。</p>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<?php /* ── 常用问答 ── */ ?>
|
||||||
|
<section class="ym-sec">
|
||||||
|
<h2 class="ym-sec-title"><span class="ym-bar"></span>几个可能想问的问题</h2>
|
||||||
|
<div class="ym-faq">
|
||||||
|
<details class="ym-qa" open>
|
||||||
|
<summary>是我浏览器坏了吗?</summary>
|
||||||
|
<div class="ym-qa-body">不是。可以在别的设备上打开对比一下,同样是黑白。整站色调由一段样式统一控制,会在纪念日当天自动生效,次日自动恢复。</div>
|
||||||
|
</details>
|
||||||
|
<details class="ym-qa">
|
||||||
|
<summary>会影响阅读吗?</summary>
|
||||||
|
<div class="ym-qa-body">内容、链接、图片都不变,只是颜色被去掉。文字对比度反而更高一些。唯一的变化是图片失去色彩。</div>
|
||||||
|
</details>
|
||||||
|
<details class="ym-qa">
|
||||||
|
<summary>什么时候恢复彩色?</summary>
|
||||||
|
<div class="ym-qa-body">当天 24 点过后自动恢复。本站按站点时区判断日期,不是按你所在时区,所以不同地区看到的切换时刻可能略有差别。</div>
|
||||||
|
</details>
|
||||||
|
<details class="ym-qa">
|
||||||
|
<summary>打印出来也是灰的吗?</summary>
|
||||||
|
<div class="ym-qa-body">不是。打印时已专门取消灰度,纸上呈现正常颜色。</div>
|
||||||
|
</details>
|
||||||
|
<details class="ym-qa">
|
||||||
|
<summary>这个页面为什么是彩色的?</summary>
|
||||||
|
<div class="ym-qa-body">说明性质的内容需要读得清楚。把说明页也做成灰的,反而增加了理解成本 —— 这与「让人明白发生了什么」的目的是相反的。</div>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<?php /* ── 底部 ── */ ?>
|
||||||
|
<div class="ym-foot">
|
||||||
|
<div class="ym-foot-line"></div>
|
||||||
|
<p class="ym-foot-text">铭记,是为了更好地前行。</p>
|
||||||
|
<p class="ym-foot-link">
|
||||||
|
<a href="<?php echo esc_url(home_url('/')); ?>">返回首页</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
return ob_get_clean();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 说明页的样式
|
||||||
|
*
|
||||||
|
* 独立命名空间 `ym-`,不碰主题的 .article / .content 规则。
|
||||||
|
* 用 CSS 变量做配色,方便以后换主题色时只改一处。
|
||||||
|
*/
|
||||||
|
function yang_mourning_page_css()
|
||||||
|
{
|
||||||
|
$css = <<<'CSS'
|
||||||
|
.ym-wrap{max-width:820px;margin:0 auto;padding:0 4px 40px;color:var(--ym-ink);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif;line-height:1.75}
|
||||||
|
.ym-wrap *{box-sizing:border-box}
|
||||||
|
|
||||||
|
/* 顶部 */
|
||||||
|
.ym-hero{position:relative;overflow:hidden;border-radius:16px;padding:44px 36px 36px;text-align:center;background:linear-gradient(160deg,#1f2933 0%,#2d3a47 55%,#3a4a58 100%);color:#fff;box-shadow:0 18px 40px -22px rgba(31,41,51,.55)}
|
||||||
|
.ym-hero::after{content:"";position:absolute;inset:0;background:radial-gradient(circle at 50% 0%,rgba(212,160,23,.18),transparent 62%);pointer-events:none}
|
||||||
|
.ym-hero-inner{position:relative;z-index:1}
|
||||||
|
.ym-hero-mark{display:flex;justify-content:center;margin-bottom:16px}
|
||||||
|
.ym-hero-title{margin:0 0 10px;font-size:26px;font-weight:600;letter-spacing:.5px;color:#fff}
|
||||||
|
.ym-hero-sub{margin:0 auto 20px;max-width:520px;font-size:15px;color:rgba(255,255,255,.78);line-height:1.8}
|
||||||
|
.ym-hero-status{display:inline-flex;align-items:center;gap:8px;font-size:13px;padding:7px 16px;border-radius:999px;border:1px solid rgba(255,255,255,.22);background:rgba(255,255,255,.08);color:rgba(255,255,255,.9)}
|
||||||
|
.ym-hero-status.is-on{border-color:rgba(212,160,23,.55);background:rgba(212,160,23,.16);color:#f0d08a}
|
||||||
|
.ym-dot{width:7px;height:7px;border-radius:50%;background:#7f8b98;flex:0 0 auto}
|
||||||
|
.ym-hero-status.is-on .ym-dot{background:#e0b64a;box-shadow:0 0 0 3px rgba(224,182,74,.25);animation:ympulse 2.4s ease-in-out infinite}
|
||||||
|
@keyframes ympulse{0%,100%{opacity:1}50%{opacity:.45}}
|
||||||
|
|
||||||
|
/* 段落区块 */
|
||||||
|
.ym-sec{margin-top:34px;padding:0 6px}
|
||||||
|
.ym-sec-title{display:flex;align-items:center;gap:11px;margin:0 0 16px;font-size:17px;font-weight:600;color:var(--ym-ink)}
|
||||||
|
.ym-bar{display:block;width:3px;height:17px;border-radius:2px;background:linear-gradient(180deg,#d4a017,#8b6508);flex:0 0 auto}
|
||||||
|
.ym-prose p{margin:0 0 12px;font-size:15.5px;color:var(--ym-ink-soft)}
|
||||||
|
.ym-prose p:last-child{margin-bottom:0}
|
||||||
|
|
||||||
|
/* 日期列表 */
|
||||||
|
.ym-group-label{margin:18px 0 9px;font-size:12px;font-weight:600;letter-spacing:1.2px;color:#5f6b78;text-transform:uppercase}
|
||||||
|
.ym-group-label:first-child{margin-top:0}
|
||||||
|
.ym-days{display:grid;gap:8px}
|
||||||
|
.ym-day{display:flex;align-items:flex-start;gap:14px;padding:14px 16px;border-radius:10px;background:var(--ym-canvas,#f7f8fa);border:1px solid var(--ym-line);transition:border-color .18s ease,transform .18s ease}
|
||||||
|
.ym-day:hover{border-color:#cfd6de;transform:translateY(-1px)}
|
||||||
|
.ym-day.is-today{background:var(--ym-gold-soft);border-color:rgba(184,134,11,.45);box-shadow:0 6px 18px -12px rgba(184,134,11,.6)}
|
||||||
|
.ym-day-date{font-variant-numeric:tabular-nums;font-size:15px;font-weight:600;color:var(--ym-gold-text);flex:0 0 auto;letter-spacing:.4px;min-width:56px}
|
||||||
|
.ym-day-body{flex:1 1 auto;min-width:0}
|
||||||
|
.ym-day-name{font-size:14.5px;font-weight:600;color:var(--ym-ink);margin-bottom:3px}
|
||||||
|
.ym-day-note{font-size:13px;color:var(--ym-ink-soft);line-height:1.6}
|
||||||
|
/* 「今天」标签:白字压金底只有 3.25:1,11px 小字更看不清。
|
||||||
|
改用深金底 #8a6100(白字 5.54:1),既保住"金"的语义,又读得清。 */
|
||||||
|
.ym-day-tag{flex:0 0 auto;font-size:11px;font-weight:600;padding:2px 9px;border-radius:999px;background:#8a6100;color:#fff;letter-spacing:.5px}
|
||||||
|
.ym-extra{display:flex;flex-wrap:wrap;gap:8px}
|
||||||
|
.ym-extra-chip{font-size:13px;padding:5px 13px;border-radius:999px;background:var(--ym-gold-soft);color:#8b6508;border:1px solid rgba(184,134,11,.28);font-variant-numeric:tabular-nums}
|
||||||
|
.ym-empty{padding:18px 16px;border-radius:10px;background:var(--ym-canvas,#f7f8fa);border:1px dashed var(--ym-line)}
|
||||||
|
.ym-empty p{margin:0;font-size:14.5px;color:var(--ym-ink-soft)}
|
||||||
|
.ym-empty-note{margin-top:6px!important;font-size:13px!important;color:#9aa5b1!important}
|
||||||
|
|
||||||
|
/* 问答 */
|
||||||
|
.ym-faq{display:grid;gap:8px}
|
||||||
|
.ym-qa{border:1px solid var(--ym-line);border-radius:10px;background:#fff;overflow:hidden}
|
||||||
|
.ym-qa summary{cursor:pointer;padding:14px 16px;font-size:14.5px;font-weight:600;color:var(--ym-ink);list-style:none;display:flex;align-items:center;justify-content:space-between;gap:10px}
|
||||||
|
.ym-qa summary::-webkit-details-marker{display:none}
|
||||||
|
.ym-qa summary::after{content:"+";font-size:17px;font-weight:400;color:#9aa5b1;transition:transform .2s ease;flex:0 0 auto}
|
||||||
|
.ym-qa[open] summary::after{content:"−"}
|
||||||
|
.ym-qa-body{padding:0 16px 15px;font-size:14px;color:var(--ym-ink-soft);line-height:1.8}
|
||||||
|
|
||||||
|
/* 底部 */
|
||||||
|
.ym-foot{margin-top:40px;text-align:center}
|
||||||
|
.ym-foot-line{width:52px;height:2px;margin:0 auto 18px;border-radius:2px;background:linear-gradient(90deg,transparent,var(--ym-gold),transparent);opacity:.7}
|
||||||
|
.ym-foot-text{margin:0 0 10px;font-size:14px;color:#5f6b78;letter-spacing:2px}
|
||||||
|
.ym-foot-link{margin:0}
|
||||||
|
.ym-foot-link a{display:inline-block;font-size:14px;color:var(--ym-gold-text);text-decoration:none;padding:8px 22px;border:1px solid rgba(184,134,11,.4);border-radius:999px;transition:background-color .18s ease,color .18s ease}
|
||||||
|
.ym-foot-link a:hover{background:var(--ym-gold);color:#fff}
|
||||||
|
|
||||||
|
/* 窄屏 */
|
||||||
|
@media (max-width:640px){
|
||||||
|
.ym-hero{padding:34px 20px 28px;border-radius:12px}
|
||||||
|
.ym-hero-title{font-size:21px}
|
||||||
|
.ym-day{flex-wrap:wrap;gap:8px}
|
||||||
|
.ym-day-date{min-width:auto}
|
||||||
|
.ym-sec{padding:0}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 用户系统开了深色模式时,说明页也要能读 —— 不能因为皮肤深色就变成黑底黑字 */
|
||||||
|
@media (prefers-color-scheme:dark){
|
||||||
|
.ym-qa{background:#1f2933;border-color:#3a4a58}
|
||||||
|
.ym-qa summary{color:#e8edf2}
|
||||||
|
.ym-qa-body{color:#b8c2cc}
|
||||||
|
.ym-days .ym-day{background:#232f3a;border-color:#3a4a58}
|
||||||
|
.ym-day-name{color:#e8edf2}
|
||||||
|
.ym-day-note{color:#a7b2bd}
|
||||||
|
.ym-prose p{color:#b8c2cc}
|
||||||
|
.ym-sec-title{color:#e8edf2}
|
||||||
|
.ym-empty{background:#232f3a;border-color:#3a4a58}
|
||||||
|
.ym-empty p{color:#a7b2bd}
|
||||||
|
}
|
||||||
|
CSS;
|
||||||
|
return $css;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 说明页的固定路径
|
||||||
|
*
|
||||||
|
* 用固定 slug 而不是新建页面,理由:
|
||||||
|
* 新建页面会写进数据库,换主题或删主题后成孤儿;固定路径由主题判定,
|
||||||
|
* 升级即生效,卸载即消失,不留垃圾数据。
|
||||||
|
*/
|
||||||
|
function yang_mourning_page_slug()
|
||||||
|
{
|
||||||
|
// 允许用户在主题选项里改,默认 /memory
|
||||||
|
$slug = trim((string) kratos_option('g_rip_page_slug', 'memory'), '/');
|
||||||
|
return $slug === '' ? 'memory' : $slug;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 说明页的 URL
|
||||||
|
*/
|
||||||
|
function yang_mourning_page_url()
|
||||||
|
{
|
||||||
|
return home_url('/' . yang_mourning_page_slug() . '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接管这个路径的请求
|
||||||
|
*
|
||||||
|
* 用 `parse_request` 而不是 `template_redirect`:越早判定,越不会先走
|
||||||
|
* 一遍「404 判定 → 主题 404 模板」的流程,也省掉多余的查询。
|
||||||
|
*/
|
||||||
|
function yang_mourning_page_route($wp)
|
||||||
|
{
|
||||||
|
if (is_admin()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$slug = yang_mourning_page_slug();
|
||||||
|
$path = trim((string) parse_url($_SERVER['REQUEST_URI'] ?? '', PHP_URL_PATH), '/');
|
||||||
|
|
||||||
|
// 去掉子目录安装的前缀
|
||||||
|
$home_path = trim((string) parse_url(home_url('/'), PHP_URL_PATH), '/');
|
||||||
|
if ($home_path !== '' && strpos($path, $home_path) === 0) {
|
||||||
|
$path = trim(substr($path, strlen($home_path)), '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 允许带尾斜杠,也允许纯 slug
|
||||||
|
if ($path !== $slug) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 别抢占用户真的建过的页面。
|
||||||
|
* 如果站里已经有一个 slug 就叫 memory 的页面,用户点进去期待看到它,
|
||||||
|
* 结果被我们静默换成哀悼说明 —— 看起来就是"我的页面坏了",且毫无提示。
|
||||||
|
* 这个查询只在路径真的命中 slug 时才执行,正常请求零开销。
|
||||||
|
* 不用 function_exists 兜底:get_page_by_path 是 WP 1.5 就有的核心函数,
|
||||||
|
* 加兜底只会让"这道保护根本没生效"变得不可测。 */
|
||||||
|
if (get_page_by_path($slug)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组装一个最小可用的 WP 查询环境,让主题的 header/footer 正常工作
|
||||||
|
$wp->query_vars = array();
|
||||||
|
status_header(200);
|
||||||
|
$GLOBALS['wp_query'] = new WP_Query(array('page_id' => 0));
|
||||||
|
$GLOBALS['wp_query']->is_404 = false;
|
||||||
|
|
||||||
|
include get_template_directory() . '/pages/page-mourning.php';
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
add_action('parse_request', 'yang_mourning_page_route', 5);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册短代码,便于在任意文章里嵌入说明
|
||||||
|
*/
|
||||||
|
function yang_mourning_shortcode($atts = array())
|
||||||
|
{
|
||||||
|
return yang_mourning_page_content();
|
||||||
|
}
|
||||||
|
add_shortcode('yang_mourning', 'yang_mourning_shortcode');
|
||||||
+123
-4
@@ -65,19 +65,19 @@ function yang_mourning_presets()
|
|||||||
'name' => __('中国人民抗日战争胜利纪念日', 'kratos'),
|
'name' => __('中国人民抗日战争胜利纪念日', 'kratos'),
|
||||||
'note' => __('1945 年 9 月 2 日日本签署投降书,9 月 3 日定为胜利纪念日', 'kratos'),
|
'note' => __('1945 年 9 月 2 日日本签署投降书,9 月 3 日定为胜利纪念日', 'kratos'),
|
||||||
'group' => 'legal',
|
'group' => 'legal',
|
||||||
'default' => 1,
|
'default' => 0,
|
||||||
),
|
),
|
||||||
'09-30' => array(
|
'09-30' => array(
|
||||||
'name' => __('烈士纪念日', 'kratos'),
|
'name' => __('烈士纪念日', 'kratos'),
|
||||||
'note' => __('向人民英雄敬献花篮仪式在天安门广场举行', 'kratos'),
|
'note' => __('向人民英雄敬献花篮仪式在天安门广场举行', 'kratos'),
|
||||||
'group' => 'legal',
|
'group' => 'legal',
|
||||||
'default' => 1,
|
'default' => 0,
|
||||||
),
|
),
|
||||||
'12-13' => array(
|
'12-13' => array(
|
||||||
'name' => __('南京大屠杀死难者国家公祭日', 'kratos'),
|
'name' => __('南京大屠杀死难者国家公祭日', 'kratos'),
|
||||||
'note' => __('1937 年 12 月 13 日南京沦陷,30 万同胞遇难', 'kratos'),
|
'note' => __('1937 年 12 月 13 日南京沦陷,30 万同胞遇难', 'kratos'),
|
||||||
'group' => 'legal',
|
'group' => 'legal',
|
||||||
'default' => 1,
|
'default' => 0,
|
||||||
),
|
),
|
||||||
|
|
||||||
// ── B 类:民间惯例,默认不勾 ──
|
// ── B 类:民间惯例,默认不勾 ──
|
||||||
@@ -256,4 +256,123 @@ function yang_mourning_status_text()
|
|||||||
$is ? __('正在哀悼,站点已黑白', 'kratos') : __('不在哀悼日', 'kratos'),
|
$is ? __('正在哀悼,站点已黑白', 'kratos') : __('不在哀悼日', 'kratos'),
|
||||||
implode('、', $days)
|
implode('、', $days)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 哀悼期间在页面顶部显示一条提示,链接到说明页
|
||||||
|
*
|
||||||
|
* ── 为什么必须放在顶部 ────────────────────────────────────────
|
||||||
|
* 用户的原话是「别人都不知道咋回事,为啥变黑白」。只把入口藏在页脚,
|
||||||
|
* 绝大多数人根本不会看到 —— 他们看到一片灰就关掉了。
|
||||||
|
* 所以提示条要出现在**首屏就能看到**的位置。
|
||||||
|
*
|
||||||
|
* ── 为什么不做成弹窗 ──────────────────────────────────────────
|
||||||
|
* 弹窗会挡住内容,纪念日当天每个访客每次访问都被弹一次,是骚扰。
|
||||||
|
* 一条可以关闭的细横条才是合适的强度,且关闭状态记在 sessionStorage
|
||||||
|
* (用 session 而不是 localStorage:当天关闭后,第二天不应该还记着)。
|
||||||
|
*/
|
||||||
|
function yang_mourning_notice_bar()
|
||||||
|
{
|
||||||
|
// 只在真正哀悼时显示,且不在说明页自身显示(那页自己会说明)
|
||||||
|
if (is_admin() || !yang_is_mourning_today()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (yang_mourning_page_is_current()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$url = yang_mourning_page_url();
|
||||||
|
$name = yang_mourning_today_name();
|
||||||
|
$text = $name !== ''
|
||||||
|
? sprintf(__('今天是%s,本站以黑白致哀。', 'kratos'), $name)
|
||||||
|
: __('今天是纪念日,本站以黑白致哀。', 'kratos');
|
||||||
|
?>
|
||||||
|
<div id="yang-mourning-bar" class="yang-mourning-bar" role="region" aria-label="<?php esc_attr_e('哀悼提示', 'kratos'); ?>">
|
||||||
|
<span class="ymb-dot" aria-hidden="true"></span>
|
||||||
|
<span class="ymb-text"><?php echo esc_html($text); ?></span>
|
||||||
|
<a class="ymb-link" href="<?php echo esc_url($url); ?>"><?php _e('了解详情', 'kratos'); ?></a>
|
||||||
|
<button type="button" class="ymb-close" aria-label="<?php esc_attr_e('关闭提示', 'kratos'); ?>">×</button>
|
||||||
|
</div>
|
||||||
|
<style>
|
||||||
|
.yang-mourning-bar{position:relative;z-index:9990;display:flex;align-items:center;justify-content:center;flex-wrap:wrap;gap:10px;padding:9px 46px 9px 18px;background:#2b2f36;color:#e8eaed;font-size:13.5px;line-height:1.6;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Microsoft YaHei",sans-serif;letter-spacing:.2px}
|
||||||
|
.yang-mourning-bar .ymb-dot{width:6px;height:6px;border-radius:50%;background:#b0b7c0;flex:0 0 auto}
|
||||||
|
.yang-mourning-bar .ymb-text{opacity:.92}
|
||||||
|
.yang-mourning-bar .ymb-link{color:#fff;text-decoration:none;border-bottom:1px solid rgba(255,255,255,.42);padding-bottom:1px;transition:border-color .18s ease}
|
||||||
|
.yang-mourning-bar .ymb-link:hover{border-bottom-color:#fff}
|
||||||
|
.yang-mourning-bar .ymb-close{position:absolute;right:12px;top:50%;transform:translateY(-50%);background:none;border:0;color:#9aa3ad;font-size:19px;line-height:1;cursor:pointer;padding:2px 7px;border-radius:4px}
|
||||||
|
.yang-mourning-bar .ymb-close:hover{color:#fff;background:rgba(255,255,255,.1)}
|
||||||
|
.yang-mourning-bar.is-hidden{display:none}
|
||||||
|
@media (max-width:560px){
|
||||||
|
.yang-mourning-bar{font-size:12.5px;padding:8px 42px 8px 12px;gap:7px;justify-content:flex-start}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
var bar = document.getElementById('yang-mourning-bar');
|
||||||
|
if (!bar) return;
|
||||||
|
// 当天关闭过就不再显示。用 sessionStorage 而非 localStorage:
|
||||||
|
// 明天是另一天,不该还记着昨天的关闭操作。
|
||||||
|
var KEY = 'yangMournBarClosed:' + <?php echo wp_json_encode(current_time('Ymd')); ?>;
|
||||||
|
try { if (sessionStorage.getItem(KEY) === '1') { bar.className += ' is-hidden'; return; } } catch (e) {}
|
||||||
|
var btn = bar.querySelector('.ymb-close');
|
||||||
|
if (btn) {
|
||||||
|
btn.addEventListener('click', function () {
|
||||||
|
bar.className += ' is-hidden';
|
||||||
|
try { sessionStorage.setItem(KEY, '1'); } catch (e) {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
add_action('wp_body_open', 'yang_mourning_notice_bar', 5);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 页脚常驻入口
|
||||||
|
*
|
||||||
|
* 提示条只在哀悼日出现,但说明页应该随时可达 ——
|
||||||
|
* 有人可能事后才知道,想去看看是怎么回事。
|
||||||
|
*/
|
||||||
|
function yang_mourning_footer_link()
|
||||||
|
{
|
||||||
|
ob_start();
|
||||||
|
?>
|
||||||
|
<p class="yang-mourning-footer">
|
||||||
|
<a href="<?php echo esc_url(yang_mourning_page_url()); ?>"><?php _e('纪念日哀悼说明', 'kratos'); ?></a>
|
||||||
|
</p>
|
||||||
|
<style>
|
||||||
|
.yang-mourning-footer{margin:6px 0 0}
|
||||||
|
.yang-mourning-footer a{color:inherit;opacity:.75;text-decoration:none;border-bottom:1px dotted currentColor;font-size:13px;transition:opacity .18s ease}
|
||||||
|
.yang-mourning-footer a:hover{opacity:1}
|
||||||
|
</style>
|
||||||
|
<?php
|
||||||
|
echo ob_get_clean();
|
||||||
|
}
|
||||||
|
add_action('wp_footer', 'yang_mourning_footer_link', 20);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前请求是不是说明页自己
|
||||||
|
*/
|
||||||
|
function yang_mourning_page_is_current()
|
||||||
|
{
|
||||||
|
$path = trim((string) parse_url($_SERVER['REQUEST_URI'] ?? '', PHP_URL_PATH), '/');
|
||||||
|
$home_path = trim((string) parse_url(home_url('/'), PHP_URL_PATH), '/');
|
||||||
|
if ($home_path !== '' && strpos($path, $home_path) === 0) {
|
||||||
|
$path = trim(substr($path, strlen($home_path)), '/');
|
||||||
|
}
|
||||||
|
return ($path === yang_mourning_page_slug());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 今天哀悼的纪念日名称(用于提示条文案)
|
||||||
|
*
|
||||||
|
* 取第一个命中的,取不到返回空串。
|
||||||
|
*/
|
||||||
|
function yang_mourning_today_name()
|
||||||
|
{
|
||||||
|
$md = current_time('m') . '-' . current_time('d');
|
||||||
|
$presets = yang_mourning_presets();
|
||||||
|
if (isset($presets[$md]) && in_array($md, yang_mourning_days(), true)) {
|
||||||
|
return $presets[$md]['name'];
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|||||||
+15
-1
@@ -268,7 +268,7 @@ $options[] = array(
|
|||||||
|
|
||||||
$options[] = array(
|
$options[] = array(
|
||||||
'name' => __('纪念日清单', 'kratos'),
|
'name' => __('纪念日清单', 'kratos'),
|
||||||
'desc' => __('勾选需要自动黑白的日期。前三个是法定纪念日(默认已勾),后面几个是民间通行做法,按自己意愿决定。日期按站点时区判断,只比对月和日,每年循环。', 'kratos'),
|
'desc' => __('勾选需要自动黑白的日期。<b>默认一个都不勾</b>,请按自己的意愿挑选。前三个是法定纪念日,后面几个是民间通行做法。日期按站点时区判断,只比对月和日,每年循环。', 'kratos'),
|
||||||
'id' => 'g_rip_days',
|
'id' => 'g_rip_days',
|
||||||
'std' => $rip_default,
|
'std' => $rip_default,
|
||||||
'type' => 'multicheck',
|
'type' => 'multicheck',
|
||||||
@@ -282,6 +282,20 @@ $options[] = array(
|
|||||||
'type' => 'text',
|
'type' => 'text',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$options[] = array(
|
||||||
|
'name' => __('说明页路径', 'kratos'),
|
||||||
|
'desc' => sprintf(
|
||||||
|
/* 注意:desc 会过 wp_kses($allowedtags),那是 WordPress 的默认白名单,
|
||||||
|
只放行 a/abbr/b/br/em/i/strong 等,**不放行 <code>** ——
|
||||||
|
写 <code> 会被静默剥掉,页面上只剩裸文字。所以这里用 <b> 强调。 */
|
||||||
|
__('变黑白时,页面顶部会出现一条提示,点进去是一个说明页面(彩色),讲清楚为什么变黑白、哪几天会变。<br>这里可以改那个页面的路径。留空默认 <b>memory</b>,即 %s<br><b>注意:</b>不要和已有文章或页面的路径重名。', 'kratos'),
|
||||||
|
esc_url(home_url('/memory/'))
|
||||||
|
),
|
||||||
|
'id' => 'g_rip_page_slug',
|
||||||
|
'std' => 'memory',
|
||||||
|
'type' => 'text',
|
||||||
|
);
|
||||||
|
|
||||||
$options[] = array(
|
$options[] = array(
|
||||||
'desc' => yang_mourning_status_text(),
|
'desc' => yang_mourning_status_text(),
|
||||||
'type' => 'info',
|
'type' => 'info',
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* 哀悼说明页的模板
|
||||||
|
*
|
||||||
|
* 由 inc/theme-mourning-page.php 的 yang_mourning_page_route() 直接 include,
|
||||||
|
* 不经过 WordPress 的模板层级。
|
||||||
|
*
|
||||||
|
* 关键点:这一页**必须是彩色的**。全站在纪念日会挂
|
||||||
|
* `html{filter:grayscale(100%)}`(wp_head 优先级 1),
|
||||||
|
* 本模板在 wp_head 优先级 2 里把它反转回来。
|
||||||
|
* 只反转 html 的 filter 不够 —— 见下方注释。
|
||||||
|
*
|
||||||
|
* @author Yongquan Yang <yangyq.net>
|
||||||
|
* @license MIT License
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('ABSPATH')) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 把灰度反转掉
|
||||||
|
*
|
||||||
|
* 为什么写两遍:
|
||||||
|
* - `html{filter:none}` 取消外面的灰度;
|
||||||
|
* - 但某些浏览器在 filter 从有到无的瞬间会留下合成层缓存,导致首屏仍是灰的,
|
||||||
|
* 所以补一条 `filter:grayscale(0%)` 显式声明,比 none 更稳。
|
||||||
|
*
|
||||||
|
* 权限用 `!important`:主题的灰度样式在 wp_head 优先级 1 输出,
|
||||||
|
* 本段在优先级 2,同优先级下后写的赢,加 !important 是防其它插件插入的覆盖。
|
||||||
|
*/
|
||||||
|
function yang_mourning_page_ungrayscale()
|
||||||
|
{
|
||||||
|
echo '<style id="yang-mourning-page-color">'
|
||||||
|
. "\nhtml{filter:none !important;-webkit-filter:none !important;}"
|
||||||
|
. "\nhtml{filter:grayscale(0%) !important;-webkit-filter:grayscale(0%) !important;}"
|
||||||
|
. "\nhtml body{-webkit-transform:none !important;}"
|
||||||
|
. "\n</style>\n";
|
||||||
|
}
|
||||||
|
add_action('wp_head', 'yang_mourning_page_ungrayscale', 2);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 输出本页样式
|
||||||
|
*/
|
||||||
|
function yang_mourning_page_print_css()
|
||||||
|
{
|
||||||
|
echo '<style id="yang-mourning-page">' . yang_mourning_page_css() . "</style>\n";
|
||||||
|
}
|
||||||
|
add_action('wp_head', 'yang_mourning_page_print_css', 3);
|
||||||
|
|
||||||
|
get_header();
|
||||||
|
?>
|
||||||
|
<div class="k-main <?php echo kratos_option('top_select', 'banner'); ?>" style="background:<?php echo kratos_option('g_background', '#f5f5f5'); ?>">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<?php echo yang_mourning_page_content(); ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php get_footer(); ?>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
Theme Name: Kratos_Yang
|
Theme Name: Kratos_Yang
|
||||||
Text Domain: kratos_Yang
|
Text Domain: kratos_Yang
|
||||||
Version: 3.2.6
|
Version: 3.2.7
|
||||||
Requires PHP: 7.0
|
Requires PHP: 7.0
|
||||||
Description: 这是一个Kratos主题的分支版本,用来增加一些定制化功能。
|
Description: 这是一个Kratos主题的分支版本,用来增加一些定制化功能。
|
||||||
Tags: 博客, 双栏, 响应式, 自定义背景, 自定义颜色, 自定义图标, 自定义菜单, 特色图像, 收录优化, 邮件中心, 主题选项, 小工具, 文章置顶, 无障碍友好
|
Tags: 博客, 双栏, 响应式, 自定义背景, 自定义颜色, 自定义图标, 自定义菜单, 特色图像, 收录优化, 邮件中心, 主题选项, 小工具, 文章置顶, 无障碍友好
|
||||||
|
|||||||
Reference in New Issue
Block a user