现象:#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 也改成与首屏同口径的顶层计数。
270 lines
12 KiB
PHP
270 lines
12 KiB
PHP
<?php
|
||
/**
|
||
* 评论模板
|
||
*
|
||
* ── 3.2.18 重写要点 ──────────────────────────────────────────
|
||
* 1. **列表容器改成 <ol>**。原来 `<div class="list">` 里直接放
|
||
* `wp_list_comments()` 输出的 `<li>`,标签层级非法。浏览器会
|
||
* 自行"修复"结构,而嵌套回复(`<ul class="children">`)在这种
|
||
* 被修复过的树里容易错位 —— 这是"评论看起来乱"的一个真实来源。
|
||
*
|
||
* 2. **回复状态条**。点「回复」时除了把表单挪过去,还在表单上方
|
||
* 显示「正在回复 @某人 / 取消」。旧实现只有表单被挪动这一条线索,
|
||
* 用户在长评论区里挪动后常常不知道自己到底在回复谁。
|
||
*
|
||
* 3. **表单加 nonce**。AJAX 提交走 nonce 校验,没有它任何第三方
|
||
* 页面都能借访客的浏览器刷评论。
|
||
*
|
||
* 4. **提交按钮的禁用态由 JS 控制**,模板里给出 `.is-loading` 的样式钩子。
|
||
*
|
||
* ── 3.2.19 修的两个结构问题 ──────────────────────────────────
|
||
* 5. **取数不再交给 `wp_list_comments()` 推导**。3.2.18 传了
|
||
* `page=1` / `per_page=10`,而核心那段"独立查询"分支在内层
|
||
* 条件不成立时**不会给 `$_comments` 赋值**(注释说会回退到
|
||
* `$wp_query->comments`,但代码里没有这个回退),结果
|
||
* `paged_walk(null, ...)` 输出空串 —— 评论一条都不显示。
|
||
* 现在由 `yang_comment_page()` 取好、当第二个参数传进去。
|
||
*
|
||
* 6. **子评论容器改成 `yang-children`**(见 `Yang_Walker_Comment`)。
|
||
* 核心 `start_lvl()` 固定输出 `class="children"`,而样式表里
|
||
* 几十条规则全按 `.yang-children` 写,类名对不上就全部失效。
|
||
*
|
||
* @author Yongquan Yang <yangyq.net>
|
||
* @license MIT License
|
||
*/
|
||
|
||
if (isset($_SERVER['SCRIPT_FILENAME']) && 'comments.php' == basename($_SERVER['SCRIPT_FILENAME'])) {
|
||
die();
|
||
}
|
||
|
||
require get_template_directory() . '/pages/page-smilies.php';
|
||
|
||
$yang_post_id = get_the_ID();
|
||
$yang_comment_count = (int) get_comments_number($yang_post_id);
|
||
$yang_max_depth = (int) get_option('thread_comments_depth', 5);
|
||
if ($yang_max_depth <= 0) {
|
||
$yang_max_depth = 5;
|
||
}
|
||
$yang_threaded = (bool) get_option('thread_comments');
|
||
$yang_has_comments = ($yang_comment_count > 0);
|
||
|
||
/* 评论是否分页:分页时首屏只给「加载更多」,不一次性铺全部 */
|
||
$yang_paged = (bool) get_option('page_comments');
|
||
$yang_per_page = (int) get_option('comments_per_page', 20);
|
||
if ($yang_per_page <= 0) {
|
||
$yang_per_page = 20;
|
||
}
|
||
?>
|
||
<div class="comments" id="comments">
|
||
|
||
<div class="comments-head">
|
||
<h3 class="title">
|
||
<?php
|
||
if ($yang_comment_count > 0) {
|
||
printf(
|
||
/* translators: %s: 评论条数 */
|
||
esc_html__('评论(%s)', 'kratos'),
|
||
esc_html(number_format_i18n($yang_comment_count))
|
||
);
|
||
} else {
|
||
esc_html_e('评论', 'kratos');
|
||
}
|
||
?>
|
||
</h3>
|
||
<?php if (comments_open()) : ?>
|
||
<a class="comments-jump-form" href="#respond"><?php esc_html_e('写下你的评论', 'kratos'); ?></a>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<?php if (!comments_open() && $yang_comment_count === 0) : ?>
|
||
|
||
<p class="comments-closed"><?php esc_html_e('评论已关闭。', 'kratos'); ?></p>
|
||
|
||
<?php elseif (!comments_open()) : ?>
|
||
|
||
<p class="comments-closed"><?php esc_html_e('评论已关闭,但你可以阅读上面的讨论。', 'kratos'); ?></p>
|
||
|
||
<?php endif; ?>
|
||
|
||
<?php if ($yang_has_comments) : ?>
|
||
<?php
|
||
/* <ol> 而不是 <div>:<li> 的父元素必须是列表容器。
|
||
加 reversed/start 会打乱视觉顺序,所以只用普通 <ol>。 */
|
||
?>
|
||
<ol class="yang-comment-list" id="yang-comment-list">
|
||
<?php
|
||
/* 取数自己来,不让 wp_list_comments 去推导分页。
|
||
原因见 inc/theme-comments.php 里 yang_comment_page() 的注释:
|
||
当它推导出的当前页与传入的 page 相等时,内部 $_comments
|
||
不会被赋值,列表直接是空的。显式传数组就绕开了那段逻辑。 */
|
||
$yang_page_comments = yang_comment_page(
|
||
$yang_post_id,
|
||
1,
|
||
$yang_paged ? $yang_per_page : 0
|
||
);
|
||
|
||
wp_list_comments(array(
|
||
'type' => 'comment',
|
||
'callback' => 'comment_callbacks',
|
||
'walker' => new Yang_Walker_Comment(),
|
||
'style' => 'ol',
|
||
'max_depth' => $yang_threaded ? $yang_max_depth : 1,
|
||
'avatar_size' => 48,
|
||
'per_page' => 0,
|
||
'page' => 0,
|
||
), $yang_page_comments);
|
||
?>
|
||
</ol>
|
||
|
||
<?php
|
||
/* 加载更多:只在「有分页」且「顶层评论数超过一页」时出现。
|
||
判据必须与 AJAX 的 has_more 同口径(都按顶层算),
|
||
否则会出现"按钮还在、点了却什么都没有"。
|
||
旧模板无条件输出,即使只有 1 条评论也显示一个点不动的按钮。 */
|
||
$yang_top_count = (int) get_comments(array(
|
||
'post_id' => $yang_post_id,
|
||
'status' => 'approve',
|
||
'parent' => 0,
|
||
'count' => true,
|
||
));
|
||
$yang_has_more = $yang_paged && $yang_top_count > $yang_per_page;
|
||
?>
|
||
<div class="comments-more" id="yang-comments-more"<?php echo $yang_has_more ? '' : ' hidden'; ?>>
|
||
<button type="button" class="comments-more-btn" data-page="2">
|
||
<?php esc_html_e('加载更多评论', 'kratos'); ?>
|
||
</button>
|
||
</div>
|
||
|
||
<?php elseif (comments_open()) : ?>
|
||
|
||
<p class="comments-empty"><?php esc_html_e('还没有评论,来说第一句吧。', 'kratos'); ?></p>
|
||
|
||
<?php endif; ?>
|
||
|
||
<?php if (comments_open()) : ?>
|
||
|
||
<?php if (get_option('comment_registration') && !is_user_logged_in()) : ?>
|
||
|
||
<div id="respond" class="comment-respond">
|
||
<p class="comments-login-required">
|
||
<?php
|
||
printf(
|
||
/* translators: %s: 登录链接 */
|
||
wp_kses(
|
||
__('您需要 <a href="%s">登录</a> 之后才可以评论。', 'kratos'),
|
||
array('a' => array('href' => array()))
|
||
),
|
||
esc_url(wp_login_url(get_permalink()))
|
||
);
|
||
?>
|
||
</p>
|
||
</div>
|
||
|
||
<?php else : ?>
|
||
|
||
<div id="respond" class="comment-respond mt-4">
|
||
<h3 class="comment-reply-title">
|
||
<span id="yang-reply-title-text"><?php esc_html_e('发表评论', 'kratos'); ?></span>
|
||
<?php
|
||
/* 取消回复按钮:由 JS 控制显示,模板里先藏起来。
|
||
不用 WordPress 的 cancel_comment_reply_link():
|
||
它生成的 <a href> 依赖 JS 拦截,没 JS 时会跳到
|
||
?replytocom= 的地址,行为很怪。这里用 button。 */
|
||
?>
|
||
<button type="button" id="yang-cancel-reply" class="comment-cancel-reply" hidden>
|
||
<?php esc_html_e('取消回复', 'kratos'); ?>
|
||
</button>
|
||
</h3>
|
||
|
||
<?php
|
||
/* 正在回复谁:JS 更新这段文字。
|
||
放在表单上方而不是按钮旁边,是因为表单会被"搬"到
|
||
被回复评论的下方,粘在按钮上的提示会跟着乱跑。 */
|
||
?>
|
||
<p class="comment-reply-hint" id="yang-reply-hint" hidden></p>
|
||
|
||
<form id="commentform" name="commentform" class="yang-comment-form" method="post"
|
||
action="<?php echo esc_url(site_url('/wp-comments-post.php')); ?>">
|
||
|
||
<?php if (!is_user_logged_in()) : ?>
|
||
<?php
|
||
/* 已登录用户的昵称邮箱从 cookie 里取,未登录才显示输入框。
|
||
esc_attr 必须做:cookie 里的值是用户可控的。 */
|
||
$yang_commenter = wp_get_current_commenter();
|
||
?>
|
||
<div class="comment-info">
|
||
<div class="comment-field comment-form-author">
|
||
<label for="author" class="sr-only"><?php esc_html_e('昵称', 'kratos'); ?></label>
|
||
<span class="comment-field-icon" aria-hidden="true"><i class="kicon i-user"></i></span>
|
||
<input class="comment-input" id="author" name="author" type="text"
|
||
placeholder="<?php esc_attr_e('昵称(必填)', 'kratos'); ?>"
|
||
value="<?php echo esc_attr($yang_commenter['comment_author']); ?>"
|
||
required="required" autocomplete="nickname" maxlength="60">
|
||
</div>
|
||
<div class="comment-field comment-form-email">
|
||
<label for="email" class="sr-only"><?php esc_html_e('邮箱', 'kratos'); ?></label>
|
||
<span class="comment-field-icon" aria-hidden="true"><i class="kicon i-cemail"></i></span>
|
||
<input class="comment-input" id="email" name="email" type="email"
|
||
placeholder="<?php esc_attr_e('邮箱(必填,不会公开)', 'kratos'); ?>"
|
||
value="<?php echo esc_attr($yang_commenter['comment_author_email']); ?>"
|
||
required="required" autocomplete="email" maxlength="120">
|
||
</div>
|
||
<div class="comment-field comment-form-url">
|
||
<label for="url" class="sr-only"><?php esc_html_e('网址', 'kratos'); ?></label>
|
||
<span class="comment-field-icon" aria-hidden="true"><i class="kicon i-url"></i></span>
|
||
<input class="comment-input" id="url" name="url" type="url"
|
||
placeholder="<?php esc_attr_e('网址(选填)', 'kratos'); ?>"
|
||
value="<?php echo esc_attr($yang_commenter['comment_author_url']); ?>"
|
||
autocomplete="url" maxlength="200">
|
||
</div>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<div class="comment-textarea">
|
||
<label for="comment" class="sr-only"><?php esc_html_e('评论内容', 'kratos'); ?></label>
|
||
<textarea class="comment-input" id="comment" name="comment" rows="6"
|
||
placeholder="<?php esc_attr_e('说点什么…(支持 :smile: 这类表情代码)', 'kratos'); ?>"
|
||
required="required" maxlength="65525"></textarea>
|
||
|
||
<div class="comment-bar">
|
||
<div class="comment-bar-left">
|
||
<button type="button" class="comment-smile-btn" id="yang-smile-btn"
|
||
aria-expanded="false" aria-controls="yang-smile-panel"
|
||
title="<?php esc_attr_e('插入表情', 'kratos'); ?>">
|
||
<i class="kicon i-face" aria-hidden="true"></i>
|
||
</button>
|
||
<div class="comment-smile-panel" id="yang-smile-panel" hidden>
|
||
<?php echo $smilies; ?>
|
||
</div>
|
||
</div>
|
||
<div class="comment-bar-right">
|
||
<span class="comment-status" id="yang-comment-status" role="status" aria-live="polite"></span>
|
||
<button type="submit" id="submit" class="comment-submit">
|
||
<?php esc_html_e('提交评论', 'kratos'); ?>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<?php
|
||
/* honeypot:把这个字段留空的是人,填了的是机器人。
|
||
CSS 用 position:absolute 移出视口,不能用 display:none
|
||
—— 部分爬虫会跳过隐藏字段,但多数不会跳过"移出视口"的。 */
|
||
?>
|
||
<p class="comment-hp" aria-hidden="true">
|
||
<label for="yang_hp"><?php esc_html_e('请勿填写此项', 'kratos'); ?></label>
|
||
<input type="text" id="yang_hp" name="yang_hp" value="" tabindex="-1" autocomplete="off">
|
||
</p>
|
||
|
||
<?php
|
||
wp_nonce_field('yang_comment_submit', 'yang_comment_nonce');
|
||
comment_id_fields();
|
||
do_action('comment_form', $yang_post_id);
|
||
?>
|
||
</form>
|
||
</div>
|
||
|
||
<?php endif; ?>
|
||
|
||
<?php endif; ?>
|
||
</div>
|